"use client"; import { useState, useEffect } from "react"; import { Plus, ChevronDown, ChevronRight, Users, Trash2 } 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 { useToast } from "@/hooks/use-toast"; import type { Department } from "@/types/department"; import * as departmentAPI from "@/lib/api/department"; interface DepartmentStructureProps { companyCode: string; selectedDepartment: Department | null; onSelectDepartment: (department: Department | null) => void; refreshTrigger?: number; } /** * 부서 구조 컴포넌트 (트리 형태) */ export function DepartmentStructure({ companyCode, selectedDepartment, onSelectDepartment, refreshTrigger, }: DepartmentStructureProps) { const { toast } = useToast(); const [departments, setDepartments] = useState([]); const [expandedDepts, setExpandedDepts] = useState>(new Set()); const [isLoading, setIsLoading] = useState(false); // 부서 추가 모달 const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [parentDeptForAdd, setParentDeptForAdd] = useState(null); const [newDeptName, setNewDeptName] = useState(""); const [duplicateMessage, setDuplicateMessage] = useState(null); // 부서 삭제 확인 모달 const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [deptToDelete, setDeptToDelete] = useState<{ code: string; name: string } | null>(null); const [deleteErrorMessage, setDeleteErrorMessage] = useState(null); // 부서 목록 로드 useEffect(() => { loadDepartments(); }, [companyCode, refreshTrigger]); const loadDepartments = async () => { setIsLoading(true); try { const response = await departmentAPI.getDepartments(companyCode); if (response.success && response.data) { setDepartments(response.data); } else { console.error("부서 목록 로드 실패:", response.error); setDepartments([]); } } catch (error) { console.error("부서 목록 로드 실패:", error); setDepartments([]); } finally { setIsLoading(false); } }; // 부서 트리 구조 생성 const buildTree = (parentCode: string | null): Department[] => { return departments .filter((dept) => dept.parent_dept_code === parentCode) .sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); }; // 부서 추가 핸들러 const handleAddDepartment = (parentDeptCode: string | null = null) => { setParentDeptForAdd(parentDeptCode); setNewDeptName(""); setIsAddModalOpen(true); }; // 부서 저장 const handleSaveDepartment = async () => { if (!newDeptName.trim()) return; try { const response = await departmentAPI.createDepartment(companyCode, { dept_name: newDeptName, parent_dept_code: parentDeptForAdd, }); if (response.success) { setIsAddModalOpen(false); setNewDeptName(""); setParentDeptForAdd(null); loadDepartments(); // 성공 Toast 표시 toast({ title: "부서 생성 완료", description: `"${newDeptName}" 부서가 생성되었습니다.`, variant: "default", }); } else { if ((response as any).isDuplicate) { setDuplicateMessage(response.error || "이미 존재하는 부서명입니다."); } else { toast({ title: "부서 생성 실패", description: response.error || "부서 추가에 실패했습니다.", variant: "destructive", }); } } } catch (error) { console.error("부서 추가 실패:", error); toast({ title: "부서 생성 실패", description: "부서 추가 중 오류가 발생했습니다.", variant: "destructive", }); } }; // 부서 삭제 확인 요청 const handleDeleteDepartmentRequest = (deptCode: string, deptName: string) => { setDeptToDelete({ code: deptCode, name: deptName }); setDeleteConfirmOpen(true); }; // 부서 삭제 실행 const handleDeleteDepartmentConfirm = async () => { if (!deptToDelete) return; try { const response = await departmentAPI.deleteDepartment(deptToDelete.code); if (response.success) { // 삭제된 부서가 선택되어 있었다면 선택 해제 if (selectedDepartment?.dept_code === deptToDelete.code) { onSelectDepartment(null); } setDeleteConfirmOpen(false); setDeptToDelete(null); loadDepartments(); // 성공 메시지 Toast로 표시 (부서원 수 포함) toast({ title: "부서 삭제 완료", description: response.message || "부서가 삭제되었습니다.", variant: "default", }); } else { // 삭제 확인 모달을 닫고 에러 모달을 표시 setDeleteConfirmOpen(false); setDeptToDelete(null); setDeleteErrorMessage(response.error || "부서 삭제에 실패했습니다."); } } catch (error) { console.error("부서 삭제 실패:", error); setDeleteConfirmOpen(false); setDeptToDelete(null); setDeleteErrorMessage("부서 삭제 중 오류가 발생했습니다."); } }; // 확장/축소 토글 const toggleExpand = (deptCode: string) => { const newExpanded = new Set(expandedDepts); if (newExpanded.has(deptCode)) { newExpanded.delete(deptCode); } else { newExpanded.add(deptCode); } setExpandedDepts(newExpanded); }; // 부서 트리 렌더링 (재귀) const renderDepartmentTree = (parentCode: string | null, level: number = 0) => { const children = buildTree(parentCode); return children.map((dept) => { const hasChildren = departments.some((d) => d.parent_dept_code === dept.dept_code); const isExpanded = expandedDepts.has(dept.dept_code); const isSelected = selectedDepartment?.dept_code === dept.dept_code; return (
{/* 부서 항목 */}
onSelectDepartment(dept)}> {/* 확장/축소 아이콘 */} {hasChildren ? ( ) : (
)} {/* 부서명 */} {dept.dept_name} {/* 인원수 */}
{dept.memberCount || 0}
{/* 액션 버튼 */}
{/* 하위 부서 (재귀) */} {hasChildren && isExpanded && renderDepartmentTree(dept.dept_code, level + 1)}
); }); }; return (
{/* 헤더 */}

부서 구조

{/* 부서 트리 */}
{isLoading ? (
로딩 중...
) : departments.length === 0 ? (
부서가 없습니다. 최상위 부서를 추가해주세요.
) : ( renderDepartmentTree(null) )}
{/* 부서 추가 모달 */} {parentDeptForAdd ? "하위 부서 추가" : "최상위 부서 추가"}
setNewDeptName(e.target.value)} placeholder="부서명을 입력하세요" autoFocus />
{/* 중복 알림 모달 */} setDuplicateMessage(null)}> 중복 알림

{duplicateMessage}

{/* 부서 삭제 확인 모달 */} 부서 삭제 확인

{deptToDelete?.name} 부서를 삭제하시겠습니까?

이 작업은 되돌릴 수 없습니다.

{/* 부서 삭제 에러 모달 */} setDeleteErrorMessage(null)}> 삭제 불가

{deleteErrorMessage}

); }