import { memo, type ChangeEvent, type KeyboardEvent, type MouseEvent, type PointerEvent, useCallback, useEffect, useMemo, useState, } from 'react' import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import * as Popover from '@radix-ui/react-popover' import { Building2, ChevronDown, Search, ShieldCheck, X } from 'lucide-react' import { useQuery } from '@apollo/client/react' import { GET_ORGANISATION_QUERY, GET_ORGANISATIONS_QUERY, } from '../../../../entities/employee/api/employee.graphql' import type { EmployeeRole, GetOrganisationData, GetOrganisationVariables, GetOrganisationsData, GetOrganisationsVariables, } from '../../../../entities/employee/model/types' import './EmployeesToolbar.scss' export type EmployeesSortOption = { value: string label: string sortField: 'ID' | 'Name' | 'Date' sortDirection: 'ASC' | 'DESC' } export const usersSortOptions: EmployeesSortOption[] = [ { value: 'date-desc', label: 'Сначала новые', sortField: 'Date', sortDirection: 'DESC', }, { value: 'id-asc', label: 'ID по возрастанию', sortField: 'ID', sortDirection: 'ASC', }, { value: 'id-desc', label: 'ID по убыванию', sortField: 'ID', sortDirection: 'DESC', }, { value: 'name-asc', label: 'ФИО А–Я', sortField: 'Name', sortDirection: 'ASC', }, { value: 'name-desc', label: 'ФИО Я–А', sortField: 'Name', sortDirection: 'DESC', }, { value: 'date-asc', label: 'Сначала старые', sortField: 'Date', sortDirection: 'ASC', }, ] export const organisationsSortOptions: EmployeesSortOption[] = [ { value: 'date-desc', label: 'Сначала новые', sortField: 'Date', sortDirection: 'DESC', }, { value: 'id-asc', label: 'ID по возрастанию', sortField: 'ID', sortDirection: 'ASC', }, { value: 'id-desc', label: 'ID по убыванию', sortField: 'ID', sortDirection: 'DESC', }, { value: 'name-asc', label: 'Название А–Я', sortField: 'Name', sortDirection: 'ASC', }, { value: 'name-desc', label: 'Название Я–А', sortField: 'Name', sortDirection: 'DESC', }, { value: 'date-asc', label: 'Сначала старые', sortField: 'Date', sortDirection: 'ASC', }, ] export type EmployeesRoleFilterOption = { value: 'Admin' | 'User' label: string } const roleFilterOptions: EmployeesRoleFilterOption[] = [ { value: 'Admin', label: 'Администратор', }, { value: 'User', label: 'Пользователь', }, ] type EmployeesToolbarProps = { addButtonText: string onAdd: () => void searchValue?: string onSearchChange?: (value: string) => void sortOptions: EmployeesSortOption[] selectedSort: EmployeesSortOption onSortChange: (option: EmployeesSortOption) => void showSort?: boolean roleFilter?: EmployeeRole | null onRoleFilterChange?: (role: EmployeeRole | null) => void organisationFilterId?: string | null onOrganisationFilterChange?: ( organisation: { id: string; name: string } | null, ) => void showUserFilters?: boolean } export const EmployeesToolbar = memo(function EmployeesToolbar({ addButtonText, onAdd, searchValue = '', onSearchChange, sortOptions, selectedSort, onSortChange, showSort = true, roleFilter = null, onRoleFilterChange, organisationFilterId = null, onOrganisationFilterChange, showUserFilters = false, }: EmployeesToolbarProps) { const [isOrganisationOpen, setIsOrganisationOpen] = useState(false) const [organisationSearch, setOrganisationSearch] = useState('') const [debouncedOrganisationSearch, setDebouncedOrganisationSearch] = useState('') useEffect(() => { const timeoutId = window.setTimeout(() => { setDebouncedOrganisationSearch(organisationSearch.trim()) }, 300) return () => { window.clearTimeout(timeoutId) } }, [organisationSearch]) const selectedRoleOption = useMemo( () => roleFilterOptions.find((option) => option.value === roleFilter) ?? null, [roleFilter], ) const selectedOrganisationVariables = useMemo( () => ({ id: organisationFilterId ?? '', }), [organisationFilterId], ) const organisationsVariables = useMemo( () => ({ page: 0, query: debouncedOrganisationSearch || undefined, sortDirection: 'ASC', sortField: 'Name', }), [debouncedOrganisationSearch], ) const { data: selectedOrganisationData, } = useQuery( GET_ORGANISATION_QUERY, { variables: selectedOrganisationVariables, skip: !showUserFilters || !organisationFilterId, fetchPolicy: 'cache-first', }, ) const { data: organisationsData, previousData: previousOrganisationsData, loading: organisationsLoading, error: organisationsError, } = useQuery( GET_ORGANISATIONS_QUERY, { variables: organisationsVariables, skip: !showUserFilters || !isOrganisationOpen, fetchPolicy: 'network-only', notifyOnNetworkStatusChange: true, }, ) const organisationsResponse = organisationsData?.getOrganisations ?? previousOrganisationsData?.getOrganisations const organisations = useMemo( () => organisationsResponse?.page ?? [], [organisationsResponse?.page], ) const selectedOrganisationName = selectedOrganisationData?.getOrganisation?.name ?? (organisationFilterId ? `Организация #${organisationFilterId}` : null) const organisationOptions = useMemo(() => { const map = new Map() if (organisationFilterId && selectedOrganisationName) { map.set(organisationFilterId, { id: organisationFilterId, name: selectedOrganisationName, }) } organisations.forEach((organisation) => { map.set(String(organisation.id), { id: String(organisation.id), name: organisation.name, }) }) return Array.from(map.values()) }, [organisationFilterId, selectedOrganisationName, organisations]) const handleSearchChange = useCallback(( event: ChangeEvent, ) => { onSearchChange?.(event.target.value) }, [onSearchChange]) const handleOrganisationSearchChange = useCallback(( event: ChangeEvent, ) => { setOrganisationSearch(event.target.value) }, []) const handleResetClick = useCallback(( event: MouseEvent, ) => { event.preventDefault() event.stopPropagation() }, []) const handleRoleResetPointerDown = useCallback(( event: PointerEvent, ) => { event.preventDefault() event.stopPropagation() onRoleFilterChange?.(null) }, [onRoleFilterChange]) const handleOrganisationResetPointerDown = useCallback(( event: PointerEvent, ) => { event.preventDefault() event.stopPropagation() onOrganisationFilterChange?.(null) }, [onOrganisationFilterChange]) const handleRoleResetKeyDown = useCallback(( event: KeyboardEvent, ) => { if (event.key !== 'Enter' && event.key !== ' ') return event.preventDefault() event.stopPropagation() onRoleFilterChange?.(null) }, [onRoleFilterChange]) const handleOrganisationResetKeyDown = useCallback(( event: KeyboardEvent, ) => { if (event.key !== 'Enter' && event.key !== ' ') return event.preventDefault() event.stopPropagation() onOrganisationFilterChange?.(null) }, [onOrganisationFilterChange]) const handleOrganisationSelect = useCallback(( organisation: { id: string; name: string }, ) => { onOrganisationFilterChange?.(organisation) setIsOrganisationOpen(false) setOrganisationSearch('') setDebouncedOrganisationSearch('') }, [onOrganisationFilterChange]) return (
{showUserFilters && ( <> {roleFilterOptions.map((option) => ( onRoleFilterChange?.(option.value)} > {option.label} {roleFilter === option.value && ( )} ))}
{organisationsLoading && organisationOptions.length === 0 && (
Загрузка организаций...
)} {organisationsError && (
Не удалось загрузить организации
)} {!organisationsLoading && !organisationsError && organisationOptions.length === 0 && (
Организации не найдены
)} {organisationOptions.map((organisation) => { const isSelected = organisation.id === organisationFilterId return ( ) })}
)} {showSort && ( {sortOptions.map((option) => ( onSortChange?.(option)} > {option.label} {selectedSort.value === option.value && ( )} ))} )}
) })