'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
})}
)}
);
}