|
|
@@ -58,8 +58,8 @@ const personInfoSchema = z.object({
|
|
|
workStatus: z.nativeEnum(WorkStatus).optional(),
|
|
|
});
|
|
|
|
|
|
-// 订单表单Schema - 与后端实体和schema保持一致
|
|
|
-const orderFormSchema = z.object({
|
|
|
+// 创建订单表单Schema - 需要人员信息
|
|
|
+const createOrderFormSchema = z.object({
|
|
|
orderName: z.string().min(1, '订单名称不能为空').max(50, '订单名称不能超过50个字符'),
|
|
|
platformId: z.number().int().positive('请选择平台'),
|
|
|
companyId: z.number().int().positive('请选择公司'),
|
|
|
@@ -70,7 +70,21 @@ const orderFormSchema = z.object({
|
|
|
orderPersons: z.array(personInfoSchema).min(1, '至少选择一名人员'),
|
|
|
});
|
|
|
|
|
|
-type OrderFormValues = z.infer<typeof orderFormSchema>;
|
|
|
+// 编辑订单表单Schema - 不需要人员信息
|
|
|
+const updateOrderFormSchema = z.object({
|
|
|
+ orderName: z.string().min(1, '订单名称不能为空').max(50, '订单名称不能超过50个字符'),
|
|
|
+ platformId: z.number().int().positive('请选择平台'),
|
|
|
+ companyId: z.number().int().positive('请选择公司'),
|
|
|
+ channelId: z.number().int().positive('请选择渠道').optional(),
|
|
|
+ expectedStartDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '请选择有效的日期格式(YYYY-MM-DD)').optional(),
|
|
|
+ orderStatus: z.nativeEnum(OrderStatus),
|
|
|
+ workStatus: z.nativeEnum(WorkStatus),
|
|
|
+ // 编辑订单时orderPersons为可选,因为人员在查看详情中编辑
|
|
|
+ orderPersons: z.array(personInfoSchema).optional(),
|
|
|
+});
|
|
|
+
|
|
|
+type CreateOrderFormValues = z.infer<typeof createOrderFormSchema>;
|
|
|
+type UpdateOrderFormValues = z.infer<typeof updateOrderFormSchema>;
|
|
|
|
|
|
interface OrderFormProps {
|
|
|
order?: OrderDetail;
|
|
|
@@ -93,24 +107,21 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
// 当残疾人选择器打开时,防止订单表单关闭
|
|
|
useEffect(() => {
|
|
|
// 这个effect只是用于调试
|
|
|
- console.debug('OrderForm: isPersonSelectorOpen changed to', isPersonSelectorOpen);
|
|
|
}, [isPersonSelectorOpen]);
|
|
|
|
|
|
// 处理Dialog的onOpenChange,防止在残疾人选择器打开时关闭订单表单
|
|
|
const handleDialogOpenChange = (newOpen: boolean) => {
|
|
|
- console.debug('OrderForm: handleDialogOpenChange called with', newOpen, 'isPersonSelectorOpen:', isPersonSelectorOpen);
|
|
|
if (isPersonSelectorOpen && !newOpen) {
|
|
|
- console.debug('OrderForm: 阻止订单表单关闭,因为残疾人选择器正在打开');
|
|
|
return; // 如果残疾人选择器正在打开,阻止订单表单关闭
|
|
|
}
|
|
|
onOpenChange(newOpen);
|
|
|
};
|
|
|
|
|
|
|
|
|
- // 初始化表单 - 根据UI包开发规范,必须使用条件渲染两个独立的Form组件
|
|
|
- // 这里使用同一个组件,但通过reset来区分创建和编辑模式
|
|
|
- const form = useForm<OrderFormValues>({
|
|
|
- resolver: zodResolver(orderFormSchema),
|
|
|
+ // 根据UI包开发规范,使用条件渲染两个独立的Form组件
|
|
|
+ // 创建订单表单
|
|
|
+ const createForm = useForm<CreateOrderFormValues>({
|
|
|
+ resolver: zodResolver(createOrderFormSchema),
|
|
|
defaultValues: {
|
|
|
orderName: '',
|
|
|
platformId: 0,
|
|
|
@@ -123,6 +134,24 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
+ // 编辑订单表单
|
|
|
+ const updateForm = useForm<UpdateOrderFormValues>({
|
|
|
+ resolver: zodResolver(updateOrderFormSchema),
|
|
|
+ defaultValues: {
|
|
|
+ orderName: '',
|
|
|
+ platformId: 0,
|
|
|
+ companyId: 0,
|
|
|
+ channelId: undefined,
|
|
|
+ expectedStartDate: undefined,
|
|
|
+ orderStatus: OrderStatus.DRAFT,
|
|
|
+ workStatus: WorkStatus.NOT_WORKING,
|
|
|
+ orderPersons: undefined,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 根据模式选择当前表单
|
|
|
+ const currentForm = order?.id ? updateForm : createForm;
|
|
|
+
|
|
|
// 当订单数据变化时,重置表单
|
|
|
useEffect(() => {
|
|
|
if (order && open) {
|
|
|
@@ -133,7 +162,8 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
? orderData.expectedStartDate.slice(0, 10)
|
|
|
: undefined;
|
|
|
|
|
|
- form.reset({
|
|
|
+ // 编辑模式:使用updateForm,不包含orderPersons
|
|
|
+ updateForm.reset({
|
|
|
orderName: orderData.orderName || '',
|
|
|
platformId: orderData.platformId || 0,
|
|
|
companyId: orderData.companyId || 0,
|
|
|
@@ -141,12 +171,13 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
expectedStartDate,
|
|
|
orderStatus: orderData.orderStatus || OrderStatus.DRAFT,
|
|
|
workStatus: orderData.workStatus || WorkStatus.NOT_WORKING,
|
|
|
- orderPersons: [],
|
|
|
+ orderPersons: undefined,
|
|
|
});
|
|
|
// 编辑模式下清空已选择的人员
|
|
|
setSelectedPersons([]);
|
|
|
} else if (!order && open) {
|
|
|
- form.reset({
|
|
|
+ // 创建模式:使用createForm,包含空的orderPersons数组
|
|
|
+ createForm.reset({
|
|
|
orderName: '',
|
|
|
platformId: 0,
|
|
|
companyId: 0,
|
|
|
@@ -159,11 +190,11 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
// 创建模式下清空已选择的人员
|
|
|
setSelectedPersons([]);
|
|
|
}
|
|
|
- }, [order, open, form]);
|
|
|
+ }, [order, open, createForm, updateForm]);
|
|
|
|
|
|
// 创建订单Mutation - 只接受订单基本信息,不包含orderPersons
|
|
|
const createMutation = useMutation({
|
|
|
- mutationFn: async (data: Omit<OrderFormValues, 'orderPersons'>) => {
|
|
|
+ mutationFn: async (data: Omit<CreateOrderFormValues, 'orderPersons'>) => {
|
|
|
const response = await orderClientManager.get().create.$post({
|
|
|
json: data,
|
|
|
});
|
|
|
@@ -177,7 +208,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
toast.success('订单创建成功');
|
|
|
queryClient.invalidateQueries({ queryKey: ['orders'] });
|
|
|
onOpenChange(false);
|
|
|
- form.reset();
|
|
|
+ createForm.reset();
|
|
|
onSuccess?.();
|
|
|
},
|
|
|
onError: (error: Error) => {
|
|
|
@@ -185,9 +216,9 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- // 更新订单Mutation
|
|
|
+ // 更新订单Mutation - 编辑订单时只传递基本信息,不包含orderPersons
|
|
|
const updateMutation = useMutation({
|
|
|
- mutationFn: async (data: OrderFormValues & { id: number }) => {
|
|
|
+ mutationFn: async (data: Omit<UpdateOrderFormValues, 'orderPersons'> & { id: number }) => {
|
|
|
const response = await orderClientManager.get().update[':id'].$put({
|
|
|
param: { id: data.id },
|
|
|
json: data,
|
|
|
@@ -203,7 +234,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
queryClient.invalidateQueries({ queryKey: ['orders'] });
|
|
|
queryClient.invalidateQueries({ queryKey: ['order', order?.id] });
|
|
|
onOpenChange(false);
|
|
|
- form.reset();
|
|
|
+ updateForm.reset();
|
|
|
onSuccess?.();
|
|
|
},
|
|
|
onError: (error: Error) => {
|
|
|
@@ -211,44 +242,56 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
- // 处理表单提交
|
|
|
- const onSubmit = async (data: OrderFormValues) => {
|
|
|
+ // 处理创建订单表单提交
|
|
|
+ const onCreateSubmit = async (data: CreateOrderFormValues) => {
|
|
|
+ setIsSubmitting(true);
|
|
|
+ try {
|
|
|
+ // 创建订单:先创建订单,然后批量添加人员
|
|
|
+ // 提取订单基本信息(不包含orderPersons)
|
|
|
+ const { orderPersons, ...orderData } = data;
|
|
|
+
|
|
|
+ // 使用createMutation创建订单
|
|
|
+ const createdOrder = await createMutation.mutateAsync(orderData);
|
|
|
+
|
|
|
+ // 如果有人员信息,批量添加人员
|
|
|
+ if (orderPersons && orderPersons.length > 0) {
|
|
|
+ try {
|
|
|
+ const batchResponse = await orderClientManager.get()[':orderId'].persons.batch.$post({
|
|
|
+ param: { orderId: createdOrder.id },
|
|
|
+ json: { persons: orderPersons },
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!batchResponse.ok) {
|
|
|
+ const error = await batchResponse.json();
|
|
|
+ throw new Error(error.message || '批量添加人员失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ toast.success(`成功添加 ${orderPersons.length} 名人员到订单`);
|
|
|
+ } catch (batchError) {
|
|
|
+ // 人员添加失败,但订单已创建成功
|
|
|
+ toast.warning(`订单创建成功,但添加人员失败: ${batchError instanceof Error ? batchError.message : '未知错误'}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // createMutation的onSuccess已经处理了刷新列表和重置表单
|
|
|
+ // 这里只需要调用onSuccess回调
|
|
|
+ onSuccess?.();
|
|
|
+ } catch (error) {
|
|
|
+ toast.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
|
+ } finally {
|
|
|
+ setIsSubmitting(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理编辑订单表单提交
|
|
|
+ const onUpdateSubmit = async (data: UpdateOrderFormValues) => {
|
|
|
setIsSubmitting(true);
|
|
|
try {
|
|
|
if (order?.id) {
|
|
|
// 编辑订单:只更新订单信息,不处理人员
|
|
|
- await updateMutation.mutateAsync({ ...data, id: order.id });
|
|
|
- } else {
|
|
|
- // 创建订单:先创建订单,然后批量添加人员
|
|
|
// 提取订单基本信息(不包含orderPersons)
|
|
|
const { orderPersons, ...orderData } = data;
|
|
|
-
|
|
|
- // 使用createMutation创建订单
|
|
|
- const createdOrder = await createMutation.mutateAsync(orderData);
|
|
|
-
|
|
|
- // 如果有人员信息,批量添加人员
|
|
|
- if (orderPersons && orderPersons.length > 0) {
|
|
|
- try {
|
|
|
- const batchResponse = await orderClientManager.get()[':orderId'].persons.batch.$post({
|
|
|
- param: { orderId: createdOrder.id },
|
|
|
- json: { persons: orderPersons },
|
|
|
- });
|
|
|
-
|
|
|
- if (!batchResponse.ok) {
|
|
|
- const error = await batchResponse.json();
|
|
|
- throw new Error(error.message || '批量添加人员失败');
|
|
|
- }
|
|
|
-
|
|
|
- toast.success(`成功添加 ${orderPersons.length} 名人员到订单`);
|
|
|
- } catch (batchError) {
|
|
|
- // 人员添加失败,但订单已创建成功
|
|
|
- toast.warning(`订单创建成功,但添加人员失败: ${batchError instanceof Error ? batchError.message : '未知错误'}`);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // createMutation的onSuccess已经处理了刷新列表和重置表单
|
|
|
- // 这里只需要调用onSuccess回调
|
|
|
- onSuccess?.();
|
|
|
+ await updateMutation.mutateAsync({ ...orderData, id: order.id });
|
|
|
}
|
|
|
} catch (error) {
|
|
|
toast.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
|
|
@@ -264,7 +307,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
// form.setValue('districtId', value.districtId, { shouldValidate: true });
|
|
|
// };
|
|
|
|
|
|
- // 处理残疾人选择
|
|
|
+ // 处理残疾人选择 - 只在创建订单时使用
|
|
|
const handlePersonSelect = (persons: DisabledPersonData | DisabledPersonData[]) => {
|
|
|
if (Array.isArray(persons)) {
|
|
|
// 多选模式
|
|
|
@@ -273,8 +316,8 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
);
|
|
|
setSelectedPersons(prev => [...prev, ...newPersons]);
|
|
|
|
|
|
- // 更新表单值 - 根据后端API要求包含必需字段
|
|
|
- const currentPersons = form.getValues('orderPersons') || [];
|
|
|
+ // 更新创建订单表单值 - 根据后端API要求包含必需字段
|
|
|
+ const currentPersons = createForm.getValues('orderPersons') || [];
|
|
|
const newFormPersons = newPersons.map(person => ({
|
|
|
personId: person.id,
|
|
|
joinDate: new Date().toISOString().slice(0, 10), // 默认当前日期(YYYY-MM-DD格式)
|
|
|
@@ -282,16 +325,16 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
leaveDate: undefined,
|
|
|
workStatus: WorkStatus.WORKING,
|
|
|
}));
|
|
|
- form.setValue('orderPersons', [...currentPersons, ...newFormPersons]);
|
|
|
+ createForm.setValue('orderPersons', [...currentPersons, ...newFormPersons]);
|
|
|
} else {
|
|
|
// 单选模式
|
|
|
const person = persons;
|
|
|
if (!selectedPersons.some(p => p.id === person.id)) {
|
|
|
setSelectedPersons(prev => [...prev, person]);
|
|
|
|
|
|
- // 更新表单值 - 根据后端API要求包含必需字段
|
|
|
- const currentPersons = form.getValues('orderPersons') || [];
|
|
|
- form.setValue('orderPersons', [
|
|
|
+ // 更新创建订单表单值 - 根据后端API要求包含必需字段
|
|
|
+ const currentPersons = createForm.getValues('orderPersons') || [];
|
|
|
+ createForm.setValue('orderPersons', [
|
|
|
...currentPersons,
|
|
|
{
|
|
|
personId: person.id,
|
|
|
@@ -306,22 +349,22 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
setIsPersonSelectorOpen(false);
|
|
|
};
|
|
|
|
|
|
- // 处理移除人员
|
|
|
+ // 处理移除人员 - 只在创建订单时使用
|
|
|
const handleRemovePerson = (personId: number) => {
|
|
|
setSelectedPersons(prev => prev.filter(p => p.id !== personId));
|
|
|
|
|
|
- // 更新表单值
|
|
|
- const currentPersons = form.getValues('orderPersons') || [];
|
|
|
- form.setValue('orderPersons', currentPersons.filter(p => p.personId !== personId));
|
|
|
+ // 更新创建订单表单值
|
|
|
+ const currentPersons = createForm.getValues('orderPersons') || [];
|
|
|
+ createForm.setValue('orderPersons', currentPersons.filter(p => p.personId !== personId));
|
|
|
};
|
|
|
|
|
|
- // 处理人员详情更新
|
|
|
- const handlePersonDetailChange = (personId: number, field: keyof OrderFormValues['orderPersons'][0], value: string | number) => {
|
|
|
- const currentPersons = form.getValues('orderPersons') || [];
|
|
|
+ // 处理人员详情更新 - 只在创建订单时使用
|
|
|
+ const handlePersonDetailChange = (personId: number, field: keyof CreateOrderFormValues['orderPersons'][0], value: string | number) => {
|
|
|
+ const currentPersons = createForm.getValues('orderPersons') || [];
|
|
|
const updatedPersons = currentPersons.map(person =>
|
|
|
person.personId === personId ? { ...person, [field]: value } : person
|
|
|
);
|
|
|
- form.setValue('orderPersons', updatedPersons);
|
|
|
+ createForm.setValue('orderPersons', updatedPersons);
|
|
|
};
|
|
|
|
|
|
// 订单状态选项
|
|
|
@@ -354,11 +397,14 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
</DialogDescription>
|
|
|
</DialogHeader>
|
|
|
|
|
|
- <Form {...form}>
|
|
|
- <form onSubmit={form.handleSubmit(onSubmit, (errors) => console.debug('表单验证错误:', errors))} className="space-y-4">
|
|
|
+ {/* 根据UI包开发规范,使用条件渲染两个独立的Form组件 */}
|
|
|
+ {order?.id ? (
|
|
|
+ // 编辑订单表单
|
|
|
+ <Form {...updateForm}>
|
|
|
+ <form onSubmit={updateForm.handleSubmit(onUpdateSubmit, (errors) => console.debug('编辑表单验证错误:', errors))} className="space-y-4" data-testid="order-update-form">
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="orderName"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -372,7 +418,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="platformId"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -382,7 +428,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
value={field.value || 0}
|
|
|
onChange={field.onChange}
|
|
|
placeholder="选择平台"
|
|
|
- data-testid="platform-selector"
|
|
|
+ data-testid="platform-selector-edit"
|
|
|
/>
|
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
|
@@ -391,7 +437,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="companyId"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -401,7 +447,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
value={field.value || 0}
|
|
|
onChange={field.onChange}
|
|
|
placeholder="选择公司"
|
|
|
- data-testid="company-selector"
|
|
|
+ data-testid="company-selector-edit"
|
|
|
/>
|
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
|
@@ -410,7 +456,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="channelId"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -420,7 +466,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
value={field.value || 0}
|
|
|
onChange={field.onChange}
|
|
|
placeholder="选择渠道"
|
|
|
- data-testid="channel-selector"
|
|
|
+ data-testid="channel-selector-edit"
|
|
|
/>
|
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
|
@@ -429,7 +475,7 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="expectedStartDate"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -449,12 +495,12 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="orderStatus"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
<FormLabel>订单状态</FormLabel>
|
|
|
- <Select onValueChange={field.onChange} value={field.value}>
|
|
|
+ <Select onValueChange={field.onChange} value={field.value} data-testid="order-status-select">
|
|
|
<FormControl>
|
|
|
<SelectTrigger>
|
|
|
<SelectValue placeholder="选择订单状态" />
|
|
|
@@ -474,12 +520,12 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={updateForm.control}
|
|
|
name="workStatus"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
<FormLabel>工作状态</FormLabel>
|
|
|
- <Select onValueChange={field.onChange} value={field.value}>
|
|
|
+ <Select onValueChange={field.onChange} value={field.value} data-testid="work-status-select">
|
|
|
<FormControl>
|
|
|
<SelectTrigger>
|
|
|
<SelectValue placeholder="选择工作状态" />
|
|
|
@@ -499,176 +545,338 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
/>
|
|
|
|
|
|
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Separator />
|
|
|
+
|
|
|
+ <DialogFooter>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="outline"
|
|
|
+ onClick={() => onOpenChange(false)}
|
|
|
+ disabled={isSubmitting}
|
|
|
+ >
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button type="submit" disabled={isSubmitting} data-testid="order-update-submit-button">
|
|
|
+ {isSubmitting ? '提交中...' : '更新'}
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ ) : (
|
|
|
+ // 创建订单表单
|
|
|
+ <Form {...createForm}>
|
|
|
+ <form onSubmit={createForm.handleSubmit(onCreateSubmit, (errors) => console.debug('创建表单验证错误:', errors))} className="space-y-4" data-testid="order-create-form">
|
|
|
+ <div className="grid grid-cols-2 gap-4">
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="orderName"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>订单名称</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入订单名称" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="platformId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>平台</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <PlatformSelector
|
|
|
+ value={field.value || 0}
|
|
|
+ onChange={field.onChange}
|
|
|
+ placeholder="选择平台"
|
|
|
+ data-testid="platform-selector-create"
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="companyId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>公司</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <CompanySelector
|
|
|
+ value={field.value || 0}
|
|
|
+ onChange={field.onChange}
|
|
|
+ placeholder="选择公司"
|
|
|
+ data-testid="company-selector-create"
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="channelId"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>渠道</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <ChannelSelector
|
|
|
+ value={field.value || 0}
|
|
|
+ onChange={field.onChange}
|
|
|
+ placeholder="选择渠道"
|
|
|
+ data-testid="channel-selector-create"
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="expectedStartDate"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>预计开始日期</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input
|
|
|
+ type="date"
|
|
|
+ {...field}
|
|
|
+ value={field.value ? field.value.slice(0, 10) : ''}
|
|
|
+ onChange={(e) => field.onChange(e.target.value || undefined)}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="orderStatus"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>订单状态</FormLabel>
|
|
|
+ <Select onValueChange={field.onChange} value={field.value} data-testid="order-status-select">
|
|
|
+ <FormControl>
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="选择订单状态" />
|
|
|
+ </SelectTrigger>
|
|
|
+ </FormControl>
|
|
|
+ <SelectContent>
|
|
|
+ {orderStatusOptions.map((option) => (
|
|
|
+ <SelectItem key={option.value} value={option.value}>
|
|
|
+ {option.label}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="workStatus"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>工作状态</FormLabel>
|
|
|
+ <Select onValueChange={field.onChange} value={field.value} data-testid="work-status-select">
|
|
|
+ <FormControl>
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="选择工作状态" />
|
|
|
+ </SelectTrigger>
|
|
|
+ </FormControl>
|
|
|
+ <SelectContent>
|
|
|
+ {workStatusOptions.map((option) => (
|
|
|
+ <SelectItem key={option.value} value={option.value}>
|
|
|
+ {option.label}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
{/* 人员选择区域 - 只在创建订单时显示 */}
|
|
|
- {!order?.id && (
|
|
|
- <div className="col-span-2">
|
|
|
- <Card>
|
|
|
- <CardHeader className="py-3">
|
|
|
- <CardTitle className="text-lg flex items-center justify-between">
|
|
|
- <div className="flex items-center">
|
|
|
- <Users className="mr-2 h-5 w-5" />
|
|
|
- 选择残疾人
|
|
|
- </div>
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- variant="outline"
|
|
|
- size="sm"
|
|
|
- onClick={() => setIsPersonSelectorOpen(true)}
|
|
|
- data-testid="select-persons-button"
|
|
|
- >
|
|
|
- <User className="mr-2 h-4 w-4" />
|
|
|
- 选择残疾人
|
|
|
- </Button>
|
|
|
- </CardTitle>
|
|
|
- <CardDescription>
|
|
|
- 创建订单时必须至少选择一名残疾人
|
|
|
- </CardDescription>
|
|
|
- </CardHeader>
|
|
|
- <CardContent className="py-3">
|
|
|
- {selectedPersons.length === 0 ? (
|
|
|
- <div className="text-center py-8 border rounded-md">
|
|
|
- <Users className="h-12 w-12 mx-auto text-muted-foreground mb-2" />
|
|
|
- <div className="text-muted-foreground">尚未选择残疾人</div>
|
|
|
- <div className="text-sm text-muted-foreground mt-1">
|
|
|
- 点击"选择残疾人"按钮打开选择器
|
|
|
- </div>
|
|
|
+ <div className="col-span-2">
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="py-3">
|
|
|
+ <CardTitle className="text-lg flex items-center justify-between">
|
|
|
+ <div className="flex items-center">
|
|
|
+ <Users className="mr-2 h-5 w-5" />
|
|
|
+ 选择残疾人
|
|
|
+ </div>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ variant="outline"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => setIsPersonSelectorOpen(true)}
|
|
|
+ data-testid="select-persons-button"
|
|
|
+ >
|
|
|
+ <User className="mr-2 h-4 w-4" />
|
|
|
+ 选择残疾人
|
|
|
+ </Button>
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ 创建订单时必须至少选择一名残疾人
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="py-3">
|
|
|
+ {selectedPersons.length === 0 ? (
|
|
|
+ <div className="text-center py-8 border rounded-md">
|
|
|
+ <Users className="h-12 w-12 mx-auto text-muted-foreground mb-2" />
|
|
|
+ <div className="text-muted-foreground">尚未选择残疾人</div>
|
|
|
+ <div className="text-sm text-muted-foreground mt-1">
|
|
|
+ 点击"选择残疾人"按钮打开选择器
|
|
|
</div>
|
|
|
- ) : (
|
|
|
- <div className="space-y-4">
|
|
|
- <div className="border rounded-md">
|
|
|
- <Table>
|
|
|
- <TableHeader>
|
|
|
- <TableRow>
|
|
|
- <TableHead>姓名</TableHead>
|
|
|
- <TableHead>性别</TableHead>
|
|
|
- <TableHead>残疾证号</TableHead>
|
|
|
- <TableHead>残疾类型</TableHead>
|
|
|
- <TableHead>残疾等级</TableHead>
|
|
|
- <TableHead>操作</TableHead>
|
|
|
+ </div>
|
|
|
+ ) : (
|
|
|
+ <div className="space-y-4">
|
|
|
+ <div className="border rounded-md">
|
|
|
+ <Table>
|
|
|
+ <TableHeader>
|
|
|
+ <TableRow>
|
|
|
+ <TableHead>姓名</TableHead>
|
|
|
+ <TableHead>性别</TableHead>
|
|
|
+ <TableHead>残疾证号</TableHead>
|
|
|
+ <TableHead>残疾类型</TableHead>
|
|
|
+ <TableHead>残疾等级</TableHead>
|
|
|
+ <TableHead>操作</TableHead>
|
|
|
+ </TableRow>
|
|
|
+ </TableHeader>
|
|
|
+ <TableBody>
|
|
|
+ {selectedPersons.map((person) => (
|
|
|
+ <TableRow key={person.id}>
|
|
|
+ <TableCell className="font-medium">{person.name}</TableCell>
|
|
|
+ <TableCell>{person.gender}</TableCell>
|
|
|
+ <TableCell>{person.disabilityId}</TableCell>
|
|
|
+ <TableCell>{person.disabilityType}</TableCell>
|
|
|
+ <TableCell>{person.disabilityLevel}</TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ onClick={() => handleRemovePerson(person.id)}
|
|
|
+ data-testid={`remove-person-button-${person.id}`}
|
|
|
+ >
|
|
|
+ <X className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </TableCell>
|
|
|
</TableRow>
|
|
|
- </TableHeader>
|
|
|
- <TableBody>
|
|
|
- {selectedPersons.map((person) => (
|
|
|
- <TableRow key={person.id}>
|
|
|
- <TableCell className="font-medium">{person.name}</TableCell>
|
|
|
- <TableCell>{person.gender}</TableCell>
|
|
|
- <TableCell>{person.disabilityId}</TableCell>
|
|
|
- <TableCell>{person.disabilityType}</TableCell>
|
|
|
- <TableCell>{person.disabilityLevel}</TableCell>
|
|
|
- <TableCell>
|
|
|
- <Button
|
|
|
- variant="ghost"
|
|
|
- size="sm"
|
|
|
- onClick={() => handleRemovePerson(person.id)}
|
|
|
- data-testid={`remove-person-button-${person.id}`}
|
|
|
- >
|
|
|
- <X className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- </TableCell>
|
|
|
- </TableRow>
|
|
|
- ))}
|
|
|
- </TableBody>
|
|
|
- </Table>
|
|
|
- </div>
|
|
|
-
|
|
|
- {/* 人员详情设置 */}
|
|
|
- <Card>
|
|
|
- <CardHeader className="py-3">
|
|
|
- <CardTitle className="text-lg">设置人员信息</CardTitle>
|
|
|
- <CardDescription>
|
|
|
- 为每个残疾人设置入职信息和薪资
|
|
|
- </CardDescription>
|
|
|
- </CardHeader>
|
|
|
- <CardContent className="py-3">
|
|
|
- <div className="space-y-4">
|
|
|
- {selectedPersons.map((person) => (
|
|
|
- <Card key={person.id} className="border">
|
|
|
- <CardHeader className="py-3">
|
|
|
- <CardTitle className="text-base flex items-center justify-between">
|
|
|
- <div className="flex items-center">
|
|
|
- <User className="mr-2 h-4 w-4" />
|
|
|
- {person.name}
|
|
|
- </div>
|
|
|
- <Badge variant="outline">
|
|
|
- {person.disabilityType} {person.disabilityLevel}
|
|
|
- </Badge>
|
|
|
- </CardTitle>
|
|
|
- <CardDescription>
|
|
|
- 残疾证号: {person.disabilityId} | 联系电话: {person.phone}
|
|
|
- </CardDescription>
|
|
|
- </CardHeader>
|
|
|
- <CardContent className="py-3">
|
|
|
- <div className="grid grid-cols-4 gap-4">
|
|
|
- <div>
|
|
|
- <FormLabel className="text-sm">入职日期</FormLabel>
|
|
|
- <Input
|
|
|
- type="date"
|
|
|
- defaultValue={new Date().toISOString().slice(0, 10)}
|
|
|
- onChange={(e) => handlePersonDetailChange(person.id, 'joinDate', e.target.value || '')}
|
|
|
- data-testid={`join-date-input-${person.id}`}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <FormLabel className="text-sm">离职日期</FormLabel>
|
|
|
- <Input
|
|
|
- type="date"
|
|
|
- onChange={(e) => handlePersonDetailChange(person.id, 'leaveDate', e.target.value || '')}
|
|
|
- data-testid={`leave-date-input-${person.id}`}
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <FormLabel className="text-sm">薪资详情</FormLabel>
|
|
|
- <Input
|
|
|
- type="number"
|
|
|
- placeholder="请输入薪资"
|
|
|
- defaultValue="0"
|
|
|
- onChange={(e) => {
|
|
|
- const value = e.target.value === '' ? 0 : Number(e.target.value);
|
|
|
- handlePersonDetailChange(person.id, 'salaryDetail', value);
|
|
|
- }}
|
|
|
- data-testid={`salary-detail-input-${person.id}`}
|
|
|
- />
|
|
|
- {/* 显示薪资验证错误 */}
|
|
|
- {form.formState.errors.orderPersons?.[selectedPersons.findIndex(p => p.id === person.id)]?.salaryDetail && (
|
|
|
- <p className="text-sm font-medium text-destructive mt-1">
|
|
|
- {form.formState.errors.orderPersons[selectedPersons.findIndex(p => p.id === person.id)].salaryDetail?.message}
|
|
|
- </p>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <FormLabel className="text-sm">工作状态</FormLabel>
|
|
|
- <Select
|
|
|
- defaultValue={WorkStatus.WORKING}
|
|
|
- onValueChange={(value) => handlePersonDetailChange(person.id, 'workStatus', value as WorkStatus)}
|
|
|
- >
|
|
|
- <SelectTrigger>
|
|
|
- <SelectValue placeholder="选择工作状态" />
|
|
|
- </SelectTrigger>
|
|
|
- <SelectContent>
|
|
|
- {workStatusOptions.map((option) => (
|
|
|
- <SelectItem key={option.value} value={option.value}>
|
|
|
- {option.label}
|
|
|
- </SelectItem>
|
|
|
- ))}
|
|
|
- </SelectContent>
|
|
|
- </Select>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
+ ))}
|
|
|
+ </TableBody>
|
|
|
+ </Table>
|
|
|
</div>
|
|
|
- )}
|
|
|
- <FormMessage>
|
|
|
- {form.formState.errors.orderPersons?.message}
|
|
|
- </FormMessage>
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
- </div>
|
|
|
- )}
|
|
|
+
|
|
|
+ {/* 人员详情设置 */}
|
|
|
+ <Card>
|
|
|
+ <CardHeader className="py-3">
|
|
|
+ <CardTitle className="text-lg">设置人员信息</CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ 为每个残疾人设置入职信息和薪资
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="py-3">
|
|
|
+ <div className="space-y-4">
|
|
|
+ {selectedPersons.map((person) => (
|
|
|
+ <Card key={person.id} className="border">
|
|
|
+ <CardHeader className="py-3">
|
|
|
+ <CardTitle className="text-base flex items-center justify-between">
|
|
|
+ <div className="flex items-center">
|
|
|
+ <User className="mr-2 h-4 w-4" />
|
|
|
+ {person.name}
|
|
|
+ </div>
|
|
|
+ <Badge variant="outline">
|
|
|
+ {person.disabilityType} {person.disabilityLevel}
|
|
|
+ </Badge>
|
|
|
+ </CardTitle>
|
|
|
+ <CardDescription>
|
|
|
+ 残疾证号: {person.disabilityId} | 联系电话: {person.phone}
|
|
|
+ </CardDescription>
|
|
|
+ </CardHeader>
|
|
|
+ <CardContent className="py-3">
|
|
|
+ <div className="grid grid-cols-4 gap-4">
|
|
|
+ <div>
|
|
|
+ <FormLabel className="text-sm">入职日期</FormLabel>
|
|
|
+ <Input
|
|
|
+ type="date"
|
|
|
+ defaultValue={new Date().toISOString().slice(0, 10)}
|
|
|
+ onChange={(e) => handlePersonDetailChange(person.id, 'joinDate', e.target.value || '')}
|
|
|
+ data-testid={`join-date-input-${person.id}`}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <FormLabel className="text-sm">离职日期</FormLabel>
|
|
|
+ <Input
|
|
|
+ type="date"
|
|
|
+ onChange={(e) => handlePersonDetailChange(person.id, 'leaveDate', e.target.value || '')}
|
|
|
+ data-testid={`leave-date-input-${person.id}`}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <FormLabel className="text-sm">薪资详情</FormLabel>
|
|
|
+ <Input
|
|
|
+ type="number"
|
|
|
+ placeholder="请输入薪资"
|
|
|
+ defaultValue="0"
|
|
|
+ onChange={(e) => {
|
|
|
+ const value = e.target.value === '' ? 0 : Number(e.target.value);
|
|
|
+ handlePersonDetailChange(person.id, 'salaryDetail', value);
|
|
|
+ }}
|
|
|
+ data-testid={`salary-detail-input-${person.id}`}
|
|
|
+ />
|
|
|
+ {/* 显示薪资验证错误 */}
|
|
|
+ {createForm.formState.errors.orderPersons?.[selectedPersons.findIndex(p => p.id === person.id)]?.salaryDetail && (
|
|
|
+ <p className="text-sm font-medium text-destructive mt-1">
|
|
|
+ {createForm.formState.errors.orderPersons[selectedPersons.findIndex(p => p.id === person.id)].salaryDetail?.message}
|
|
|
+ </p>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <FormLabel className="text-sm">工作状态</FormLabel>
|
|
|
+ <Select
|
|
|
+ defaultValue={WorkStatus.WORKING}
|
|
|
+ onValueChange={(value) => handlePersonDetailChange(person.id, 'workStatus', value as WorkStatus)}
|
|
|
+ >
|
|
|
+ <SelectTrigger>
|
|
|
+ <SelectValue placeholder="选择工作状态" />
|
|
|
+ </SelectTrigger>
|
|
|
+ <SelectContent>
|
|
|
+ {workStatusOptions.map((option) => (
|
|
|
+ <SelectItem key={option.value} value={option.value}>
|
|
|
+ {option.label}
|
|
|
+ </SelectItem>
|
|
|
+ ))}
|
|
|
+ </SelectContent>
|
|
|
+ </Select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <FormMessage>
|
|
|
+ {createForm.formState.errors.orderPersons?.message}
|
|
|
+ </FormMessage>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<Separator />
|
|
|
@@ -682,12 +890,14 @@ export const OrderForm: React.FC<OrderFormProps> = ({
|
|
|
>
|
|
|
取消
|
|
|
</Button>
|
|
|
- <Button type="submit" disabled={isSubmitting}>
|
|
|
- {isSubmitting ? '提交中...' : order?.id ? '更新' : '创建'}
|
|
|
+ <Button type="submit" disabled={isSubmitting} data-testid="order-create-submit-button">
|
|
|
+ {isSubmitting ? '提交中...' : '创建'}
|
|
|
</Button>
|
|
|
</DialogFooter>
|
|
|
- </form>
|
|
|
- </Form>
|
|
|
+
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ )}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
|