ERP-node/frontend/components/admin/department/DepartmentMembers.tsx

248 lines
8.2 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { Plus, X, Star } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Badge } from "@/components/ui/badge";
import type { Department, DepartmentMember } from "@/types/department";
import * as departmentAPI from "@/lib/api/department";
interface DepartmentMembersProps {
companyCode: string;
selectedDepartment: Department | null;
}
/**
* 부서 인원 관리 컴포넌트
*/
export function DepartmentMembers({ companyCode, selectedDepartment }: DepartmentMembersProps) {
const [members, setMembers] = useState<DepartmentMember[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
const [searchUserId, setSearchUserId] = useState("");
// 부서원 목록 로드
useEffect(() => {
if (selectedDepartment) {
loadMembers();
}
}, [selectedDepartment]);
const loadMembers = async () => {
if (!selectedDepartment) return;
setIsLoading(true);
try {
const response = await departmentAPI.getDepartmentMembers(selectedDepartment.dept_code);
if (response.success && response.data) {
setMembers(response.data);
} else {
console.error("부서원 목록 로드 실패:", response.error);
setMembers([]);
}
} catch (error) {
console.error("부서원 목록 로드 실패:", error);
setMembers([]);
} finally {
setIsLoading(false);
}
};
// 부서원 추가
const handleAddMember = async () => {
if (!searchUserId.trim() || !selectedDepartment) return;
try {
const response = await departmentAPI.addDepartmentMember(
selectedDepartment.dept_code,
searchUserId
);
if (response.success) {
setIsAddModalOpen(false);
setSearchUserId("");
loadMembers();
} else {
alert(response.error || "부서원 추가에 실패했습니다.");
}
} catch (error) {
console.error("부서원 추가 실패:", error);
alert("부서원 추가 중 오류가 발생했습니다.");
}
};
// 부서원 제거
const handleRemoveMember = async (userId: string) => {
if (!selectedDepartment) return;
if (!confirm("이 부서에서 사용자를 제외하시겠습니까?")) return;
try {
const response = await departmentAPI.removeDepartmentMember(
selectedDepartment.dept_code,
userId
);
if (response.success) {
loadMembers();
} else {
alert(response.error || "부서원 제거에 실패했습니다.");
}
} catch (error) {
console.error("부서원 제거 실패:", error);
alert("부서원 제거 중 오류가 발생했습니다.");
}
};
// 주 부서 설정
const handleSetPrimaryDepartment = async (userId: string) => {
if (!selectedDepartment) return;
try {
const response = await departmentAPI.setPrimaryDepartment(
selectedDepartment.dept_code,
userId
);
if (response.success) {
loadMembers();
} else {
alert(response.error || "주 부서 설정에 실패했습니다.");
}
} catch (error) {
console.error("주 부서 설정 실패:", error);
alert("주 부서 설정 중 오류가 발생했습니다.");
}
};
if (!selectedDepartment) {
return (
<div className="flex h-64 flex-col items-center justify-center rounded-lg border bg-card p-8 shadow-sm">
<p className="text-sm text-muted-foreground"> </p>
</div>
);
}
return (
<div className="space-y-4">
{/* 헤더 */}
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg font-semibold">{selectedDepartment.dept_name}</h3>
<p className="text-sm text-muted-foreground"> {members.length}</p>
</div>
<Button size="sm" className="h-9 gap-2 text-sm" onClick={() => setIsAddModalOpen(true)}>
<Plus className="h-4 w-4" />
</Button>
</div>
{/* 부서원 목록 */}
<div className="space-y-2 rounded-lg border bg-card p-4 shadow-sm">
{isLoading ? (
<div className="py-8 text-center text-sm text-muted-foreground"> ...</div>
) : members.length === 0 ? (
<div className="py-8 text-center text-sm text-muted-foreground">
. .
</div>
) : (
<div className="space-y-2">
{members.map((member) => (
<div
key={member.user_id}
className="flex items-center justify-between rounded-lg border bg-background p-3 transition-colors hover:bg-muted/50"
>
<div className="flex-1">
<div className="flex items-center gap-2">
<span className="font-medium">{member.user_name}</span>
<span className="text-sm text-muted-foreground">({member.user_id})</span>
{member.is_primary && (
<Badge variant="default" className="h-5 gap-1 text-xs">
<Star className="h-3 w-3" />
</Badge>
)}
</div>
<div className="mt-1 flex gap-4 text-xs text-muted-foreground">
{member.position_name && <span>: {member.position_name}</span>}
{member.email && <span>: {member.email}</span>}
{member.phone && <span>: {member.phone}</span>}
{member.cell_phone && <span>: {member.cell_phone}</span>}
</div>
</div>
<div className="flex gap-2">
{!member.is_primary && (
<Button
variant="outline"
size="sm"
className="h-8 gap-1 text-xs"
onClick={() => handleSetPrimaryDepartment(member.user_id)}
>
<Star className="h-3 w-3" />
</Button>
)}
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-destructive"
onClick={() => handleRemoveMember(member.user_id)}
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
)}
</div>
{/* 부서원 추가 모달 */}
<Dialog open={isAddModalOpen} onOpenChange={setIsAddModalOpen}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle> </DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="user_id">
ID <span className="text-destructive">*</span>
</Label>
<Input
id="user_id"
value={searchUserId}
onChange={(e) => setSearchUserId(e.target.value)}
placeholder="사용자 ID를 입력하세요"
autoFocus
/>
<p className="text-xs text-muted-foreground">
. .
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddModalOpen(false)}>
</Button>
<Button onClick={handleAddMember} disabled={!searchUserId.trim()}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}