From 37927b0ecf650a905dd24eaf75ed850fb55b1361 Mon Sep 17 00:00:00 2001 From: lizerk Date: Mon, 13 Apr 2026 23:20:20 +0300 Subject: [PATCH] refactor(frontend): add department action menu --- frontend/src/app/departments/[id]/page.tsx | 155 +++++++++++++++------ frontend/src/hooks/useDepartmentPage.ts | 20 +++ frontend/src/lib/api/employees.ts | 2 + 3 files changed, 138 insertions(+), 39 deletions(-) diff --git a/frontend/src/app/departments/[id]/page.tsx b/frontend/src/app/departments/[id]/page.tsx index ad2d92e3..5008b0ad 100644 --- a/frontend/src/app/departments/[id]/page.tsx +++ b/frontend/src/app/departments/[id]/page.tsx @@ -1,13 +1,14 @@ "use client"; import Link from "next/link"; -import { useParams } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import { useEffect, useRef, useState, type ReactNode, type RefObject } from "react"; import { ArrowLeft, Building2, Check, ChevronDown, + ChevronRight, Crown, PencilLine, Plus, @@ -524,11 +525,14 @@ function RoleEditorModal({ export default function DepartmentDetailPage() { const params = useParams<{ id: string }>(); + const router = useRouter(); const departmentId = Number(params?.id); const h = useDepartmentPage(departmentId); const [managementMode, setManagementMode] = useState(false); const [roleMenuOpenForId, setRoleMenuOpenForId] = useState(null); const roleMenuRef = useRef(null); + const [departmentMenuOpen, setDepartmentMenuOpen] = useState(false); + const departmentMenuRef = useRef(null); const activeMembersCount = h.members.filter((member) => member.is_active).length; const roleOptions = h.roles.map((role) => ({ id: role.id, name: role.name })); @@ -562,6 +566,29 @@ export default function DepartmentDetailPage() { }; }, [roleMenuOpenForId]); + useEffect(() => { + if (!departmentMenuOpen) return; + + const handlePointerDown = (event: MouseEvent) => { + if (departmentMenuRef.current && !departmentMenuRef.current.contains(event.target as Node)) { + setDepartmentMenuOpen(false); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setDepartmentMenuOpen(false); + } + }; + + document.addEventListener("mousedown", handlePointerDown); + document.addEventListener("keydown", handleEscape); + return () => { + document.removeEventListener("mousedown", handlePointerDown); + document.removeEventListener("keydown", handleEscape); + }; + }, [departmentMenuOpen]); + return (
@@ -608,49 +635,99 @@ export default function DepartmentDetailPage() { {h.department.description}

-
- {canUseManagementMode ? ( + {(canUseManagementMode || h.userPerms.can_manage) ? ( +
- ) : null} - {isManagementMode && h.userPerms.can_manage ? ( - <> - - - - ) : null} -
+ + {departmentMenuOpen ? ( +
+ {h.userPerms.can_manage ? ( + + ) : null} + + {h.userPerms.can_manage ? ( + + ) : null} + + {canUseManagementMode ? ( + + ) : null} + + {h.userPerms.can_manage ? ( + <> +
+ + + ) : null} +
+ ) : null} +
+ ) : null}
void; setSelectedMemberId: (id: number | null) => void; submitAddMember: () => Promise; + submitDeleteDepartment: () => Promise; submitHeadChange: () => Promise; submitQuickHeadChange: (headId: number) => Promise; submitHeadRemoval: () => Promise; @@ -336,6 +338,23 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle } }, [department, loadCore, selectedMemberId]); + const submitDeleteDepartment = useCallback(async () => { + if (!department) return false; + if (!window.confirm(`Удалить отдел «${department.name}»?`)) return false; + + try { + setPendingKey("department-delete"); + await apiClient.deleteDepartment(department.id); + toast.success("Отдел удалён"); + return true; + } catch (submitError) { + toast.error(getErrorMessage(submitError, "Не удалось удалить отдел")); + return false; + } finally { + setPendingKey(null); + } + }, [department]); + const submitHeadChange = useCallback(async () => { if (!department) return; @@ -531,6 +550,7 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle setSelectedHeadId, setSelectedMemberId, submitAddMember, + submitDeleteDepartment, submitHeadChange, submitQuickHeadChange, submitHeadRemoval, diff --git a/frontend/src/lib/api/employees.ts b/frontend/src/lib/api/employees.ts index 9368d323..9da6aa1c 100644 --- a/frontend/src/lib/api/employees.ts +++ b/frontend/src/lib/api/employees.ts @@ -53,6 +53,8 @@ export function createEmployeesApi(request: RequestFn) { return request(`/api/v1/departments/${qs ? '?' + qs : ''}`); }, getDepartment: (id: number | string) => request(`/api/v1/departments/${id}/`), + deleteDepartment: (id: number | string) => + request(`/api/v1/departments/${id}/`, { method: 'DELETE' }), updateDepartment: (id: number | string, data: { name?: string; description?: string }) => request(`/api/v1/departments/${id}/`, { method: 'PATCH',