import React, { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { z } from 'zod'; import { Button } from '@d8d/shared-ui-components/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@d8d/shared-ui-components/components/ui/dialog'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@d8d/shared-ui-components/components/ui/card'; import { Badge } from '@d8d/shared-ui-components/components/ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table'; import { Separator } from '@d8d/shared-ui-components/components/ui/separator'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from '@d8d/shared-ui-components/components/ui/form'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@d8d/shared-ui-components/components/ui/select'; import { Input } from '@d8d/shared-ui-components/components/ui/input'; import { toast } from 'sonner'; import { User, Users, X } from 'lucide-react'; import { OrderStatus, WorkStatus } from '@d8d/allin-enums'; import { DisabledPersonSelector } from '@d8d/allin-disability-person-management-ui'; import { PlatformSelector } from '@d8d/allin-platform-management-ui'; import { CompanySelector } from '@d8d/allin-company-management-ui'; import { ChannelSelector } from '@d8d/allin-channel-management-ui'; import { orderClientManager } from '../api/orderClient'; import type { OrderDetail } from '../api/types'; import type { DisabledPersonData } from '@d8d/allin-disability-person-management-ui'; // 人员信息Schema - 与后端BatchAddPersonItemSchema保持一致 const personInfoSchema = z.object({ personId: z.number().int().positive('请选择人员'), joinDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '请选择有效的入职日期格式(YYYY-MM-DD)'), salaryDetail: z.coerce.number().positive('薪资必须为正数'), leaveDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '请选择有效的离职日期格式(YYYY-MM-DD)').optional(), workStatus: z.nativeEnum(WorkStatus).optional(), }); // 订单表单Schema - 与后端实体和schema保持一致 const orderFormSchema = 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: z.array(personInfoSchema).min(1, '至少选择一名人员'), }); type OrderFormValues = z.infer; interface OrderFormProps { order?: OrderDetail; open: boolean; onOpenChange: (open: boolean) => void; onSuccess?: () => void; } export const OrderForm: React.FC = ({ order, open, onOpenChange, onSuccess, }) => { const queryClient = useQueryClient(); const [isSubmitting, setIsSubmitting] = useState(false); const [selectedPersons, setSelectedPersons] = useState([]); const [isPersonSelectorOpen, setIsPersonSelectorOpen] = useState(false); // 当残疾人选择器打开时,防止订单表单关闭 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({ resolver: zodResolver(orderFormSchema), defaultValues: { orderName: '', platformId: 0, companyId: 0, channelId: undefined, expectedStartDate: undefined, orderStatus: OrderStatus.DRAFT, workStatus: WorkStatus.NOT_WORKING, orderPersons: [], }, }); // 当订单数据变化时,重置表单 useEffect(() => { if (order && open) { // 使用类型断言,因为OrderDetail可能不包含所有表单字段 const orderData = order as any; // 转换日期格式:如果后端返回ISO格式,转换为YYYY-MM-DD const expectedStartDate = orderData.expectedStartDate ? orderData.expectedStartDate.slice(0, 10) : undefined; form.reset({ orderName: orderData.orderName || '', platformId: orderData.platformId || 0, companyId: orderData.companyId || 0, channelId: orderData.channelId || undefined, expectedStartDate, orderStatus: orderData.orderStatus || OrderStatus.DRAFT, workStatus: orderData.workStatus || WorkStatus.NOT_WORKING, orderPersons: [], }); // 编辑模式下清空已选择的人员 setSelectedPersons([]); } else if (!order && open) { form.reset({ orderName: '', platformId: 0, companyId: 0, channelId: undefined, expectedStartDate: undefined, orderStatus: OrderStatus.DRAFT, workStatus: WorkStatus.NOT_WORKING, orderPersons: [], }); // 创建模式下清空已选择的人员 setSelectedPersons([]); } }, [order, open, form]); // 创建订单Mutation - 只接受订单基本信息,不包含orderPersons const createMutation = useMutation({ mutationFn: async (data: Omit) => { const response = await orderClientManager.get().create.$post({ json: data, }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || '创建订单失败'); } return response.json(); }, onSuccess: () => { toast.success('订单创建成功'); queryClient.invalidateQueries({ queryKey: ['orders'] }); onOpenChange(false); form.reset(); onSuccess?.(); }, onError: (error: Error) => { toast.error(`创建订单失败: ${error.message}`); }, }); // 更新订单Mutation const updateMutation = useMutation({ mutationFn: async (data: OrderFormValues & { id: number }) => { const response = await orderClientManager.get().update[':id'].$put({ param: { id: data.id }, json: data, }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || '更新订单失败'); } return response.json(); }, onSuccess: () => { toast.success('订单更新成功'); queryClient.invalidateQueries({ queryKey: ['orders'] }); queryClient.invalidateQueries({ queryKey: ['order', order?.id] }); onOpenChange(false); form.reset(); onSuccess?.(); }, onError: (error: Error) => { toast.error(`更新订单失败: ${error.message}`); }, }); // 处理表单提交 const onSubmit = async (data: OrderFormValues) => { 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?.(); } } catch (error) { toast.error(`操作失败: ${error instanceof Error ? error.message : '未知错误'}`); } finally { setIsSubmitting(false); } }; // 处理区域选择变化 - 已移除相关字段 // const handleAreaChange = (value: { provinceId?: number; cityId?: number; districtId?: number }) => { // form.setValue('provinceId', value.provinceId, { shouldValidate: true }); // form.setValue('cityId', value.cityId, { shouldValidate: true }); // form.setValue('districtId', value.districtId, { shouldValidate: true }); // }; // 处理残疾人选择 const handlePersonSelect = (persons: DisabledPersonData | DisabledPersonData[]) => { if (Array.isArray(persons)) { // 多选模式 const newPersons = persons.filter( person => !selectedPersons.some(p => p.id === person.id) ); setSelectedPersons(prev => [...prev, ...newPersons]); // 更新表单值 - 根据后端API要求包含必需字段 const currentPersons = form.getValues('orderPersons') || []; const newFormPersons = newPersons.map(person => ({ personId: person.id, joinDate: new Date().toISOString().slice(0, 10), // 默认当前日期(YYYY-MM-DD格式) salaryDetail: 0, // 默认值,需要用户填写 leaveDate: undefined, workStatus: WorkStatus.WORKING, })); form.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', [ ...currentPersons, { personId: person.id, joinDate: new Date().toISOString().slice(0, 10), // 默认当前日期(YYYY-MM-DD格式) salaryDetail: 0, // 默认值,需要用户填写 leaveDate: undefined, workStatus: WorkStatus.WORKING, } ]); } } 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 handlePersonDetailChange = (personId: number, field: keyof OrderFormValues['orderPersons'][0], value: string | number) => { const currentPersons = form.getValues('orderPersons') || []; const updatedPersons = currentPersons.map(person => person.personId === personId ? { ...person, [field]: value } : person ); form.setValue('orderPersons', updatedPersons); }; // 订单状态选项 const orderStatusOptions = [ { value: OrderStatus.DRAFT, label: '草稿' }, { value: OrderStatus.CONFIRMED, label: '已确认' }, { value: OrderStatus.IN_PROGRESS, label: '进行中' }, { value: OrderStatus.COMPLETED, label: '已完成' }, { value: OrderStatus.CANCELLED, label: '已取消' }, ]; // 工作状态选项 const workStatusOptions = [ { value: WorkStatus.NOT_WORKING, label: '未就业' }, { value: WorkStatus.PRE_WORKING, label: '待就业' }, { value: WorkStatus.WORKING, label: '已就业' }, { value: WorkStatus.RESIGNED, label: '已离职' }, ]; return ( <> {order?.id ? '编辑订单' : '创建订单'} {order?.id ? '修改订单信息' : '创建新的订单'}
console.debug('表单验证错误:', errors))} className="space-y-4">
( 订单名称 )} /> ( 平台 )} /> ( 公司 )} /> ( 渠道 )} /> ( 预计开始日期 field.onChange(e.target.value || undefined)} /> )} /> ( 订单状态 )} /> ( 工作状态 )} /> {/* 人员选择区域 - 只在创建订单时显示 */} {!order?.id && (
选择残疾人
创建订单时必须至少选择一名残疾人
{selectedPersons.length === 0 ? (
尚未选择残疾人
点击"选择残疾人"按钮打开选择器
) : (
姓名 性别 残疾证号 残疾类型 残疾等级 操作 {selectedPersons.map((person) => ( {person.name} {person.gender} {person.disabilityId} {person.disabilityType} {person.disabilityLevel} ))}
{/* 人员详情设置 */} 设置人员信息 为每个残疾人设置入职信息和薪资
{selectedPersons.map((person) => (
{person.name}
{person.disabilityType} {person.disabilityLevel}
残疾证号: {person.disabilityId} | 联系电话: {person.phone}
入职日期 handlePersonDetailChange(person.id, 'joinDate', e.target.value || '')} data-testid={`join-date-input-${person.id}`} />
离职日期 handlePersonDetailChange(person.id, 'leaveDate', e.target.value || '')} data-testid={`leave-date-input-${person.id}`} />
薪资详情 { 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 && (

{form.formState.errors.orderPersons[selectedPersons.findIndex(p => p.id === person.id)].salaryDetail?.message}

)}
工作状态
))}
)} {form.formState.errors.orderPersons?.message}
)}
p.id)} /> ); }; export default OrderForm;