feat(workload): add workload list
This commit is contained in:
33
app/person/workload/LabelWorkloadTable.tsx
Normal file
33
app/person/workload/LabelWorkloadTable.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import { getLabelStatisticsList } from "@/components/label/api/workload"
|
||||
import WorkloadMetricsTable from "./WorkloadMetricsTable"
|
||||
|
||||
export default function LabelWorkloadTable() {
|
||||
return (
|
||||
<WorkloadMetricsTable
|
||||
title="标注工时"
|
||||
sheetName="标注工时数据"
|
||||
fileName="标注工时数据.xlsx"
|
||||
fetchData={getLabelStatisticsList}
|
||||
metrics={[
|
||||
{ key: "work_time", title: "系统工时" },
|
||||
{ key: "object_size", title: "新增对象数" },
|
||||
{ key: "confirmed_prelabel_size", title: "确认预标注对象数" },
|
||||
{ key: "prelabel_object_size", title: "预标注对象数" },
|
||||
{ key: "commit_label_1", title: "提交标注任务数" },
|
||||
{ key: "commit_reviewed_1", title: "被审核任务数" },
|
||||
{ key: "reviewed_size", title: "被审核对象数" },
|
||||
{ key: "commented_size", title: "被批注对象数" },
|
||||
{ key: "rejected_label_1", title: "被驳回任务数" },
|
||||
{ key: "total_reviewed_size", title: "被审核过任务对象总数" },
|
||||
{ key: "dynamic_size", title: "新增动态对象数" },
|
||||
{ key: "static_size", title: "新增静态对象数" },
|
||||
{ key: "key_frame_size", title: "新增关键帧数" },
|
||||
{ key: "question_size", title: "新增问题数" },
|
||||
{ key: "commented_question_size", title: "被批注问题数" },
|
||||
{ key: "reviewed_question_size", title: "被审核问题数" },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
35
app/person/workload/Review1WorkloadTable.tsx
Normal file
35
app/person/workload/Review1WorkloadTable.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client"
|
||||
|
||||
import { getReview1StatisticsList } from "@/components/label/api/workload"
|
||||
import WorkloadMetricsTable from "./WorkloadMetricsTable"
|
||||
|
||||
export default function Review1WorkloadTable() {
|
||||
return (
|
||||
<WorkloadMetricsTable
|
||||
title="审核工时"
|
||||
sheetName="审核工时数据"
|
||||
fileName="审核工时数据.xlsx"
|
||||
fetchData={getReview1StatisticsList}
|
||||
metrics={[
|
||||
{ key: "work_time", title: "系统工时" },
|
||||
{ key: "object_size", title: "新增对象数" },
|
||||
{ key: "confirmed_prelabel_size", title: "确认预标注对象数" },
|
||||
{ key: "prelabel_object_size", title: "预标注对象数" },
|
||||
{ key: "review_size", title: "审核对象数" },
|
||||
{ key: "comment_size", title: "批注对象数" },
|
||||
{ key: "reviewed_size", title: "被审核对象数" },
|
||||
{ key: "commented_size", title: "被批注对象数" },
|
||||
{ key: "commit_review_1", title: "提交审核任务数" },
|
||||
{ key: "commit_reviewed_2", title: "被复审审核任务数" },
|
||||
{ key: "reject_review_1", title: "审核驳回任务数" },
|
||||
{ key: "rejected_review_1", title: "被驳回的审核任务数" },
|
||||
{ key: "total_review_size", title: "已审核任务对象总数" },
|
||||
{ key: "review_dynamic_size", title: "审核动态对象数" },
|
||||
{ key: "review_static_size", title: "审核静态对象数" },
|
||||
{ key: "total_reviewed_size", title: "已被审核过任务对象总数" },
|
||||
{ key: "review_key_frame_size", title: "审核关键帧数" },
|
||||
{ key: "review_question_size", title: "审核问题数" },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
30
app/person/workload/Review2WorkloadTable.tsx
Normal file
30
app/person/workload/Review2WorkloadTable.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import { getReview2StatisticsList } from "@/components/label/api/workload"
|
||||
import WorkloadMetricsTable from "./WorkloadMetricsTable"
|
||||
|
||||
export default function Review2WorkloadTable() {
|
||||
return (
|
||||
<WorkloadMetricsTable
|
||||
title="复审工时"
|
||||
sheetName="复审工时数据"
|
||||
fileName="复审工时数据.xlsx"
|
||||
fetchData={getReview2StatisticsList}
|
||||
metrics={[
|
||||
{ key: "work_time", title: "系统工时" },
|
||||
{ key: "object_size", title: "新增对象数" },
|
||||
{ key: "confirmed_prelabel_size", title: "确认预标注对象数" },
|
||||
{ key: "prelabel_object_size", title: "预标注对象数" },
|
||||
{ key: "review_size", title: "复审对象数" },
|
||||
{ key: "comment_size", title: "批注对象数" },
|
||||
{ key: "commit_review_2", title: "提交复审任务数" },
|
||||
{ key: "reject_review_2", title: "复审驳回任务数" },
|
||||
{ key: "total_review_size", title: "已复审任务对象总数" },
|
||||
{ key: "review_dynamic_size", title: "复审动态对象数" },
|
||||
{ key: "review_static_size", title: "复审静态对象数" },
|
||||
{ key: "review_key_frame_size", title: "复审关键帧数" },
|
||||
{ key: "review_question_size", title: "复审问题数" },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
404
app/person/workload/WorkloadMetricsTable.tsx
Normal file
404
app/person/workload/WorkloadMetricsTable.tsx
Normal file
@@ -0,0 +1,404 @@
|
||||
"use client"
|
||||
|
||||
import { TimeFilter } from "@/components/label/api/workload/typing"
|
||||
import {
|
||||
SettingContentPanel,
|
||||
SettingDataTable,
|
||||
SettingFilterActions,
|
||||
SettingFilterPanel,
|
||||
SettingHeaderActions,
|
||||
SettingInlineFilterField,
|
||||
SettingListHeader,
|
||||
settingSurfaceClassNames,
|
||||
} from "@/components/setting/PageSurface"
|
||||
import { Button, Collapse, Stack, TextInput } from "@mantine/core"
|
||||
import { DateInput } from "@mantine/dates"
|
||||
import { notifications } from "@mantine/notifications"
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconDownload,
|
||||
IconRefresh,
|
||||
IconSearch,
|
||||
} from "@tabler/icons-react"
|
||||
import dayjs from "dayjs"
|
||||
import "dayjs/locale/zh-cn"
|
||||
import { DataTableColumn, DataTableSortStatus } from "mantine-datatable"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import * as XLSX from "xlsx-js-style"
|
||||
import { DimensionType, GroupedData, MetricColumn } from "./config"
|
||||
|
||||
function emptyGroupedData(): GroupedData {
|
||||
return {
|
||||
all: [],
|
||||
order_by_group_name: [],
|
||||
order_by_project_name: [],
|
||||
order_by_project_type: [],
|
||||
order_by_user_name: [],
|
||||
}
|
||||
}
|
||||
|
||||
function createInitialFilters() {
|
||||
const currentDate = dayjs().subtract(1, "day").format("YYYY-MM-DD")
|
||||
return {
|
||||
startDate: currentDate,
|
||||
endDate: currentDate,
|
||||
dimension: "all" as DimensionType,
|
||||
projectName: "",
|
||||
projectType: "",
|
||||
userName: "",
|
||||
groupName: "",
|
||||
}
|
||||
}
|
||||
|
||||
export default function WorkloadMetricsTable(props: {
|
||||
title: string
|
||||
sheetName: string
|
||||
fileName: string
|
||||
metrics: MetricColumn[]
|
||||
fetchData: (params: TimeFilter) => Promise<GroupedData>
|
||||
}) {
|
||||
const { title, sheetName, fileName, metrics, fetchData } = props
|
||||
|
||||
const [filters, setFilters] = useState(() => createInitialFilters())
|
||||
const [appliedFilters, setAppliedFilters] = useState(() =>
|
||||
createInitialFilters()
|
||||
)
|
||||
const [data, setData] = useState<GroupedData>(emptyGroupedData())
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const [sortStatus, setSortStatus] = useState<DataTableSortStatus>({
|
||||
columnAccessor: "work_time",
|
||||
direction: "desc",
|
||||
})
|
||||
|
||||
const refresh = async (nextFilters = appliedFilters) => {
|
||||
if (!nextFilters.startDate || !nextFilters.endDate) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
title: "参数不完整",
|
||||
message: "请选择起止日期",
|
||||
})
|
||||
return
|
||||
}
|
||||
try {
|
||||
setLoading(true)
|
||||
const res = await fetchData({
|
||||
start_date: nextFilters.startDate,
|
||||
end_date: nextFilters.endDate,
|
||||
})
|
||||
setData({
|
||||
...emptyGroupedData(),
|
||||
...(res ?? {}),
|
||||
})
|
||||
} catch (e) {
|
||||
notifications.show({
|
||||
color: "red",
|
||||
title: "加载失败",
|
||||
message: e instanceof Error ? e.message : "请求失败",
|
||||
})
|
||||
setData(emptyGroupedData())
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const initialFilters = createInitialFilters()
|
||||
queueMicrotask(() => refresh(initialFilters))
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const filteredRecords = useMemo(() => {
|
||||
let records = [...(data[appliedFilters.dimension] ?? [])]
|
||||
if (appliedFilters.projectName.trim()) {
|
||||
records = records.filter((item) =>
|
||||
String(item.project_name ?? "")
|
||||
.toLowerCase()
|
||||
.includes(appliedFilters.projectName.trim().toLowerCase())
|
||||
)
|
||||
}
|
||||
if (appliedFilters.projectType.trim()) {
|
||||
records = records.filter((item) =>
|
||||
String(item.project_type ?? "")
|
||||
.toLowerCase()
|
||||
.includes(appliedFilters.projectType.trim().toLowerCase())
|
||||
)
|
||||
}
|
||||
if (appliedFilters.userName.trim()) {
|
||||
records = records.filter((item) =>
|
||||
String(item.user_name ?? "")
|
||||
.toLowerCase()
|
||||
.includes(appliedFilters.userName.trim().toLowerCase())
|
||||
)
|
||||
}
|
||||
if (appliedFilters.groupName.trim()) {
|
||||
records = records.filter((item) =>
|
||||
String(item.group_name ?? "")
|
||||
.toLowerCase()
|
||||
.includes(appliedFilters.groupName.trim().toLowerCase())
|
||||
)
|
||||
}
|
||||
return records
|
||||
}, [appliedFilters, data])
|
||||
|
||||
const sortedRecords = useMemo(() => {
|
||||
const list = [...filteredRecords]
|
||||
const accessor = sortStatus.columnAccessor
|
||||
const dir = sortStatus.direction === "asc" ? 1 : -1
|
||||
list.sort((a, b) => {
|
||||
const av = a?.[accessor as string]
|
||||
const bv = b?.[accessor as string]
|
||||
const an = Number(av)
|
||||
const bn = Number(bv)
|
||||
if (Number.isFinite(an) && Number.isFinite(bn)) {
|
||||
return (an - bn) * dir
|
||||
}
|
||||
return String(av ?? "").localeCompare(String(bv ?? "")) * dir
|
||||
})
|
||||
return list
|
||||
}, [filteredRecords, sortStatus.columnAccessor, sortStatus.direction])
|
||||
|
||||
const baseColumns = useMemo(() => {
|
||||
const allCols: Record<DimensionType, DataTableColumn<any>[]> = {
|
||||
all: [
|
||||
{ accessor: "project_name", title: "项目名称", width: 180 },
|
||||
{ accessor: "project_type", title: "项目类型", width: 280 },
|
||||
{ accessor: "user_name", title: "用户名", width: 120 },
|
||||
{ accessor: "group_name", title: "用户组", width: 160 },
|
||||
],
|
||||
order_by_group_name: [
|
||||
{ accessor: "group_name", title: "用户组", width: 180 },
|
||||
],
|
||||
order_by_project_name: [
|
||||
{ accessor: "project_name", title: "项目名称", width: 220 },
|
||||
],
|
||||
order_by_project_type: [
|
||||
{ accessor: "project_type", title: "项目类型", width: 320 },
|
||||
],
|
||||
order_by_user_name: [
|
||||
{ accessor: "user_name", title: "用户名", width: 180 },
|
||||
],
|
||||
}
|
||||
return allCols[appliedFilters.dimension]
|
||||
}, [appliedFilters.dimension])
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const totalsByKey = Object.fromEntries(
|
||||
metrics.map((metric) => {
|
||||
const sum = sortedRecords.reduce((acc, item) => {
|
||||
const value = Number(item?.[metric.key] ?? 0)
|
||||
return acc + (Number.isFinite(value) ? value : 0)
|
||||
}, 0)
|
||||
return [metric.key, sum]
|
||||
})
|
||||
) as Record<string, number>
|
||||
|
||||
const metricCols = metrics.map((metric) => {
|
||||
return {
|
||||
accessor: metric.key,
|
||||
title: metric.title,
|
||||
width: 140,
|
||||
sortable: true,
|
||||
footer: totalsByKey[metric.key] ?? 0,
|
||||
} as DataTableColumn<any>
|
||||
})
|
||||
return [
|
||||
{
|
||||
accessor: "index",
|
||||
title: "序号",
|
||||
width: 80,
|
||||
footer: "总计",
|
||||
render: (_record: any, index: number) => index + 1,
|
||||
} as DataTableColumn<any>,
|
||||
...baseColumns,
|
||||
...metricCols,
|
||||
]
|
||||
}, [baseColumns, metrics, sortedRecords])
|
||||
|
||||
const handleExport = () => {
|
||||
if (!sortedRecords.length) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
title: "暂无数据",
|
||||
message: "当前筛选条件下无可导出数据",
|
||||
})
|
||||
return
|
||||
}
|
||||
const baseHeaders = baseColumns.map((c) => String(c.title ?? c.accessor))
|
||||
const metricHeaders = metrics.map((m) => m.title)
|
||||
const header = ["序号", ...baseHeaders, ...metricHeaders]
|
||||
|
||||
const baseAccessors = baseColumns.map((c) => String(c.accessor))
|
||||
const rows = sortedRecords.map((item, index) => {
|
||||
return [
|
||||
index + 1,
|
||||
...baseAccessors.map((key) => item?.[key] ?? "-"),
|
||||
...metrics.map((m) => item?.[m.key] ?? 0),
|
||||
]
|
||||
})
|
||||
|
||||
const wb = XLSX.utils.book_new()
|
||||
const ws = XLSX.utils.aoa_to_sheet([header, ...rows])
|
||||
XLSX.utils.book_append_sheet(wb, ws, sheetName)
|
||||
XLSX.writeFile(wb, fileName)
|
||||
}
|
||||
|
||||
const rowKey = useMemo(() => {
|
||||
if (appliedFilters.dimension === "all") {
|
||||
return (record: any) =>
|
||||
`${record.project_id ?? "p"}_${record.uid ?? "u"}_${record.project_name ?? ""}_${record.user_name ?? ""}`
|
||||
}
|
||||
if (appliedFilters.dimension === "order_by_group_name") return "group_name"
|
||||
if (appliedFilters.dimension === "order_by_project_name")
|
||||
return "project_name"
|
||||
if (appliedFilters.dimension === "order_by_project_type")
|
||||
return "project_type"
|
||||
return "user_name"
|
||||
}, [appliedFilters.dimension])
|
||||
|
||||
const handleSearch = () => {
|
||||
setAppliedFilters(filters)
|
||||
void refresh(filters)
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
const nextFilters = createInitialFilters()
|
||||
setFilters(nextFilters)
|
||||
setAppliedFilters(nextFilters)
|
||||
void refresh(nextFilters)
|
||||
}
|
||||
|
||||
const [searchExpanded, setSearchExpanded] = useState(false)
|
||||
|
||||
return (
|
||||
<Stack h="100%" gap={12} style={{ minWidth: 0 }}>
|
||||
<SettingFilterPanel>
|
||||
<Stack gap={12}>
|
||||
<div className={settingSurfaceClassNames.filterTopRow}>
|
||||
<SettingInlineFilterField label="开始日期">
|
||||
<DateInput
|
||||
locale="zh-cn"
|
||||
value={filters.startDate}
|
||||
onChange={(e) =>
|
||||
setFilters((s) => ({
|
||||
...s,
|
||||
startDate: dayjs(e).format("YYYY-MM-DD"),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</SettingInlineFilterField>
|
||||
<SettingInlineFilterField label="结束日期">
|
||||
<DateInput
|
||||
locale="zh-cn"
|
||||
value={filters.endDate}
|
||||
onChange={(e) =>
|
||||
setFilters((s) => ({
|
||||
...s,
|
||||
endDate: dayjs(e).format("YYYY-MM-DD"),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</SettingInlineFilterField>
|
||||
<SettingInlineFilterField label="项目名称">
|
||||
<TextInput
|
||||
placeholder="请输入项目名称"
|
||||
value={filters.projectName}
|
||||
onChange={(e) =>
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
projectName: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</SettingInlineFilterField>
|
||||
|
||||
<SettingFilterActions
|
||||
mt={0}
|
||||
className={settingSurfaceClassNames.filterActions}>
|
||||
<Button
|
||||
variant="transparent"
|
||||
className={settingSurfaceClassNames.collapseToggle}
|
||||
rightSection={
|
||||
searchExpanded ? (
|
||||
<IconChevronUp size={16} />
|
||||
) : (
|
||||
<IconChevronDown size={16} />
|
||||
)
|
||||
}
|
||||
onClick={() => setSearchExpanded((v) => !v)}>
|
||||
{searchExpanded ? "收起" : "展开"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
onClick={handleReset}>
|
||||
重置
|
||||
</Button>
|
||||
<Button
|
||||
leftSection={<IconSearch size={16} />}
|
||||
onClick={handleSearch}
|
||||
loading={loading}>
|
||||
查询
|
||||
</Button>
|
||||
</SettingFilterActions>
|
||||
</div>
|
||||
<Collapse in={searchExpanded} transitionDuration={150} animateOpacity>
|
||||
<div className={settingSurfaceClassNames.filterExpandedGrid}>
|
||||
<SettingInlineFilterField label="项目类型">
|
||||
<TextInput
|
||||
placeholder="请输入项目类型"
|
||||
value={filters.projectType}
|
||||
onChange={(e) =>
|
||||
setFilters((prev) => ({
|
||||
...prev,
|
||||
projectType: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</SettingInlineFilterField>
|
||||
</div>
|
||||
</Collapse>
|
||||
</Stack>
|
||||
</SettingFilterPanel>
|
||||
|
||||
<SettingContentPanel
|
||||
style={{ flex: 1, minHeight: 0, minWidth: 0, display: "flex" }}>
|
||||
<Stack gap={12} style={{ flex: 1, minHeight: 0, minWidth: 0 }}>
|
||||
<SettingListHeader
|
||||
title={title}
|
||||
count={`${sortedRecords.length} 条`}
|
||||
actions={
|
||||
<SettingHeaderActions>
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<IconRefresh size={16} />}
|
||||
onClick={() => void refresh(appliedFilters)}
|
||||
loading={loading}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
leftSection={<IconDownload size={16} />}
|
||||
onClick={handleExport}>
|
||||
下载数据
|
||||
</Button>
|
||||
</SettingHeaderActions>
|
||||
}
|
||||
/>
|
||||
<SettingDataTable<any>
|
||||
fetching={loading}
|
||||
records={sortedRecords}
|
||||
columns={columns}
|
||||
idAccessor={rowKey as any}
|
||||
minHeight={0}
|
||||
scrollAreaProps={{ type: "auto" }}
|
||||
style={{ width: "100%", flex: 1, minWidth: 0, minHeight: 0 }}
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
/>
|
||||
</Stack>
|
||||
</SettingContentPanel>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
27
app/person/workload/config.ts
Normal file
27
app/person/workload/config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export type DimensionType =
|
||||
| "all"
|
||||
| "order_by_user_name"
|
||||
| "order_by_group_name"
|
||||
| "order_by_project_name"
|
||||
| "order_by_project_type"
|
||||
|
||||
export const dimensionOpts = [
|
||||
{ label: "全部", value: "all" },
|
||||
{ label: "用户名", value: "order_by_user_name" },
|
||||
{ label: "用户组", value: "order_by_group_name" },
|
||||
{ label: "项目名称", value: "order_by_project_name" },
|
||||
{ label: "项目类型", value: "order_by_project_type" },
|
||||
]
|
||||
|
||||
export type MetricColumn = {
|
||||
key: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type GroupedData = {
|
||||
all: any[]
|
||||
order_by_group_name: any[]
|
||||
order_by_project_name: any[]
|
||||
order_by_project_type: any[]
|
||||
order_by_user_name: any[]
|
||||
}
|
||||
87
app/person/workload/page.tsx
Normal file
87
app/person/workload/page.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
SettingPage,
|
||||
settingSurfaceClassNames,
|
||||
settingTabsClassNames,
|
||||
settingTabsStyles,
|
||||
} from "@/components/setting/PageSurface"
|
||||
import { Stack, Tabs, Text } from "@mantine/core"
|
||||
import { useState } from "react"
|
||||
import LabelWorkloadTable from "./LabelWorkloadTable"
|
||||
import Review1WorkloadTable from "./Review1WorkloadTable"
|
||||
import Review2WorkloadTable from "./Review2WorkloadTable"
|
||||
|
||||
type WorkloadTab = "label" | "review1" | "review2"
|
||||
|
||||
function createVisitedTabs() {
|
||||
return {
|
||||
label: true,
|
||||
review1: false,
|
||||
review2: false,
|
||||
task: false,
|
||||
}
|
||||
}
|
||||
|
||||
export default function TeamWorkloadPage() {
|
||||
const moduleTitle = "个人中心"
|
||||
const [activeTab, setActiveTab] = useState<WorkloadTab>("label")
|
||||
const [visitedTabs, setVisitedTabs] = useState(createVisitedTabs)
|
||||
|
||||
const handleTabChange = (value: string | null) => {
|
||||
if (!value) return
|
||||
|
||||
const nextTab = value as WorkloadTab
|
||||
setActiveTab(nextTab)
|
||||
setVisitedTabs((current) =>
|
||||
current[nextTab] ? current : { ...current, [nextTab]: true }
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingPage>
|
||||
<Stack className={settingSurfaceClassNames.pageIntro}>
|
||||
<Text className={settingSurfaceClassNames.breadcrumb}>
|
||||
{moduleTitle} /{" "}
|
||||
<span className={settingSurfaceClassNames.breadcrumbCurrent}>
|
||||
工时列表
|
||||
</span>
|
||||
</Text>
|
||||
</Stack>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
variant="pills"
|
||||
color="#1874FF"
|
||||
autoContrast
|
||||
radius="sm"
|
||||
classNames={settingTabsClassNames}
|
||||
styles={{
|
||||
root: {
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
...settingTabsStyles,
|
||||
}}>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab value="label">标注工时</Tabs.Tab>
|
||||
<Tabs.Tab value="review1">审核工时</Tabs.Tab>
|
||||
<Tabs.Tab value="review2">复审工时</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Panel value="label">
|
||||
{visitedTabs.label ? <LabelWorkloadTable /> : null}
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="review1">
|
||||
{visitedTabs.review1 ? <Review1WorkloadTable /> : null}
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="review2">
|
||||
{visitedTabs.review2 ? <Review2WorkloadTable /> : null}
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
</SettingPage>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user