From a4910421c3157d605e650be33158396d8f9dbbf9 Mon Sep 17 00:00:00 2001 From: lizerk Date: Tue, 14 Apr 2026 00:28:06 +0300 Subject: [PATCH] refactor(frontend): use people picker in department modal --- frontend/src/app/departments/[id]/page.tsx | 122 ++++++++++++++++++--- frontend/src/hooks/useDepartmentPage.ts | 16 +-- 2 files changed, 112 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/departments/[id]/page.tsx b/frontend/src/app/departments/[id]/page.tsx index e9396737..c391a413 100644 --- a/frontend/src/app/departments/[id]/page.tsx +++ b/frontend/src/app/departments/[id]/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; -import { useEffect, useRef, useState, type ReactNode, type RefObject } from "react"; +import { useEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react"; import { ArrowLeft, Building2, @@ -12,6 +12,7 @@ import { Crown, PencilLine, Plus, + Search, Trash2, UserPlus, Users, @@ -23,7 +24,6 @@ import { getDepartmentMembersListClassName, } from "@/components/departments/layout"; import { - getDepartmentMemberModalEmployeeLabel, getDepartmentMemberModalHelperText, getDepartmentMemberModalItems, getDepartmentMemberModalPlaceholder, @@ -36,7 +36,7 @@ import { SearchableSelectSingle } from "@/components/shared/SearchableSelect"; import { Modal } from "@/components/ui/Modal"; import { useDepartmentPage } from "@/hooks/useDepartmentPage"; import { displayUserName, userProfileLink } from "@/lib/shared"; -import type { DepartmentMemberLink } from "@/types/api"; +import type { DepartmentMemberLink, User } from "@/types/api"; type DepartmentMemberModalMode = "add" | "assignRole"; @@ -417,7 +417,7 @@ function AddMemberModal({ canAssignRoles: boolean; directoryError: string | null; isOpen: boolean; - items: Array<{ id: number; name: string }>; + items: User[]; loading: boolean; mode: DepartmentMemberModalMode; optionsLoading: boolean; @@ -431,6 +431,12 @@ function AddMemberModal({ selectedId: number | null; selectedRoleId: number | null; }) { + const [query, setQuery] = useState(""); + const handleClose = () => { + setQuery(""); + onClose(); + }; + const helperText = getDepartmentMemberModalHelperText( mode, optionsLoading, @@ -443,11 +449,26 @@ function AddMemberModal({ selectedEmployeeId: selectedId, selectedRoleId, }); + const filteredItems = useMemo(() => { + const normalizedQuery = query.trim().toLowerCase(); + if (!normalizedQuery) return items; + + return items.filter((employee) => { + const personName = displayUserName(employee).toLowerCase(); + const email = (employee.email || "").toLowerCase(); + const position = (employee.position?.name || "").toLowerCase(); + return ( + personName.includes(normalizedQuery) || + email.includes(normalizedQuery) || + position.includes(normalizedQuery) + ); + }); + }, [items, query]); return ( + ); + }) + ) : !directoryError && !optionsLoading ? ( +
+

Ничего не найдено

+

+ Попробуй изменить запрос или очистить поиск. +

+
+ ) : null} + + {directoryError ? (
diff --git a/frontend/src/hooks/useDepartmentPage.ts b/frontend/src/hooks/useDepartmentPage.ts index 5bd4a98f..48953861 100644 --- a/frontend/src/hooks/useDepartmentPage.ts +++ b/frontend/src/hooks/useDepartmentPage.ts @@ -35,7 +35,7 @@ type SavingKey = export type DepartmentPageController = { addMemberOpen: boolean; allEmployees: User[]; - assignableEmployees: Array<{ id: number; name: string }>; + assignableEmployees: User[]; currentUserId: number | null; department: Department | null; departmentDraft: { name: string; description: string }; @@ -54,7 +54,7 @@ export type DepartmentPageController = { roleEditorOpen: boolean; roleUsage: Record; roles: DepartmentRole[]; - selectableEmployees: Array<{ id: number; name: string }>; + selectableEmployees: User[]; selectedHeadId: number | null; selectedMemberId: number | null; selectedRoleId: number | null; @@ -272,19 +272,11 @@ export function useDepartmentPage(departmentId: number): DepartmentPageControlle members.filter((member) => member.is_active).map((member) => member.employee.id), ); - return allEmployees - .filter((employee) => !activeIds.has(employee.id)) - .map((employee) => ({ - id: employee.id, - name: `${employee.last_name || ""} ${employee.first_name || ""}`.trim() || employee.email, - })); + return allEmployees.filter((employee) => !activeIds.has(employee.id)); }, [allEmployees, members]); const assignableEmployees = useMemo(() => { - return allEmployees.map((employee) => ({ - id: employee.id, - name: `${employee.last_name || ""} ${employee.first_name || ""}`.trim() || employee.email, - })); + return allEmployees; }, [allEmployees]); const headCandidates = useMemo(() => {