diff --git a/backend-node/src/controllers/departmentController.ts b/backend-node/src/controllers/departmentController.ts index 93b37475..9e3f0b6a 100644 --- a/backend-node/src/controllers/departmentController.ts +++ b/backend-node/src/controllers/departmentController.ts @@ -250,25 +250,19 @@ export async function deleteDepartment(req: AuthenticatedRequest, res: Response) if (parseInt(hasChildren?.count || "0") > 0) { res.status(400).json({ success: false, - message: "하위 부서가 있는 부서는 삭제할 수 없습니다.", + message: "하위 부서가 있는 부서는 삭제할 수 없습니다. 먼저 하위 부서를 삭제해주세요.", }); return; } - // 부서원 확인 - const hasMembers = await queryOne(` - SELECT COUNT(*) as count - FROM user_dept + // 부서원 삭제 (부서 삭제 전에 먼저 삭제) + const deletedMembers = await query(` + DELETE FROM user_dept WHERE dept_code = $1 + RETURNING user_id `, [deptCode]); - if (parseInt(hasMembers?.count || "0") > 0) { - res.status(400).json({ - success: false, - message: "부서원이 있는 부서는 삭제할 수 없습니다.", - }); - return; - } + const memberCount = deletedMembers.length; // 부서 삭제 const result = await query(` @@ -285,11 +279,17 @@ export async function deleteDepartment(req: AuthenticatedRequest, res: Response) return; } - logger.info("부서 삭제 성공", { deptCode }); + logger.info("부서 삭제 성공", { + deptCode, + deptName: result[0].dept_name, + deletedMemberCount: memberCount + }); res.status(200).json({ success: true, - message: "부서가 삭제되었습니다.", + message: memberCount > 0 + ? `부서가 삭제되었습니다. (부서원 ${memberCount}명 제외됨)` + : "부서가 삭제되었습니다.", }); } catch (error) { logger.error("부서 삭제 실패", error); diff --git a/frontend/components/admin/department/DepartmentMembers.tsx b/frontend/components/admin/department/DepartmentMembers.tsx index 6fb398ec..7147971b 100644 --- a/frontend/components/admin/department/DepartmentMembers.tsx +++ b/frontend/components/admin/department/DepartmentMembers.tsx @@ -13,6 +13,7 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; +import { useToast } from "@/hooks/use-toast"; import type { Department, DepartmentMember } from "@/types/department"; import * as departmentAPI from "@/lib/api/department"; @@ -30,6 +31,7 @@ export function DepartmentMembers({ selectedDepartment, onMemberChange, }: DepartmentMembersProps) { + const { toast } = useToast(); const [members, setMembers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isAddModalOpen, setIsAddModalOpen] = useState(false); @@ -108,16 +110,31 @@ export function DepartmentMembers({ setSearchResults([]); loadMembers(); onMemberChange?.(); // 부서 구조 새로고침 + + // 성공 Toast 표시 + toast({ + title: "부서원 추가 완료", + description: "부서원이 추가되었습니다.", + variant: "default", + }); } else { if ((response as any).isDuplicate) { setDuplicateMessage(response.error || "이미 해당 부서의 부서원입니다."); } else { - alert(response.error || "부서원 추가에 실패했습니다."); + toast({ + title: "부서원 추가 실패", + description: response.error || "부서원 추가에 실패했습니다.", + variant: "destructive", + }); } } } catch (error) { console.error("부서원 추가 실패:", error); - alert("부서원 추가 중 오류가 발생했습니다."); + toast({ + title: "부서원 추가 실패", + description: "부서원 추가 중 오류가 발생했습니다.", + variant: "destructive", + }); } }; @@ -142,12 +159,27 @@ export function DepartmentMembers({ setMemberToRemove(null); loadMembers(); onMemberChange?.(); // 부서 구조 새로고침 + + // 성공 Toast 표시 + toast({ + title: "부서원 제거 완료", + description: `${memberToRemove.name} 님이 부서에서 제외되었습니다.`, + variant: "default", + }); } else { - alert(response.error || "부서원 제거에 실패했습니다."); + toast({ + title: "부서원 제거 실패", + description: response.error || "부서원 제거에 실패했습니다.", + variant: "destructive", + }); } } catch (error) { console.error("부서원 제거 실패:", error); - alert("부서원 제거 중 오류가 발생했습니다."); + toast({ + title: "부서원 제거 실패", + description: "부서원 제거 중 오류가 발생했습니다.", + variant: "destructive", + }); } }; @@ -163,12 +195,27 @@ export function DepartmentMembers({ if (response.success) { loadMembers(); + + // 성공 Toast 표시 + toast({ + title: "주 부서 설정 완료", + description: "주 부서가 변경되었습니다.", + variant: "default", + }); } else { - alert(response.error || "주 부서 설정에 실패했습니다."); + toast({ + title: "주 부서 설정 실패", + description: response.error || "주 부서 설정에 실패했습니다.", + variant: "destructive", + }); } } catch (error) { console.error("주 부서 설정 실패:", error); - alert("주 부서 설정 중 오류가 발생했습니다."); + toast({ + title: "주 부서 설정 실패", + description: "주 부서 설정 중 오류가 발생했습니다.", + variant: "destructive", + }); } }; diff --git a/frontend/components/admin/department/DepartmentStructure.tsx b/frontend/components/admin/department/DepartmentStructure.tsx index 49df54bc..4347d612 100644 --- a/frontend/components/admin/department/DepartmentStructure.tsx +++ b/frontend/components/admin/department/DepartmentStructure.tsx @@ -3,16 +3,11 @@ 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import type { Department, DepartmentFormData } from "@/types/department"; +import { useToast } from "@/hooks/use-toast"; +import type { Department } from "@/types/department"; import * as departmentAPI from "@/lib/api/department"; interface DepartmentStructureProps { @@ -31,6 +26,7 @@ export function DepartmentStructure({ onSelectDepartment, refreshTrigger, }: DepartmentStructureProps) { + const { toast } = useToast(); const [departments, setDepartments] = useState([]); const [expandedDepts, setExpandedDepts] = useState>(new Set()); const [isLoading, setIsLoading] = useState(false); @@ -40,10 +36,11 @@ export function DepartmentStructure({ 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(() => { @@ -97,16 +94,31 @@ export function DepartmentStructure({ setNewDeptName(""); setParentDeptForAdd(null); loadDepartments(); + + // 성공 Toast 표시 + toast({ + title: "부서 생성 완료", + description: `"${newDeptName}" 부서가 생성되었습니다.`, + variant: "default", + }); } else { if ((response as any).isDuplicate) { setDuplicateMessage(response.error || "이미 존재하는 부서명입니다."); } else { - alert(response.error || "부서 추가에 실패했습니다."); + toast({ + title: "부서 생성 실패", + description: response.error || "부서 추가에 실패했습니다.", + variant: "destructive", + }); } } } catch (error) { console.error("부서 추가 실패:", error); - alert("부서 추가 중 오류가 발생했습니다."); + toast({ + title: "부서 생성 실패", + description: "부서 추가 중 오류가 발생했습니다.", + variant: "destructive", + }); } }; @@ -124,15 +136,32 @@ export function DepartmentStructure({ 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 { - alert(response.error || "부서 삭제에 실패했습니다."); + // 삭제 확인 모달을 닫고 에러 모달을 표시 + setDeleteConfirmOpen(false); + setDeptToDelete(null); + setDeleteErrorMessage(response.error || "부서 삭제에 실패했습니다."); } } catch (error) { console.error("부서 삭제 실패:", error); - alert("부서 삭제 중 오류가 발생했습니다."); + setDeleteConfirmOpen(false); + setDeptToDelete(null); + setDeleteErrorMessage("부서 삭제 중 오류가 발생했습니다."); } }; @@ -160,15 +189,12 @@ export function DepartmentStructure({
{/* 부서 항목 */}
-
onSelectDepartment(dept)} - > +
onSelectDepartment(dept)}> {/* 확장/축소 아이콘 */} {hasChildren ? ( ) : (
@@ -192,7 +214,7 @@ export function DepartmentStructure({ {dept.dept_name} {/* 인원수 */} -
+
{dept.memberCount || 0}
@@ -214,7 +236,7 @@ export function DepartmentStructure({
{/* 부서 트리 */} -
+
{isLoading ? ( -
로딩 중...
+
로딩 중...
) : departments.length === 0 ? ( -
+
부서가 없습니다. 최상위 부서를 추가해주세요.
) : ( @@ -260,9 +282,7 @@ export function DepartmentStructure({ - - {parentDeptForAdd ? "하위 부서 추가" : "최상위 부서 추가"} - + {parentDeptForAdd ? "하위 부서 추가" : "최상위 부서 추가"}
@@ -301,10 +321,7 @@ export function DepartmentStructure({

{duplicateMessage}

- @@ -321,9 +338,7 @@ export function DepartmentStructure({

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

-

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

+

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

+ + +
); } -