From 4311cd67ebbc1662e74e87de1c72cce9e3f3263b Mon Sep 17 00:00:00 2001 From: zhangheng Date: Wed, 22 Apr 2026 14:06:20 +0800 Subject: [PATCH 1/5] fix(table): fix table display --- .../components/PersonalProjectTable.tsx | 14 +++++++++++++- .../dashboard/components/PersonalTaskTable.tsx | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/person/dashboard/components/PersonalProjectTable.tsx b/app/person/dashboard/components/PersonalProjectTable.tsx index 213aa6e..09dca2b 100644 --- a/app/person/dashboard/components/PersonalProjectTable.tsx +++ b/app/person/dashboard/components/PersonalProjectTable.tsx @@ -103,7 +103,19 @@ export default function PersonalProjectTable() { title: "项目名称", width: 160, render: (record: Project.PersonProjectDashboardResponseItem) => { - return record.project_name ?? "-" + return ( + + {record.project_name ?? "-"} + + ) }, }, { diff --git a/app/person/dashboard/components/PersonalTaskTable.tsx b/app/person/dashboard/components/PersonalTaskTable.tsx index b883083..b94fab0 100644 --- a/app/person/dashboard/components/PersonalTaskTable.tsx +++ b/app/person/dashboard/components/PersonalTaskTable.tsx @@ -78,9 +78,21 @@ export default function PersonalTaskTable() { { accessor: "project_name", title: "项目名称", - width: 150, + width: 160, render: (record: Task.SimpleTaskItem) => { - return {record.project_name} + return ( + + {record.project_name ?? "-"} + + ) }, }, { From 81c9f1d16534dc8710ded7f26f3b953803554608 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Wed, 22 Apr 2026 16:18:26 +0800 Subject: [PATCH 2/5] fix(header): fix userIcon --- components/layout/AppLayout.tsx | 112 ++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/components/layout/AppLayout.tsx b/components/layout/AppLayout.tsx index a2b80f4..9ec9b02 100644 --- a/components/layout/AppLayout.tsx +++ b/components/layout/AppLayout.tsx @@ -17,8 +17,10 @@ import { useComputedColorScheme, useMantineColorScheme, } from "@mantine/core" +import Avvvatars from "avvvatars-react" + import { useDisclosure, useMediaQuery } from "@mantine/hooks" -import { IconLogout, IconMoon, IconSun, IconUser } from "@tabler/icons-react" +import { IconLogout, IconMoon, IconSun } from "@tabler/icons-react" import cx from "clsx" import { Suspense, useEffect, useMemo, useState } from "react" import actionToggleClasses from "./ActionToggle.module.css" @@ -33,6 +35,103 @@ import { ClientIcon } from "./components/ClientIcon" import { LinksGroup } from "./components/NavbarLinks" import { useAppLayoutStore } from "./store" +const COMPOUND_SURNAMES = new Set([ + "欧阳", + "太史", + "端木", + "上官", + "司马", + "东方", + "独孤", + "南宫", + "万俟", + "闻人", + "夏侯", + "诸葛", + "尉迟", + "公羊", + "赫连", + "澹台", + "皇甫", + "宗政", + "濮阳", + "公冶", + "太叔", + "申屠", + "公孙", + "慕容", + "仲孙", + "钟离", + "长孙", + "宇文", + "司徒", + "鲜于", + "司空", + "闾丘", + "子车", + "亓官", + "司寇", + "巫马", + "公西", + "颛孙", + "壤驷", + "公良", + "漆雕", + "乐正", + "宰父", + "谷梁", + "拓跋", + "轩辕", + "令狐", + "段干", + "百里", + "呼延", + "东郭", + "南门", + "羊舌", + "微生", + "梁丘", + "左丘", + "东门", + "西门", + "南荣", + "第五", +]) + +function isChineseName(value: string) { + return /[\u4e00-\u9fff]/.test(value) +} + +function getAvatarDisplayValue(rawValue?: string | null) { + const value = rawValue?.trim() ?? "" + + if (!value) return "-" + + if (isChineseName(value)) { + const normalized = value.replace(/[\s·•・・]/g, "") + if (normalized.length <= 1) return normalized + + const surnameLength = + normalized.length >= 3 && COMPOUND_SURNAMES.has(normalized.slice(0, 2)) + ? 2 + : 1 + const givenName = normalized.slice(surnameLength) + + if (!givenName) return normalized.slice(-1) + if (givenName.length <= 2) return givenName + return givenName.slice(-2) + } + + const tokens = value.split(/[\s-_]+/).filter(Boolean) + if (tokens.length >= 2) { + return `${tokens[0][0] ?? ""}${tokens[tokens.length - 1][0] ?? ""}` + .trim() + .toUpperCase() + } + + return value.slice(0, 2).toUpperCase() +} + export default function AppLayout({ menu, children, @@ -144,6 +243,10 @@ export default function AppLayout({ } const matches = useMediaQuery("(min-width: 48em)") const userName = usePermissionStore((s) => s.user_name) + const avatarDisplayValue = useMemo( + () => getAvatarDisplayValue(userName), + [userName] + ) return isClient ? ( withoutLayout ? ( @@ -199,10 +302,9 @@ export default function AppLayout({ - diff --git a/package.json b/package.json index d97f454..4f912fa 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@tiptap/react": "^3.15.3", "@tiptap/starter-kit": "^3.15.3", "@tmcw/togeojson": "^7.1.2", + "avvvatars-react": "^0.4.2", "classnames": "^2.5.1", "clsx": "^2.1.1", "dayjs": "^1.11.18", From cd1bad6da9cd9722fe4dd1594d21ba1d444eef56 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Thu, 23 Apr 2026 10:35:33 +0800 Subject: [PATCH 3/5] fix(table): add daterange --- app/layout.tsx | 1 + .../components/PersonalProjectTable.tsx | 78 +++++++++++++++++-- components/label/api/project/index.ts | 7 +- components/label/api/project/typing.ts | 5 ++ package.json | 1 + 5 files changed, 83 insertions(+), 9 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 230ae6c..fb37f14 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import "@mantine/core/styles.css" import "@mantine/core/styles.layer.css" +import "@mantine/dates/styles.css" import "@mantine/notifications/styles.css" import "mantine-datatable/styles.css" import Script from "next/script" diff --git a/app/person/dashboard/components/PersonalProjectTable.tsx b/app/person/dashboard/components/PersonalProjectTable.tsx index 09dca2b..812bfa6 100644 --- a/app/person/dashboard/components/PersonalProjectTable.tsx +++ b/app/person/dashboard/components/PersonalProjectTable.tsx @@ -2,10 +2,12 @@ import { getPersonProjectDashBoard } from "@/components/label/api/project" import { Project } from "@/components/label/api/project/typing" -import { Button, Group, Text } from "@mantine/core" +import { Button, Flex, Group, Text } from "@mantine/core" +import { DatePickerInput, type DatesRangeValue } from "@mantine/dates" import { IconRefresh } from "@tabler/icons-react" import dayjs from "dayjs" +import "dayjs/locale/zh-cn" import duration from "dayjs/plugin/duration" import { DataTable, DataTableColumn } from "mantine-datatable" import { useRouter } from "next/navigation" @@ -24,6 +26,19 @@ function formatDurationSeconds(seconds?: number | null) { return "-" } +interface FilterFormRecord { + query_date: DatesRangeValue +} + +function createInitialFilters(): FilterFormRecord { + return { + query_date: [ + dayjs().subtract(1, "month").format("YYYY-MM-DD"), + dayjs().format("YYYY-MM-DD"), + ], + } +} + export default function PersonalProjectTable() { const router = useRouter() const [records, setRecords] = useState< @@ -35,6 +50,10 @@ export default function PersonalProjectTable() { const [pageSize, setPageSize] = useState(50) const [totalItems, setTotalItems] = useState(0) + const [filters, setFilters] = useState(() => createInitialFilters()) + const [queryTrigger, setQueryTrigger] = useState(0) + const [form, setForm] = useState(createInitialFilters()) + const totalPages = useMemo(() => { const totalItems = records.length return Math.max(1, Math.ceil(totalItems / pageSize)) @@ -45,22 +64,48 @@ export default function PersonalProjectTable() { return records.slice(start, start + pageSize) }, [page, pageSize, records]) + const appliedParams = useMemo(() => { + const obj: Project.ReqPersonProjectDashBoard = { + start_date: dayjs().format("YYYY-MM-DD"), + end_date: dayjs().format("YYYY-MM-DD"), + } + if (form.query_date[0] && form.query_date[1]) { + obj.start_date = dayjs(form.query_date[0]).format("YYYY-MM-DD") + obj.end_date = dayjs(form.query_date[1]).format("YYYY-MM-DD") + } + + return obj + }, [form.query_date]) + const load = useCallback(async () => { + if (!queryTrigger) return setLoading(true) try { - const res = await getPersonProjectDashBoard() + const res = await getPersonProjectDashBoard(appliedParams) setRecords(res ?? []) setTotalItems(res?.length ?? 0) setPage(1) } finally { setLoading(false) } - }, []) + }, [appliedParams, queryTrigger]) useEffect(() => { load() }, [load]) + useEffect(() => { + queueMicrotask(() => { + setQueryTrigger((v) => v + 1) + }) + }, []) + + const handleSearch = () => { + setPage(1) + setForm({ ...filters }) + setQueryTrigger((v) => v + 1) + } + useEffect(() => { if (page > totalPages) setPage(totalPages) }, [page, totalPages]) @@ -165,15 +210,34 @@ export default function PersonalProjectTable() { align="stretch" style={{ flexDirection: "column" }}> - - 参与项目 - + + + 参与项目 + + + setFilters((s) => ({ + ...s, + query_date: value, + })) + } + /> + +