421 lines
12 KiB
JavaScript
421 lines
12 KiB
JavaScript
import React, { useState, useEffect, useMemo } from "react";
|
|
import {
|
|
Form,
|
|
Input,
|
|
Select,
|
|
Button,
|
|
Space,
|
|
Card,
|
|
Typography,
|
|
message,
|
|
DatePicker,
|
|
} from "antd";
|
|
import {
|
|
ArrowLeftOutlined,
|
|
SaveOutlined,
|
|
} from "@ant-design/icons";
|
|
import { supabase } from "@/config/supabase";
|
|
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import TaskList from '@/components/TaskList';
|
|
import dayjs from 'dayjs';
|
|
import {supabaseService} from '@/hooks/supabaseService'
|
|
|
|
const { Title } = Typography;
|
|
const TYPE = "task";
|
|
|
|
export default function TaskForm() {
|
|
const { id } = useParams();
|
|
const [searchParams] = useSearchParams();
|
|
const isEdit = searchParams.get("edit") === "true";
|
|
const templateId = searchParams.get("templateId");
|
|
const isView = id && !isEdit;
|
|
const [form] = Form.useForm();
|
|
const navigate = useNavigate();
|
|
const [loading, setLoading] = useState(false);
|
|
const [customers, setCustomers] = useState([]);
|
|
const [formValues, setFormValues] = useState({});
|
|
const [units, setUnits] = useState([]);
|
|
const [loadingUnits,setLoadingUnits]=useState(false)
|
|
const fetchUnits = async () => {
|
|
setLoadingUnits(true);
|
|
try {
|
|
const { data: units } = await supabaseService.select("resources", {
|
|
filter: {
|
|
type: { eq: "units" },
|
|
"attributes->>template_type": { in: `(${TYPE})` },
|
|
},
|
|
order: {
|
|
column: "created_at",
|
|
ascending: false,
|
|
},
|
|
});
|
|
|
|
setUnits(units || []);
|
|
} catch (error) {
|
|
message.error("获取单位列表失败");
|
|
console.error(error);
|
|
} finally {
|
|
setLoadingUnits(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchUnits();
|
|
}, []);
|
|
const initialValues = {
|
|
sections: [
|
|
{
|
|
key: uuidv4(),
|
|
sectionName: "任务类型 1",
|
|
items: [
|
|
{
|
|
key: uuidv4(),
|
|
name: "",
|
|
description: "",
|
|
timeRange: null,
|
|
unit: "未开始",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
status: "未开始",
|
|
timeRange: null,
|
|
};
|
|
|
|
// 处理表单值变化
|
|
const handleValuesChange = (changedValues, allValues) => {
|
|
setFormValues(allValues);
|
|
};
|
|
|
|
// 获取任务详情
|
|
const fetchTaskDetail = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const { data, error } = await supabase
|
|
.from("resources")
|
|
.select("*")
|
|
.eq("id", id)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
if (data?.attributes) {
|
|
const formData = {
|
|
taskName: data.attributes.taskName,
|
|
customers: data.attributes.customers.map((customer) => customer.id) || [],
|
|
description: data.attributes.description,
|
|
sections: data.attributes.sections.map((section) => ({
|
|
key: uuidv4(),
|
|
sectionName: section.sectionName,
|
|
items: section.items.map((item) => ({
|
|
key: uuidv4(),
|
|
name: item.name,
|
|
description: item.description || "",
|
|
timeRange: item.timeRange,
|
|
unit: item.unit || "未开始",
|
|
})),
|
|
})),
|
|
status: data.attributes.status || "未开始",
|
|
timeRange: data.attributes.timeRange
|
|
? [dayjs(data.attributes.timeRange[0]), dayjs(data.attributes.timeRange[1])]
|
|
: null,
|
|
};
|
|
|
|
form.setFieldsValue(formData);
|
|
setFormValues(formData);
|
|
}
|
|
} catch (error) {
|
|
console.error("获取任务详情失败:", error);
|
|
message.error("获取任务详情失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 获取模板数据
|
|
const fetchTemplateData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const { data: template, error } = await supabase
|
|
.from("resources")
|
|
.select("*")
|
|
.eq("type", "serviceTemplate")
|
|
.eq("id", templateId)
|
|
.single();
|
|
|
|
if (error) throw error;
|
|
|
|
if (template?.attributes) {
|
|
const taskData = {
|
|
taskName: template.attributes.templateName,
|
|
description: template.attributes.description,
|
|
sections: template.attributes.sections.map((section) => ({
|
|
key: uuidv4(),
|
|
sectionName: section.sectionName,
|
|
items: section.items.map((item) => ({
|
|
key: uuidv4(),
|
|
name: item.name,
|
|
description: item.description,
|
|
timeRange: null,
|
|
unit: "未开始",
|
|
})),
|
|
})),
|
|
};
|
|
form.setFieldsValue(taskData);
|
|
setFormValues(taskData);
|
|
}
|
|
} catch (error) {
|
|
console.error("获取模板数据失败:", error);
|
|
message.error("获取模板数据失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 获取客户列表
|
|
const fetchCustomers = async () => {
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from("resources")
|
|
.select("*")
|
|
.eq("type", "customer");
|
|
|
|
if (error) throw error;
|
|
setCustomers(data || []);
|
|
} catch (error) {
|
|
console.error("获取客户列表失败:", error);
|
|
}
|
|
};
|
|
|
|
// 保存任务
|
|
const onFinish = async (values) => {
|
|
try {
|
|
setLoading(true);
|
|
const taskData = {
|
|
type: "task",
|
|
attributes: {
|
|
taskName: values.taskName,
|
|
customers: customers
|
|
.filter(customer => values.customers.includes(customer.id))
|
|
.map(customer => ({
|
|
id: customer.id,
|
|
name: customer.attributes.name
|
|
})),
|
|
description: values.description,
|
|
sections: values.sections.map((section) => ({
|
|
sectionName: section.sectionName,
|
|
items: section.items.map((item) => ({
|
|
name: item.name,
|
|
description: item.description,
|
|
timeRange: item.timeRange,
|
|
unit: item.unit,
|
|
})),
|
|
})),
|
|
status: values.status,
|
|
timeRange: values.timeRange
|
|
? [
|
|
values.timeRange[0].format('YYYY-MM-DD'),
|
|
values.timeRange[1].format('YYYY-MM-DD')
|
|
]
|
|
: null,
|
|
},
|
|
};
|
|
|
|
let result;
|
|
if (id) {
|
|
result = await supabase
|
|
.from("resources")
|
|
.update(taskData)
|
|
.eq("id", id)
|
|
.select();
|
|
} else {
|
|
result = await supabase
|
|
.from("resources")
|
|
.insert([taskData])
|
|
.select();
|
|
}
|
|
|
|
if (result.error) throw result.error;
|
|
|
|
message.success("保存成功");
|
|
navigate("/company/task");
|
|
} catch (error) {
|
|
console.error("保存失败:", error);
|
|
message.error("保存失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchCustomers();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (id) {
|
|
fetchTaskDetail();
|
|
} else if (templateId) {
|
|
fetchTemplateData();
|
|
} else {
|
|
form.setFieldsValue(initialValues);
|
|
setFormValues(initialValues);
|
|
}
|
|
}, [id, templateId]);
|
|
|
|
return (
|
|
<div className="bg-gradient-to-b from-gray-50 to-white dark:from-gray-800 dark:to-gray-900/90 min-h-screen p-2">
|
|
<Card
|
|
className="shadow-lg rounded-lg border-0"
|
|
title={
|
|
<div className="flex justify-between items-center py-2">
|
|
<div className="flex items-center space-x-3">
|
|
<Title level={4} className="mb-0 text-gray-800">
|
|
{id ? (isEdit ? "编辑任务" : "查看任务") : "新建任务"}
|
|
</Title>
|
|
<span className="text-gray-400 text-sm">
|
|
{id
|
|
? isEdit
|
|
? "请修改任务信息"
|
|
: "任务详情"
|
|
: "请填写任务信息"}
|
|
</span>
|
|
</div>
|
|
<Space size="middle">
|
|
<Button
|
|
icon={<ArrowLeftOutlined />}
|
|
onClick={() => navigate("/company/task")}
|
|
>
|
|
返回
|
|
</Button>
|
|
{!isView && (
|
|
<Button
|
|
type="primary"
|
|
icon={<SaveOutlined />}
|
|
onClick={() => form.submit()}
|
|
loading={loading}
|
|
>
|
|
保存
|
|
</Button>
|
|
)}
|
|
</Space>
|
|
</div>
|
|
}
|
|
>
|
|
<Form
|
|
form={form}
|
|
onFinish={onFinish}
|
|
onValuesChange={handleValuesChange}
|
|
layout="vertical"
|
|
disabled={isView}
|
|
initialValues={initialValues}
|
|
>
|
|
<Card
|
|
className="shadow-sm rounded-lg"
|
|
type="inner"
|
|
title={
|
|
<span className="flex items-center space-x-2 text-gray-700">
|
|
<span className="w-1 h-4 bg-blue-500 rounded-full" />
|
|
<span>基本信息</span>
|
|
</span>
|
|
}
|
|
bordered={false}
|
|
>
|
|
<div className="grid grid-cols-2 gap-8">
|
|
<Form.Item
|
|
name="taskName"
|
|
label={<span className="text-gray-700 font-medium">任务名称</span>}
|
|
rules={[{ required: true, message: "请输入任务名称" }]}
|
|
>
|
|
<Input
|
|
placeholder="请输入任务名称"
|
|
className=" hover:border-blue-400 focus:border-blue-500"
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="customers"
|
|
label={<span className="text-gray-700 font-medium">客户名称</span>}
|
|
rules={[{ required: true, message: "请选择至少一个客户" }]}
|
|
>
|
|
<Select
|
|
mode="multiple"
|
|
placeholder="请选择客户"
|
|
className=" hover:border-blue-400 focus:border-blue-500"
|
|
showSearch
|
|
optionFilterProp="children"
|
|
filterOption={(input, option) =>
|
|
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
|
}
|
|
options={customers.map((customer) => ({
|
|
value: customer.id,
|
|
label: customer.attributes.name,
|
|
}))}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="status"
|
|
label={<span className="text-gray-700 font-medium">任务状态</span>}
|
|
rules={[{ required: true, message: "请选择任务状态" }]}
|
|
>
|
|
<Select
|
|
allowClear
|
|
loading={loadingUnits}
|
|
placeholder="请选择任务状态"
|
|
className=" hover:border-blue-400 focus:border-blue-500"
|
|
options={units.map((unit) => ({
|
|
label: unit.attributes.name,
|
|
value: unit.attributes.name,
|
|
}))}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="timeRange"
|
|
label={<span className="text-gray-700 font-medium">时间范围</span>}
|
|
rules={[{ required: true, message: "请选择时间范围" }]}
|
|
>
|
|
<DatePicker.RangePicker
|
|
className="w-full hover:border-blue-400 focus:border-blue-500"
|
|
format="YYYY-MM-DD"
|
|
onChange={(dates) => {
|
|
if (dates) {
|
|
form.setFieldValue('timeRange', [
|
|
dayjs(dates[0]),
|
|
dayjs(dates[1])
|
|
]);
|
|
} else {
|
|
form.setFieldValue('timeRange', null);
|
|
}
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
</div>
|
|
</Card>
|
|
|
|
<Card
|
|
className="shadow-sm rounded-lg mt-6"
|
|
type="inner"
|
|
title={
|
|
<span className="flex items-center space-x-2 text-gray-700">
|
|
<span className="w-1 h-4 bg-blue-500 rounded-full" />
|
|
<span>任务流程</span>
|
|
</span>
|
|
}
|
|
bordered={false}
|
|
>
|
|
<TaskList
|
|
type={TYPE}
|
|
form={form}
|
|
isView={isView}
|
|
formValues={formValues}
|
|
onValuesChange={handleValuesChange}
|
|
/>
|
|
</Card>
|
|
</Form>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|