'use client'; import * as React from 'react'; import { closestCenter, DndContext, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors, type DragEndEvent, type UniqueIdentifier, } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconDotsVertical, IconLayoutColumns, IconPlus, IconStarFilled, IconBuilding, IconId, IconMars, IconVenus, IconEye, IconTrash, } from '@tabler/icons-react'; import { ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getSortedRowModel, Row, SortingState, useReactTable, VisibilityState, } from '@tanstack/react-table'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import { Badge } from '@nice/ui/components/badge'; import { Button } from '@nice/ui/components/button'; import { Checkbox } from '@nice/ui/components/checkbox'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@nice/ui/components/dropdown-menu'; import { Label } from '@nice/ui/components/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@nice/ui/components/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@nice/ui/components/table'; import { Avatar, AvatarFallback, AvatarImage } from '@nice/ui/components/avatar'; import { Input } from '@nice/ui/components/input'; import { ProfileDetail, ProfileMetadata } from '@fenghuo/common/profile'; import { TaxonomySlug } from '@fenghuo/common/term'; import { MultipleDeptSelector } from '../selector/dept-select'; import { useTranslation } from '@nice/i18n' // 职务等级星星显示组件 function DutyLevelStars({ level }: { level: number | string }) { // 转换为数字并检查是否为有效的正整数 const numLevel = Number(level); if (!numLevel || numLevel <= 0) return null; const stars = Array.from({ length: 3 }, (_, index) => { const filled = index < numLevel; return filled ? ( ) : ( null ); }); return
{stars}
; } // 日期格式化辅助函数 function formatDate(date: Date | string | null | undefined): string { if (!date) return '-'; try { return dayjs(date).format('YYYY-MM-DD'); } catch { return '-'; } } // 政治面貌和党派职务显示组件 function PoliticalInfo({ metadata }: { metadata?: ProfileMetadata }) { if (!metadata?.politicalStatus && !metadata?.partyPosition) { return -; } return (
{metadata.politicalStatus && ( {metadata.politicalStatus} )} {metadata.partyPosition && (
{metadata.partyPosition}
)}
); } // 籍贯显示组件 function NativePlace({ native }: { native?: ProfileMetadata['native'] }) { if (!native) return -; const parts = [native.province, native.city, native.county].filter(Boolean); return {parts.join(' ')}; } // 学历信息显示组件 function EducationInfo({ metadata }: { metadata?: ProfileMetadata }) { if (!metadata?.education && !metadata?.educationForm && !metadata?.schoolMajor) { return -; } return (
{metadata.education && ( {metadata.education} )} {metadata.educationForm && ( ({metadata.educationForm}) )}
{metadata.schoolMajor && (
{metadata.schoolMajor}
)}
); } // 组织信息展示组件 function OrganizationInfo({ organization }: { organization?: ProfileDetail['organization'] }) { const { t } = useTranslation(); if (!organization) { return (
{t('profile.profile_table.table.no_organization')}
); } // 提取组织类型术语(作为标签展示) const organizationTypeTerms = organization.terms?.filter( (term) => term.taxonomy?.slug === TaxonomySlug.ORGANIZATION_TYPE ) || []; // 提取专业术语(作为小字展示) const professionTerms = organization.terms?.filter( (term) => term.taxonomy?.slug === TaxonomySlug.PROFESSION ) || []; return (
{ organization.parentId ? ( {organization?.parent?.name}/{organization.name} ) : ( {organization.name} ) } {/* 组织类型标签 */} {organizationTypeTerms.map((term) => ( {term.name} ))}
{/* 专业信息 */} {professionTerms.length > 0 && (
{professionTerms.map((term) => term.name).join(' · ')}
)}
); } // 可拖拽行组件 function DraggableRow({ row }: { row: Row }) { const { transform, transition, setNodeRef, isDragging } = useSortable({ id: row.original.id, }); return ( {row.getVisibleCells().map((cell) => ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ))} ); } // 分页信息接口 interface PaginationInfo { page: number; pageSize: number; totalPages: number; totalCount: number; hasNextPage: boolean; hasPreviousPage: boolean; } // 主数据表组件 export function ProfileDataTable({ data: initialData, isLoading = false, pagination, onPageChange, onPageSizeChange, onAddEmployee, onViewDetail, onDeleteEmployee, searchValue = '', onSearchChange, // 添加部门筛选相关属性 selectedOrganizationIds = [], onOrganizationChange, sortBy = 'default', onSortChange, }: { data: ProfileDetail[]; isLoading?: boolean; pagination?: PaginationInfo; onPageChange?: (page: number) => void; onPageSizeChange?: (size: number) => void; onAddEmployee?: () => void; onViewDetail?: (profileId: string) => void; onDeleteEmployee?: (profileId: string) => void; searchValue?: string; onSearchChange?: (value: string) => void; // 添加部门筛选相关类型 selectedOrganizationIds?: string[]; onOrganizationChange?: (organizationIds: string[]) => void; // 添加排序相关类型 sortBy?: string; onSortChange?: (sortBy: string) => void; }) { const [data, setData] = React.useState(() => initialData); const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({ // 默认隐藏的列 hireInfo: false, politicalInfo: false, nativePlace: false, trainAppraisal: false, educationInfo: false, otherInfo: false, birthDate: false, }); const [columnFilters, setColumnFilters] = React.useState([]); const [sorting, setSorting] = React.useState([]); // 移除本地分页状态,使用服务端分页 const sortableId = React.useId(); const sensors = useSensors(useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), useSensor(KeyboardSensor, {})); const { t } = useTranslation(); // 定义列配置 - 移到组件内部以访问回调函数 const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => (
table.toggleAllPageRowsSelected(!!value)} aria-label={t('common.select_all')} />
), cell: ({ row }) => (
row.toggleSelected(!!value)} aria-label={t('common.select_row')} />
), enableSorting: false, enableHiding: false, size: 20, }, { accessorKey: 'name', header: t('profile.profile_table.column.name'), cell: ({ row }) => { const profile = row.original; return (
{profile.avatar ? ( ) : ( {profile.name.slice(0, 1)} )}
{profile.name}
{profile.gender === 1 ? ( ) : ( )}
); }, enableHiding: false, size: 120, }, { accessorKey: 'certificates', header: t('profile.profile_table.column.id_card'), cell: ({ row }) => { const profile = row.original; return (
{profile.paperId}
{profile.paperId && (
{t('profile.profile_table.table.id_card_label')}{profile.idNum}
)}
); }, enableHiding: true, size: 140, }, { accessorKey: 'command', header: t('profile.profile_table.column.formation_command'), cell: ({ row }) => { const profile = row.original; return (
{profile.command || }
); }, enableHiding: true, size: 120, }, { accessorKey: 'dutyInfo', header: t('profile.profile_table.column.duty'), cell: ({ row }) => { const profile = row.original; return (
{profile.dutyName} {profile.dutyLevel > 0 && }
{profile.dutyCode}
); }, enableHiding: true, size: 100, }, { accessorKey: 'organization', header: t('profile.profile_table.column.organization'), cell: ({ row }) => { const profile = row.original; return ; }, enableHiding: true, size: 180, }, { accessorKey: 'identityInfo', header: t('profile.profile_table.column.identity'), cell: ({ row }) => { const profile = row.original; return (
{profile.identity && (
{profile.identity} { profile.level && ( {profile.level} ) }
)} {profile.levelDate && (
{formatDate(profile.levelDate)}
)}
); }, enableHiding: true, size: 120, }, { accessorKey: 'hireInfo', header: t('profile.profile_table.column.hire_date'), cell: ({ row }) => { const profile = row.original; return (
{formatDate(profile.hireDate)}
{profile.relativeHireDate && profile.hireDate !== profile.relativeHireDate && (
{t('profile.profile_sheet.relative_hire_time')}: {formatDate(profile.relativeHireDate)}
)}
); }, enableHiding: true, size: 120, }, // 以下列默认隐藏 { accessorKey: 'politicalInfo', header: t('profile.profile_table.column.political'), cell: ({ row }) => { const metadata = row.original.metadata as ProfileMetadata; return ; }, enableHiding: true, size: 100, }, { accessorKey: 'nativePlace', header: t('profile.profile_table.column.native'), cell: ({ row }) => { const metadata = row.original.metadata as ProfileMetadata; return ; }, enableHiding: true, size: 130, }, { accessorKey: 'educationInfo', header: t('profile.profile_table.column.education'), cell: ({ row }) => { const metadata = row.original.metadata as ProfileMetadata; return ; }, enableHiding: true, size: 100, }, { accessorKey: 'birthDate', header: t('profile.profile_table.table.birth_date'), cell: ({ row }) => { const profile = row.original; return (
{formatDate(profile.birthday)}
); }, enableHiding: true, size: 80, }, { id: 'actions', header: t('common.actions'), cell: ({ row }) => ( onViewDetail?.(row.original.id)}> {t('common.view_details')} onDeleteEmployee?.(row.original.id)}> {t('common.delete')} ), enableHiding: false, size: 50, }, ]; // 当数据更新时,同步本地状态 React.useEffect(() => { setData(initialData); }, [initialData]); const dataIds = React.useMemo(() => data?.map(({ id }) => id) || [], [data]); const table = useReactTable({ data, columns, state: { sorting, columnVisibility, rowSelection, columnFilters, }, getRowId: (row) => row.id, enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), // 禁用本地分页,使用服务端分页 manualPagination: true, // 禁用列调整大小功能,保持列宽度固定 enableColumnResizing: false, }); function handleDragEnd(event: DragEndEvent) { const { active, over } = event; if (active && over && active.id !== over.id) { setData((data) => { const oldIndex = dataIds.indexOf(active.id); const newIndex = dataIds.indexOf(over.id); return arrayMove(data, oldIndex, newIndex); }); } } return (
{/* 顶部工具栏 */}

{t('profile.profile_table.title')}

{isLoading ? t('common.loading') : t('profile.profile_table.table.records_count', { count: pagination?.totalCount || 0 })}
{/* 部门筛选器 */} {/* 搜索框 */}
onSearchChange?.(e.target.value)} className="w-56 border border-[#5D6E89] h-8" />
{/* 列显示选择器 */} {table .getAllColumns() .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide()) .map((column) => { return ( column.toggleVisibility(!!value)} onSelect={(event) => { event.preventDefault(); }} > {column.columnDef.header as string} ); })} {/* 排序方式选择器 */}
{/* 数据表 */}
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} ); })} ))} {isLoading ? ( column.getIsVisible()).length} className="h-24 text-center"> {t('common.loading')} ) : table.getRowModel().rows?.length ? ( {table.getRowModel().rows.map((row) => ( ))} ) : ( column.getIsVisible()).length} className="h-24 text-center"> {t('common.no_data')} )}
{/* 分页器 - 使用服务端分页 */} {pagination && (
{t('pagination.selected_rows', { selected: table.getFilteredSelectedRowModel().rows.length, total: pagination.totalCount })}
{t('pagination.page_info', { current: pagination.page, total: pagination.totalPages })}
)}
); }