"use client" import { getProjectList, getProjectReviewers, regenerateProjectEmbedding, } from "@/components/label/api/project" import { Project, ReviewUsers } from "@/components/label/api/project/typing" import { useAllUserStore, usePermissionStore, } from "@/components/label/store/auth" import { CREATED_CODE, STATUS_CODE } from "@/components/label/utils/constants" import { ActionIcon, Badge, Button, Collapse, CopyButton, Flex, Group, MultiSelect, Paper, SimpleGrid, Stack, Text, TextInput, Tooltip, } from "@mantine/core" import { notifications } from "@mantine/notifications" import { IconCheck, IconChevronDown, IconChevronUp, IconCopy, IconPlus, IconRefresh, IconSearch, } from "@tabler/icons-react" import dayjs from "dayjs" import { DataTable, DataTableColumn } from "mantine-datatable" import { useCallback, useEffect, useMemo, useState } from "react" import NewProjectModal from "./components/NewProjectModal" import ReviewModal from "./components/ReviewModal" function statusBadgeColor(status?: number | null) { if (!status) return "gray" if ([191, 199, 700].includes(status)) return "red" if ([580].includes(status)) return "yellow" if ([600].includes(status)) return "green" if ([201, 202, 203].includes(status)) return "cyan" return "blue" } function toTrimmed(value: string) { return value.trim() } function createInitialFilters() { return { project_name: "", project_type: "", admin_users: [] as string[], create_at_start: dayjs().subtract(6, "day").format("YYYY-MM-DD"), create_at_end: dayjs().format("YYYY-MM-DD"), } } export default function ProjectAuditPage() { const { userOpts } = useAllUserStore() const permission = usePermissionStore((s) => s.detailInfo?.permissions) const isShow = useCallback( (type: string) => { const v = permission?.project?.[type]?.[0] return typeof v === "boolean" ? v : true }, [permission] ) const [reviewers, setReviewers] = useState(null) const [loading, setLoading] = useState(false) const [records, setRecords] = useState([]) const [total, setTotal] = useState(0) const [queryTrigger, setQueryTrigger] = useState(0) const [pageNumber, setPageNumber] = useState(1) const [pageSize, setPageSize] = useState(15) const [searchExpanded, setSearchExpanded] = useState(false) const [filters, setFilters] = useState(() => createInitialFilters()) const [appliedFilters, setAppliedFilters] = useState(() => createInitialFilters() ) const adminOptions = useMemo(() => { return userOpts.map((u) => ({ label: u.label, value: String(u.value), })) }, [userOpts]) const queryParams = useMemo(() => { const obj: Project.ListRequest & any = { page_number: pageNumber, page_size: pageSize, status: CREATED_CODE, project_name: appliedFilters.project_name ? toTrimmed(appliedFilters.project_name) : undefined, project_type: appliedFilters.project_type ? toTrimmed(appliedFilters.project_type) : undefined, admin_user: appliedFilters.admin_users.length ? appliedFilters.admin_users.map(String).join(",") : undefined, start_date: appliedFilters.create_at_start || undefined, end_date: appliedFilters.create_at_end || undefined, } return obj }, [appliedFilters, pageNumber, pageSize]) const load = useCallback(async () => { if (!queryTrigger) return setLoading(true) try { const res = await getProjectList(queryParams) setRecords(res?.project_list ?? []) setTotal(res?.total_items ?? 0) } catch (e) { setRecords([]) setTotal(0) notifications.show({ color: "red", title: "加载失败", message: e instanceof Error ? e.message : "请求失败", }) } finally { setLoading(false) } }, [queryParams, queryTrigger]) useEffect(() => { load() }, [load]) useEffect(() => { queueMicrotask(() => { setQueryTrigger((v) => v + 1) }) }, []) useEffect(() => { const run = async () => { try { const res = await getProjectReviewers() setReviewers(res) } catch { setReviewers(null) } } run() }, []) const [createOpen, setCreateOpen] = useState(false) const [reviewOpen, setReviewOpen] = useState(false) const [selected, setSelected] = useState(null) const reviewerText = useCallback( (status?: number) => { if (!reviewers || !status) return "-" if (status === 1) return reviewers.reviewer1.join("、") if (status === 101) return reviewers.reviewer2.join("、") if (status === 102) return reviewers.reviewer3.join("、") return "-" }, [reviewers] ) const columns = useMemo(() => { const cols: DataTableColumn[] = [ { accessor: "id", title: "项目ID", width: 90 }, { accessor: "name", title: "项目名称", width: 220, render: (record) => ( {record.name} ), }, { accessor: "project_type", title: "所属业务", width: 180, render: (record) => record.project_type ?? "-", }, { accessor: "label_schema_name", title: "标注方案", width: 200, render: (record) => record.label_schema_name ? ( {record.label_schema_name} ) : ( "-" ), }, { accessor: "status", title: "项目状态", width: 130, render: (record) => ( {STATUS_CODE.get(record.status) ?? record.status} ), }, { accessor: "create_user", title: "项目负责人", width: 140, render: (record: any) => record.create_user ?? record.owner ?? "-", }, { accessor: "admin_user", title: "标注管理员", width: 180, render: (record) => { const ids = Array.isArray((record as any).admin_user) ? ((record as any).admin_user as any[]) : [] const names = ids .map((uid) => userOpts.find((o) => o.value === uid)?.label) .filter(Boolean) return names.length ? ( {names.join(",")} ) : ( "-" ) }, }, { accessor: "data_size", title: "总数据量", width: 120, render: (record: any) => record.data_size ?? "-", }, { accessor: "data_source", title: "数据源", width: 260, render: (record: any) => { const value = record.data_source if (!value) return "-" return ( {value} {({ copied, copy }) => ( {copied ? : } )} ) }, }, { accessor: "remarks", title: "备注", width: 180, render: (record: any) => record.remarks ?? record.remark ?? "-", }, { accessor: "create_at", title: "创建时间", width: 160, render: (record: any) => record.create_at ?? "-", }, { accessor: "latest_review_user", title: "上一节点审核人", width: 140, render: (record: any) => record.latest_review_user ?? "-", }, { accessor: "current_reviewer", title: "当前节点审核人", width: 160, render: (record) => reviewerText((record as any).status), }, { accessor: "operation", title: "操作", width: 80, textAlign: "center", render: (record: any) => { return ( {isShow("review") && record.status < 180 ? ( ) : null} {record.status === 191 ? ( ) : null} ) }, }, ] return cols }, [isShow, load, reviewerText, userOpts]) return ( 项目审核 setFilters((s) => ({ ...s, project_name: e.currentTarget.value, })) } /> setFilters((s) => ({ ...s, project_type: e.currentTarget.value, })) } /> setFilters((s) => ({ ...s, admin_users: v }))} searchable clearable /> setFilters((s) => ({ ...s, create_at_start: e.target.value, })) } /> { setFilters((s) => ({ ...s, create_at_end: e.target.value, })) }} /> {isShow("add") ? ( ) : null} withTableBorder withRowBorders pinFirstColumn pinLastColumn fetching={loading} records={records} columns={columns} totalRecords={total} recordsPerPage={pageSize} page={pageNumber} onPageChange={(p) => setPageNumber(p)} onRecordsPerPageChange={(s) => { setPageSize(s) setPageNumber(1) }} recordsPerPageOptions={[15, 45, 99]} noRecordsText="暂无数据" recordsPerPageLabel="当前页数" width="100%" style={{ width: "100%" }} scrollAreaProps={{ type: "auto" }} /> { setCreateOpen(false) if (refresh) load() }} /> { setReviewOpen(false) setSelected(null) if (refresh) load() }} /> ) }