From e159270fddfeeb06655d145cc1c51f1863266141 Mon Sep 17 00:00:00 2001 From: shay7sev Date: Fri, 6 Feb 2026 13:52:32 +0800 Subject: [PATCH] feat(management/project): management/project type --- .../detail/components/AdminUserModal.tsx | 10 +- app/management/person/collection/page.tsx | 626 +-------- app/management/project/all/page.tsx | 7 + app/management/project/dynamic/page.tsx | 7 + .../project}/components/ProjectIcon.tsx | 0 components/project/index.tsx | 1127 +++++++++++++++++ 6 files changed, 1150 insertions(+), 627 deletions(-) create mode 100644 app/management/project/all/page.tsx create mode 100644 app/management/project/dynamic/page.tsx rename {app/management/person/collection => components/project}/components/ProjectIcon.tsx (100%) create mode 100644 components/project/index.tsx diff --git a/app/management/person/collection/detail/components/AdminUserModal.tsx b/app/management/person/collection/detail/components/AdminUserModal.tsx index 5b55997..df13c2b 100644 --- a/app/management/person/collection/detail/components/AdminUserModal.tsx +++ b/app/management/person/collection/detail/components/AdminUserModal.tsx @@ -1,8 +1,8 @@ "use client" import { Button, Group, Modal, MultiSelect, Stack } from "@mantine/core" -import { useEffect } from "react" import { useForm } from "@mantine/form" +import { useEffect } from "react" export default function AdminUserModal(props: { opened: boolean @@ -23,7 +23,12 @@ export default function AdminUserModal(props: { }, [opened, value, form]) return ( - onClose()} title="编辑项目管理员" centered size={560}> + onClose()} + title="编辑项目管理员" + centered + size={560}>
{ const next = values.users @@ -51,4 +56,3 @@ export default function AdminUserModal(props: { ) } - diff --git a/app/management/person/collection/page.tsx b/app/management/person/collection/page.tsx index ddf1ccf..9e6d96f 100644 --- a/app/management/person/collection/page.tsx +++ b/app/management/person/collection/page.tsx @@ -1,629 +1,7 @@ "use client" -import { - getProjectList, - getProjectTypeList, - projectCollect, -} from "@/components/label/api/project" -import { Project } from "@/components/label/api/project/typing" -import { - initAllParams, - initCollectParams, - initDynamicParams, - useAllUserStore, - useProjectStore, -} from "@/components/label/store/auth" -import { - projectLabelTypeOpts, - projectStatusOpts, - STATUS_CODE, -} from "@/components/label/utils/constants" -import { - ActionIcon, - Badge, - Button, - Collapse, - Flex, - Group, - Loader, - Pagination, - Paper, - Select, - SimpleGrid, - Stack, - Text, - TextInput, - UnstyledButton, -} from "@mantine/core" -import { notifications } from "@mantine/notifications" -import { - IconCalendar, - IconChevronDown, - IconChevronUp, - IconRefresh, - IconSearch, - IconStar, - IconStarFilled, -} from "@tabler/icons-react" -import dayjs from "dayjs" -import { User2 } from "lucide-react" -import { useRouter, useSearchParams } from "next/navigation" -import { useCallback, useEffect, useMemo, useState } from "react" -import { CardCoverIcon } from "./components/ProjectIcon" - -type PageType = "all" | "collect" | "dynamic" - -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 parseStatusValue(value?: string) { - if (!value) return undefined - if (value.includes(",")) { - return value - .split(",") - .map((v) => Number(v)) - .filter((n) => Number.isFinite(n)) - } - const n = Number(value) - return Number.isFinite(n) ? [n] : undefined -} +import ProjectInfoPage from "@/components/project" export default function CollectionPage() { - const router = useRouter() - const urlParams = useSearchParams() - const type = (urlParams.get("type") as PageType | null) ?? "collect" - - const { userOpts } = useAllUserStore() - const { - allParams, - allTotal, - collectParams, - collectTotal, - dynamicParams, - dynamicTotal, - resetParams, - setSearchParams, - setTotal, - }: any = useProjectStore() - - const storeParams = useMemo(() => { - return type === "collect" - ? collectParams - : type === "dynamic" - ? dynamicParams - : allParams - }, [allParams, collectParams, dynamicParams, type]) - - const total = useMemo(() => { - return type === "collect" - ? collectTotal - : type === "dynamic" - ? dynamicTotal - : allTotal - }, [allTotal, collectTotal, dynamicTotal, type]) - - const [typeOpts, setTypeOpts] = useState< - Array<{ label: string; value: string }> - >([]) - const [loading, setLoading] = useState(false) - const [records, setRecords] = useState([]) - - const [searchExpanded, setSearchExpanded] = useState(false) - const [filters, setFilters] = useState(() => { - return { - project_name: "", - project_type: "", - status: "", - owner: "", - label_type: "", - admin_user: "", - start_date: dayjs().subtract(6, "day").format("YYYY-MM-DD"), - end_date: dayjs().format("YYYY-MM-DD"), - } - }) - - const [appliedFilters, setAppliedFilters] = useState(filters) - const [queryTrigger, setQueryTrigger] = useState(0) - - useEffect(() => { - const initFormOpts = async () => { - const res = await getProjectTypeList() - setTypeOpts((res ?? []).map((name) => ({ label: name, value: name }))) - } - initFormOpts() - }, []) - - const queryParams = useMemo(() => { - const base = - type === "collect" - ? { ...initCollectParams } - : type === "dynamic" - ? { ...initDynamicParams } - : { ...initAllParams } - - const params: Project.ListRequest & any = { - ...base, - ...storeParams, - page_number: storeParams.page_number ?? 1, - page_size: storeParams.page_size ?? 15, - project_name: appliedFilters.project_name || undefined, - project_type: appliedFilters.project_type || undefined, - owner: appliedFilters.owner || undefined, - admin_user: appliedFilters.admin_user || undefined, - start_date: appliedFilters.start_date || undefined, - end_date: appliedFilters.end_date || undefined, - label_type: appliedFilters.label_type - ? Number(appliedFilters.label_type) - : undefined, - status: parseStatusValue(appliedFilters.status), - } - - return params - }, [appliedFilters, storeParams, type]) - - const load = useCallback(async () => { - if (!queryTrigger) return - setLoading(true) - try { - const res = await getProjectList(queryParams) - setRecords(res?.project_list ?? []) - setTotal(res?.total_items ?? 0, type) - } catch (e) { - setRecords([]) - setTotal(0, type) - notifications.show({ - color: "red", - title: "加载失败", - message: e instanceof Error ? e.message : "请求失败", - }) - } finally { - setLoading(false) - } - }, [queryParams, queryTrigger, setTotal, type]) - - useEffect(() => { - load() - }, [load]) - - useEffect(() => { - queueMicrotask(() => { - setQueryTrigger((v) => v + 1) - }) - }, []) - - const currentPage = storeParams.page_number ?? 1 - const pageSize = storeParams.page_size ?? 15 - - const ProjectCard = (record: Project.DataProps) => { - return ( - - - - - - - { - const url = type - ? `/management/person/collection/detail?id=${record.id}&type=${type}` - : `/management/person/collection/detail?id=${record.id}` - router.push(url) - }}> - - {record.name} - - - - {STATUS_CODE.get(record.status) ?? record.status} - - - - - {record.owner ?? "-"} - - - - - {record.create_at ? record.create_at.split(" ")[0] : "-"} - - - - - { - try { - await projectCollect({ - project_id: record.id, - is_collect: !record.is_collect, - }) - notifications.show({ - color: "green", - title: "操作成功", - message: record.is_collect ? "已取消收藏" : "已收藏", - }) - setQueryTrigger((v) => v + 1) - } catch (e) { - notifications.show({ - color: "red", - title: "操作失败", - message: e instanceof Error ? e.message : "请求失败", - }) - } - }}> - {record.is_collect ? ( - - ) : ( - - )} - - - - - - 备注:{record.remarks ? record.remarks : "暂无备注"} - - - - ) - } - - const PaginationBar = (props: { - currentPage: number - pageSize: number - total: number - onChangePage: (page: number) => void - onChangePageSize: (size: number) => void - }) => { - const pageCount = Math.max( - 1, - Math.ceil((props.total ?? 0) / props.pageSize) - ) - return ( -
- - setFilters((s) => ({ ...s, project_type: v ?? "" })) - } - /> - ({ - label: o.label, - value: String(o.value), - }))} - value={filters.label_type || null} - onChange={(v) => - setFilters((s) => ({ ...s, label_type: v ?? "" })) - } - /> - ({ + label: `${n}/页`, + value: String(n), + }))} + onChange={(v) => { + const next = Number(v) + if (!Number.isFinite(next)) return + props.onChangePageSize(next) + }} + allowDeselect={false} + style={{ width: 92 }} + /> + +
+ ) + } + + return ( + + + + {/* { + const next = v as PageType + router.replace(`/management/person/collection?type=${next}`) + }} + /> */} + + + {type === "collect" + ? "我的收藏" + : type === "dynamic" + ? "动态项目" + : "全部项目"} + + + + + + + + + + + + + + setFilters((s) => ({ + ...s, + project_name: e.currentTarget.value, + })) + } + /> + ({ + label: o.label, + value: o.label, + }))} + value={filters.status} + onChange={(v) => setFilters((s) => ({ ...s, status: v ?? "" }))} + /> + + setFilters((s) => ({ ...s, owner: e.currentTarget.value })) + } + /> + ({ + label: o.label, + value: String(o.value), + }))} + value={filters.admin_user || null} + onChange={(v) => + setFilters((s) => ({ ...s, admin_user: v ?? "" })) + } + /> + + setFilters((s) => ({ + ...s, + start_date: e.currentTarget.value, + })) + } + /> + + setFilters((s) => ({ ...s, end_date: e.currentTarget.value })) + } + /> + + + + + + +
+ {loading ? ( +
+ +
+ ) : records.length ? ( +
+ {records.map(ProjectCard)} +
+ ) : ( +
+ + 暂无数据 + +
+ )} +
+ +
+ { + setSearchParams({ ...storeParams, page_number: p }, type) + setQueryTrigger((v) => v + 1) + }} + onChangePageSize={(s) => { + setSearchParams( + { ...storeParams, page_number: 1, page_size: s }, + type + ) + setQueryTrigger((v) => v + 1) + }} + /> +
+
+ + setInfoModalRecord(null)} + title="项目信息" + centered + size={850}> + {(() => { + const record = infoModalRecord + const adminUsers = + record?.admin_user && Array.isArray(record.admin_user) + ? record.admin_user + .map((uid) => userOpts.find((o) => o.value === uid)?.label) + .filter(Boolean) + : [] + + const labelSchemaText = record?.label_schema_name + ? `${record.label_schema_name}${ + record.label_schema_version + ? ` - ${record.label_schema_version}` + : "" + }` + : "-" + + const progressText = `${formatPercent(record?.labeled_scale)} / ${formatPercent( + record?.reviewed_scale + )}` + + const processText = record ? getProcessText(record) : "-" + + const Row = (props: { title: ReactNode; children: ReactNode }) => ( +
+
{props.title}
+
+ {props.children} +
+
+ ) + + const EllipsisText = (props: { + value?: string | null + copyable?: boolean + }) => { + const value = props.value + if (!value) return - + const textNode = ( + + {value} + + ) + return props.copyable ? ( + + +
{textNode}
+
+ + {({ copied, copy }) => ( + + {copied ? ( + + ) : ( + + )} + + )} + +
+ ) : ( + +
{textNode}
+
+ ) + } + + return ( +
+ + + {record?.id ?? "-"} + + + + + + + + + + + + {record?.status ? ( + + {STATUS_CODE.get(record.status) ?? record.status} + + ) : ( + - + )} + + + {progressText} + + + {record?.data_size ?? "-"} + + + {record?.owner ?? "-"} + + + + 工序 + + + + + + }> + + + + + + + + + + + + + {record?.create_at ?? "-"} + + + {record?.update_at ?? "-"} + + +
+ ) + })()} +
+ + setEditModalRecord(null)} + title="编辑项目" + centered + size={480}> + { + const record = editModalRecord + if (!record) return + try { + await projectEditById( + { + project_name: values.project_name, + owner: values.owner || undefined, + remarks: values.remarks || undefined, + }, + record.id + ) + notifications.show({ + color: "green", + title: "编辑成功", + message: "已保存", + }) + setEditModalRecord(null) + setQueryTrigger((v) => v + 1) + } catch (e) { + notifications.show({ + color: "red", + title: "编辑失败", + message: e instanceof Error ? e.message : "请求失败", + }) + } + })}> + + + +