Files
labelmain-demo/app/management/project/detail/InfoSettingContainer.tsx

370 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import {
getProjectAdminList,
projectConfig,
} from "@/components/label/api/project"
import { ProjectDetail } from "@/components/label/api/project/typing"
import { useAllUserStore } from "@/components/label/store/auth"
import {
ActionIcon,
Collapse,
Group,
Paper,
ScrollArea,
SimpleGrid,
Stack,
Switch,
Text,
} from "@mantine/core"
import { notifications } from "@mantine/notifications"
import {
IconCheck,
IconChevronDown,
IconChevronRight,
IconEdit,
IconX,
} from "@tabler/icons-react"
import { useCallback, useEffect, useMemo, useState } from "react"
import AdminUserModal from "./components/AdminUserModal"
import LabelSchemeContainer from "./components/LabelSchemeContainer"
import NoteRulesTable from "./components/NoteRulesTable"
import SamplingPlanContainer from "./components/SamplingPlanContainer"
import TaskStageTabsContainer from "./components/TaskStageTabsContainer"
export default function InfoSettingContainer(props: {
info: ProjectDetail.DataProps | undefined
userEnums: {
allEnum: Map<any, any>
labelEnum: Map<any, any>
review1Enum: Map<any, any>
review2Enum: Map<any, any>
}
updateAction: () => void
}) {
const { info, userEnums, updateAction } = props
const projectId = info?.id ?? -1
const { userOpts } = useAllUserStore()
const fallbackUserOptions = useMemo(() => {
return userOpts.map((o) => ({ label: o.label, value: String(o.value) }))
}, [userOpts])
const [adminOptions, setAdminOptions] = useState<
Array<{ label: string; value: string }>
>([])
const [adminModalOpen, setAdminModalOpen] = useState(false)
const [lockingValue, setLockingValue] = useState<boolean>(
!!info?.is_lock_object
)
const [lockingEditValue, setLockingEditValue] = useState<boolean>(
!!info?.is_lock_object
)
const [isLockingEdit, setIsLockingEdit] = useState(false)
const [savingLock, setSavingLock] = useState(false)
const [show, setShow] = useState({
process: true,
scheme: true,
sampling: true,
note: true,
})
useEffect(() => {
const v = !!info?.is_lock_object
setLockingValue(v)
setLockingEditValue(v)
setIsLockingEdit(false)
}, [info?.is_lock_object])
const loadAdminOptions = useCallback(async () => {
try {
const res = await getProjectAdminList()
const opts = (res ?? []).map((item) => ({
label: item.label,
value: String(item.value),
}))
setAdminOptions(opts)
} catch {
setAdminOptions([])
}
}, [])
useEffect(() => {
queueMicrotask(loadAdminOptions)
}, [loadAdminOptions])
const adminText = useMemo(() => {
const adminUids = info?.admin_user ?? []
const names = adminUids.map((uid) => {
const opt = userOpts.find((o) => o.value === uid)
return opt?.label ?? String(uid)
})
return names.length ? names.join("、") : "-"
}, [info?.admin_user, userOpts])
const saveLock = async () => {
if (projectId === -1) return
setSavingLock(true)
try {
await projectConfig({
project_id: projectId,
is_lock_object: lockingEditValue,
})
notifications.show({
color: "green",
title: "操作成功",
message: "已保存配置",
})
setLockingValue(lockingEditValue)
setIsLockingEdit(false)
updateAction()
} catch (e) {
notifications.show({
color: "red",
title: "操作失败",
message: e instanceof Error ? e.message : "请求失败",
})
} finally {
setSavingLock(false)
}
}
const sectionTitle = (
key: keyof typeof show,
title: string,
color: "blue" | "orange" | "teal" | "grape"
) => {
const opened = show[key]
return (
<Group justify="space-between" wrap="nowrap">
<Group gap={8}>
<div
style={{
width: 3,
height: 14,
borderRadius: 2,
backgroundColor: `var(--mantine-color-${color}-6)`,
}}
/>
<Text fw={700}>{title}</Text>
</Group>
<ActionIcon
variant="subtle"
color="gray"
onClick={() => setShow((s) => ({ ...s, [key]: !opened }))}>
{opened ? (
<IconChevronDown size={16} />
) : (
<IconChevronRight size={16} />
)}
</ActionIcon>
</Group>
)
}
const finalAdminOptions =
adminOptions.length > 0 ? adminOptions : fallbackUserOptions
return (
<>
<Stack gap="md" style={{ flex: 1, minHeight: 0 }} w="100%">
<Paper
withBorder
p="sm"
radius="md"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
}}>
<Stack gap="sm">
<Group justify="space-between" wrap="wrap">
<Text fw={700}></Text>
<ActionIcon variant="subtle" onClick={updateAction}>
<IconEdit size={16} />
</ActionIcon>
</Group>
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="sm">
<Paper
withBorder
p="xs"
radius="sm"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
}}>
<Group justify="space-between" wrap="nowrap" align="center">
<Stack gap={2} style={{ minWidth: 0 }}>
<Text size="sm" fw={600}>
</Text>
<Text
size="sm"
c="dimmed"
style={{
maxWidth: 420,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}>
{adminText}
</Text>
</Stack>
<ActionIcon
variant="subtle"
color="blue"
onClick={() => {
setAdminModalOpen(true)
if (adminOptions.length === 0) {
queueMicrotask(loadAdminOptions)
}
}}>
<IconEdit size={16} />
</ActionIcon>
</Group>
</Paper>
<Paper
withBorder
p="xs"
radius="sm"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
}}>
<Group justify="space-between" wrap="nowrap" align="center">
<Stack gap={2}>
<Text size="sm" fw={600}>
</Text>
<Text size="sm" c="dimmed">
</Text>
</Stack>
{isLockingEdit ? (
<Group gap={4} wrap="nowrap">
<Switch
checked={lockingEditValue}
onChange={(e) =>
setLockingEditValue(e.currentTarget.checked)
}
/>
<ActionIcon
variant="subtle"
color="gray"
onClick={() => {
setLockingEditValue(lockingValue)
setIsLockingEdit(false)
}}>
<IconX size={16} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="blue"
loading={savingLock}
onClick={saveLock}>
<IconCheck size={16} />
</ActionIcon>
</Group>
) : (
<Group gap={8} wrap="nowrap">
<Switch checked={lockingValue} readOnly />
<ActionIcon
variant="subtle"
color="blue"
onClick={() => setIsLockingEdit(true)}>
<IconEdit size={16} />
</ActionIcon>
</Group>
)}
</Group>
</Paper>
</SimpleGrid>
<Group justify="flex-end">
<ActionIcon variant="subtle" onClick={updateAction}>
<IconCheck size={16} />
</ActionIcon>
<Text size="xs" c="dimmed">
</Text>
</Group>
</Stack>
</Paper>
<Paper
withBorder
p="sm"
radius="md"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
flex: 1,
minHeight: 0,
}}>
<ScrollArea h="100%" type="auto">
<Stack gap="sm">
{sectionTitle("process", "任务流程", "blue")}
<Collapse in={show.process}>
<TaskStageTabsContainer
projectId={projectId}
info={info}
userOptions={fallbackUserOptions}
userEnums={userEnums}
onUpdatedAction={updateAction}
/>
</Collapse>
{sectionTitle("scheme", "标注方案", "orange")}
<Collapse in={show.scheme}>
<LabelSchemeContainer info={info} />
</Collapse>
{sectionTitle("sampling", "抽检方案", "teal")}
<Collapse in={show.sampling}>
<SamplingPlanContainer
projectId={projectId}
info={info}
onUpdated={updateAction}
/>
</Collapse>
{sectionTitle("note", "批注规则", "grape")}
<Collapse in={show.note}>
<NoteRulesTable info={info} onUpdated={updateAction} />
</Collapse>
</Stack>
</ScrollArea>
</Paper>
</Stack>
<AdminUserModal
opened={adminModalOpen}
value={info?.admin_user ?? []}
options={finalAdminOptions}
onCloseAction={async (next) => {
setAdminModalOpen(false)
if (!next || projectId === -1) return
try {
await projectConfig({ project_id: projectId, admin_user: next })
notifications.show({
color: "green",
title: "操作成功",
message: "已保存项目管理员",
})
updateAction()
} catch (e) {
notifications.show({
color: "red",
title: "操作失败",
message: e instanceof Error ? e.message : "请求失败",
})
}
}}
/>
</>
)
}