"use client" import { deleteDailyWorkData, getSelfDailyWorkList, } from "@/components/label/api/daily" import { Daily } from "@/components/label/api/daily/typing" import { getSelfProjectList } from "@/components/label/api/project" import { useAllUserStore } from "@/components/label/store/auth" import { ActionIcon, Badge, Button, Collapse, Flex, Group, MultiSelect, Paper, SimpleGrid, Stack, Text, TextInput, } from "@mantine/core" import { modals } from "@mantine/modals" import { notifications } from "@mantine/notifications" import { IconChevronDown, IconChevronUp, IconPencil, IconPlus, IconRefresh, IconSearch, IconTrash, } from "@tabler/icons-react" import dayjs from "dayjs" import { DataTable, DataTableColumn, DataTableSortStatus, } from "mantine-datatable" import { useCallback, useEffect, useMemo, useState } from "react" import DailyReportModal from "./components/DailyReportModal" import { accountTypeOpts, actualTypeOpts, objTypeOpts, objTypeTextMap, performanceOpts, performanceTextMap, setTypeOpts, workTypeOpts, } from "./config" type LeaderTreeNode = { title?: string value?: string | number children?: LeaderTreeNode[] } function flattenLeaderTree( nodes: LeaderTreeNode[], prefixTitle: string[] = [] ): Array<{ label: string; value: string }> { const result: Array<{ label: string; value: string }> = [] nodes.forEach((node) => { const title = node.title ?? "" const nextPrefix = title ? [...prefixTitle, title] : prefixTitle const children = node.children ?? [] const hasChildren = children.length > 0 const valueRaw = node.value if (hasChildren) { result.push(...flattenLeaderTree(children, nextPrefix)) return } if (valueRaw === undefined || valueRaw === null) return if (typeof valueRaw === "string" && valueRaw.includes("-")) return result.push({ label: nextPrefix.join(" / "), value: String(valueRaw), }) }) return result } function weekDayText(day: number) { const list = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"] return list[day] ?? "-" } function toBoolean(value?: string) { if (value === "true") return true if (value === "false") return false return undefined } function toNumber(value?: string) { if (!value) return undefined const n = Number(value) return Number.isFinite(n) ? n : undefined } export default function PersonReportPage() { const treeData = useAllUserStore((s) => s.treeData) const leaderOptions = useMemo(() => { return flattenLeaderTree(treeData ?? []) }, [treeData]) const defaultFilters = useMemo(() => { return { date_start: dayjs().subtract(6, "day").format("YYYY-MM-DD"), date_end: dayjs().format("YYYY-MM-DD"), set_type: "", actual_type: "", leader_uid: [] as string[], work_type: "", accounting_type: "", project_id: [] as string[], performance_accounting: "", obj_type: "", } }, []) const [projectOptions, setProjectOptions] = useState< Array<{ label: string; value: string }> >([]) const [filters, setFilters] = useState(defaultFilters) const [appliedFilters, setAppliedFilters] = useState(defaultFilters) const [queryTrigger, setQueryTrigger] = useState(0) const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(20) const [totalItems, setTotalItems] = useState(0) const [loading, setLoading] = useState(false) const [records, setRecords] = useState([]) const [summary, setSummary] = useState(null) const [sortStatus, setSortStatus] = useState({ columnAccessor: "id", direction: "desc", }) const [modalOpened, setModalOpened] = useState(false) const [editingRecord, setEditingRecord] = useState(null) const [searchExpanded, setSearchExpanded] = useState(false) const queryParams = useMemo(() => { const params: Daily.Request = { page_number: page, page_size: pageSize, flag: 0, date_start: appliedFilters.date_start || undefined, date_end: appliedFilters.date_end || undefined, set_type: appliedFilters.set_type || undefined, actual_type: appliedFilters.actual_type || undefined, leader_uid: appliedFilters.leader_uid.length ? appliedFilters.leader_uid .map((v) => Number(v)) .filter((v) => Number.isFinite(v)) : undefined, work_type: appliedFilters.work_type || undefined, accounting_type: appliedFilters.accounting_type || undefined, project_id: appliedFilters.project_id.length ? appliedFilters.project_id .map((v) => Number(v)) .filter((v) => Number.isFinite(v)) : undefined, performance_accounting: toBoolean(appliedFilters.performance_accounting), obj_type: toNumber(appliedFilters.obj_type), order_by: `${sortStatus.columnAccessor} ${sortStatus.direction}`, } Object.keys(params).forEach((k) => { const key = k as keyof Daily.Request const value = params[key] if (value === "" || value === null) delete params[key] if (Array.isArray(value) && value.length === 0) delete params[key] if (value !== 0 && value !== false && value !== "" && !value) delete params[key] }) return params }, [ appliedFilters, page, pageSize, sortStatus.columnAccessor, sortStatus.direction, ]) const load = useCallback(async () => { if (!queryTrigger) return setLoading(true) try { const res = await getSelfDailyWorkList(queryParams) setRecords(res?.daily_work_list ?? []) setTotalItems(res?.total_items ?? 0) setSummary(res?.total_summary ?? null) } catch (e) { setRecords([]) setTotalItems(0) setSummary(null) 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) }) }, []) const columns = useMemo(() => { const cols: DataTableColumn[] = [ { accessor: "id", title: "ID", width: 80, sortable: true }, { accessor: "date", title: "日期", width: 120, sortable: true, render: (record) => record.date ?? "-", }, { accessor: "week", title: "星期", width: 90, render: (record) => record.date ? weekDayText(dayjs(record.date).day()) : "-", }, { accessor: "set_type", title: "规定类型", width: 120, render: (record) => record.set_type ?? "-", }, { accessor: "actual_type", title: "实际类型", width: 120, render: (record) => record.actual_type ?? "-", }, { accessor: "leader_name", title: "任务组长", width: 120 }, { accessor: "work_type", title: "工作类别", width: 140 }, { accessor: "accounting_type", title: "核算方式", width: 140 }, { accessor: "project_name", title: "项目名称", width: 220, render: (record) => ( {record.project_name ?? "-"} ), }, { accessor: "performance_accounting", title: "绩效核算", width: 110, render: (record) => { if (record.performance_accounting === true) return {performanceTextMap.get(true)} if (record.performance_accounting === false) return {performanceTextMap.get(false)} return "-" }, }, { accessor: "obj_type", title: "对象类型", width: 100, render: (record) => { if (record.obj_type === 0 || record.obj_type === 1) return objTypeTextMap.get(record.obj_type) ?? "-" return "-" }, }, { accessor: "start_time", title: "开始时间", width: 120 }, { accessor: "end_time", title: "结束时间", width: 120 }, { accessor: "work_time", title: "工时", width: 90, sortable: true, }, { accessor: "rate", title: "额定倍率", width: 90 }, { accessor: "standard_size", title: "标准量", width: 90, sortable: true, }, { accessor: "completed_size", title: "完成量", width: 90, sortable: true, }, { accessor: "residual_size", title: "余量", width: 90, sortable: true, }, { accessor: "residual_work_time", title: "余量工时", width: 110, sortable: true, }, { accessor: "remarks", title: "备注", width: 200, render: (record) => ( {record.remarks ?? "-"} ), }, { accessor: "operation", title: "操作", width: 90, textAlign: "center", render: (record) => { const disabled = !!record.lock_status return ( { if (disabled) return setEditingRecord(record) setModalOpened(true) }}> { if (disabled || !record.id) return modals.openConfirmModal({ title: "删除日报", centered: true, children: ( 删除后不可恢复,你确定删除该日报吗? ), labels: { confirm: "删除", cancel: "取消" }, confirmProps: { color: "red" }, onConfirm: async () => { await deleteDailyWorkData(record.id as number) notifications.show({ color: "green", title: "操作成功", message: "已删除日报", }) load() }, }) }}> ) }, }, ] return cols }, [load]) const summaryText = useMemo(() => { return { total_work_time: summary?.total_work_time ?? 0, total_standard_size: summary?.total_standard_size ?? 0, total_completed_size: summary?.total_completed_size ?? 0, total_residual_size: summary?.total_residual_size ?? 0, total_residual_work_time: summary?.total_residual_work_time ?? 0, } }, [summary]) const asyncGetSelfProjectList = useCallback(async () => { const res = await getSelfProjectList() setProjectOptions( (res ?? []).map((r) => ({ label: r.label, value: String(r.value) })) ) }, []) useEffect(() => { queueMicrotask(() => asyncGetSelfProjectList()) }, [asyncGetSelfProjectList]) return ( 搜索条件 setFilters((s) => ({ ...s, date_start: e.target.value, })) } /> { setFilters((s) => ({ ...s, date_end: e.target.value })) }} /> setFilters((s) => ({ ...s, project_id: v }))} /> setFilters((s) => ({ ...s, leader_uid: v }))} /> setFilters((s) => ({ ...s, set_type: v[0] ?? "" })) } /> setFilters((s) => ({ ...s, actual_type: v[0] ?? "" })) } /> setFilters((s) => ({ ...s, work_type: v[0] ?? "" })) } /> setFilters((s) => ({ ...s, accounting_type: v[0] ?? "" })) } /> setFilters((s) => ({ ...s, performance_accounting: v[0] ?? "", })) } /> setFilters((s) => ({ ...s, obj_type: v[0] ?? "" })) } /> width="100%" style={{ width: "100%" }} withTableBorder withRowBorders pinFirstColumn pinLastColumn scrollAreaProps={{ type: "auto" }} fetching={loading} records={records} columns={columns} totalRecords={totalItems} recordsPerPage={pageSize} page={page} onPageChange={setPage} onRecordsPerPageChange={(v) => { setPageSize(v) setPage(1) }} recordsPerPageOptions={[10, 15, 20, 50]} noRecordsText="暂无数据" recordsPerPageLabel="当前页数" sortStatus={sortStatus} onSortStatusChange={(s) => { setSortStatus(s) setPage(1) }} /> 当前页总计 工时:{summaryText.total_work_time} 标准量:{summaryText.total_standard_size} 完成量:{summaryText.total_completed_size} 余量:{summaryText.total_residual_size} 余量工时:{summaryText.total_residual_work_time} { setModalOpened(false) setEditingRecord(null) if (refresh) load() }} /> ) }