"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 { SettingContentPanel, SettingDataTable, SettingFilterActions, SettingFilterPanel, SettingHeaderActions, SettingListHeader, SettingPage, } from "@/components/setting/PageSurface" import { Badge, Button, Collapse, Group, Modal, MultiSelect, Select, Stack, Text, TextInput, } from "@mantine/core" import { modals } from "@mantine/modals" import { notifications } from "@mantine/notifications" import { IconChevronDown, IconChevronUp, IconPlus, IconRefresh, IconSearch, } from "@tabler/icons-react" import { formatUserTreeOptions } from "@/components/tree-select/libs/util" import { VirtualTreeSelect } from "@/components/tree-select/tree" import { DateInput } from "@mantine/dates" import dayjs from "dayjs" import "dayjs/locale/zh-cn" import { 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" import classes from "./page.module.css" 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 } const DEFAULT_RECORDS_PER_PAGE_OPTIONS = [10, 15, 20, 50] function InlineFilterField({ label, children, }: { label: string children: React.ReactNode }) { return (
{label}
{children}
) } export default function PersonReportPage() { const treeData = useAllUserStore((s) => s.treeData) const leaderOptions = useMemo( () => formatUserTreeOptions(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 [recordsPerPageOptions, setRecordsPerPageOptions] = useState( () => [...DEFAULT_RECORDS_PER_PAGE_OPTIONS] ) const [customPageSizeInput, setCustomPageSizeInput] = useState("") const [customPageSizeModalOpened, setCustomPageSizeModalOpened] = useState(false) 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 openCustomPageSizeModal = useCallback(() => { setCustomPageSizeModalOpened(true) }, []) const closeCustomPageSizeModal = useCallback(() => { setCustomPageSizeModalOpened(false) setCustomPageSizeInput("") }, []) const handleAddCustomPageSize = useCallback(() => { const rawValue = customPageSizeInput.trim() if (!rawValue) return const size = Number(rawValue) if (!Number.isInteger(size) || size <= 0) { notifications.show({ color: "red", title: "输入无效", message: "请填写大于 0 的整数条数", }) return } if (recordsPerPageOptions.includes(size)) { notifications.show({ color: "blue", title: "已存在", message: `每页 ${size} 条已在可选项中`, }) return } setRecordsPerPageOptions((prev) => [...prev, size].sort((a, b) => a - b)) closeCustomPageSizeModal() notifications.show({ color: "green", title: "添加成功", message: `已新增每页 ${size} 条`, }) }, [closeCustomPageSizeModal, customPageSizeInput, recordsPerPageOptions]) const handleCustomPageSizeEnter = useCallback( (event: React.KeyboardEvent) => { if (event.key !== "Enter") return event.preventDefault() handleAddCustomPageSize() }, [handleAddCustomPageSize] ) const sum = (arr: number[]) => arr.reduce((accumulator, currentValue) => { const value = Number(currentValue ?? 0) return accumulator + (Number.isFinite(value) ? value : 0) }, 0) const columns = useMemo(() => { const cols: DataTableColumn[] = [ { accessor: "id", title: "ID", width: 80, // sortable: true, footer: "总计", }, { 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, footer: sum(records.map((r) => r.work_time ?? 0)).toLocaleString(), }, { accessor: "rate", title: "额定倍率", width: 90 }, { accessor: "standard_size", title: "标准量", width: 90, sortable: true, footer: sum(records.map((r) => r.standard_size ?? 0)).toLocaleString(), }, { accessor: "completed_size", title: "完成量", width: 90, sortable: true, footer: sum(records.map((r) => r.completed_size ?? 0)).toLocaleString(), }, { accessor: "residual_size", title: "余量", width: 90, sortable: true, footer: sum(records.map((r) => r.residual_size ?? 0)).toLocaleString(), }, { accessor: "residual_work_time", title: "余量工时", width: 110, sortable: true, footer: sum( records.map((r) => r.residual_work_time ?? 0) ).toLocaleString(), }, { accessor: "remarks", title: "备注", width: 200, render: (record) => ( {record.remarks ?? "-"} ), }, { accessor: "operation", title: "操作", width: 90, textAlign: "center", render: (record) => { const disabled = !!record.lock_status return (
) }, }, ] return cols }, [load, records]) // 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: dayjs(e).format("YYYY-MM-DD"), })) } /> { setFilters((s) => ({ ...s, date_end: dayjs(e).format("YYYY-MM-DD"), })) }} /> setFilters((s) => ({ ...s, project_id: v }))} />
setFilters((s) => ({ ...s, leader_uid: v }))} /> setFilters((s) => ({ ...s, actual_type: value ?? "" })) } /> setFilters((s) => ({ ...s, accounting_type: value ?? "", })) } /> setFilters((s) => ({ ...s, obj_type: value ?? "" })) } />
} />
width="100%" minHeight={0} style={{ width: "100%", flex: 1 }} pinFirstColumn pinLastColumn scrollAreaProps={{ type: "auto" }} fetching={loading} records={records} columns={columns} totalRecords={totalItems} recordsPerPage={pageSize} page={page} paginationText={({ from, to, totalRecords }) => `显示 ${from}-${to} 条,共 ${totalRecords} 条记录` } onPageChange={setPage} onRecordsPerPageChange={(v) => { setPageSize(v) setPage(1) }} recordsPerPageOptions={recordsPerPageOptions} sortStatus={sortStatus} onSortStatusChange={(s) => { setSortStatus(s) setPage(1) }} renderPagination={({ Controls }) => ( )} /> {/* 当前页总计 工时:{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() }} /> setCustomPageSizeInput(event.currentTarget.value) } onKeyDown={handleCustomPageSizeEnter} />
) }