|
|
@@ -31,10 +31,17 @@ import {
|
|
|
SelectTrigger,
|
|
|
SelectValue,
|
|
|
} from "@d8d/shared-ui-components/components/ui/select";
|
|
|
+import {
|
|
|
+ Popover,
|
|
|
+ PopoverContent,
|
|
|
+ PopoverTrigger,
|
|
|
+} from "@d8d/shared-ui-components/components/ui/popover";
|
|
|
+import { Calendar as CalendarComponent } from "@d8d/shared-ui-components/components/ui/calendar";
|
|
|
import { Badge } from "@d8d/shared-ui-components/components/ui/badge";
|
|
|
import { Input } from "@d8d/shared-ui-components/components/ui/input";
|
|
|
import { toast } from "sonner";
|
|
|
-import { Users, FileText, Calendar, Play, CheckCircle, X } from "lucide-react";
|
|
|
+import { format } from "date-fns";
|
|
|
+import { Users, FileText, Calendar as CalendarIcon, Play, CheckCircle, X } from "lucide-react";
|
|
|
import {
|
|
|
OrderStatus,
|
|
|
WorkStatus,
|
|
|
@@ -45,7 +52,6 @@ import { salaryClientManager } from "@d8d/allin-salary-management-ui";
|
|
|
import { DisabledPersonSelector } from "@d8d/allin-disability-person-management-ui";
|
|
|
import OrderAssetModal from "./OrderAssetModal";
|
|
|
import AttendanceModal from "./AttendanceModal";
|
|
|
-import PersonDateEditDialog from "./PersonDateEditDialog";
|
|
|
import type { DisabledPersonData } from "@d8d/allin-disability-person-management-ui";
|
|
|
|
|
|
interface OrderDetailModalProps {
|
|
|
@@ -76,15 +82,8 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
const [isPersonSelectorOpen, setIsPersonSelectorOpen] = useState(false);
|
|
|
const [isAssetAssociationOpen, setIsAssetAssociationOpen] = useState(false);
|
|
|
const [isAttendanceModalOpen, setIsAttendanceModalOpen] = useState(false);
|
|
|
- const [isDateEditDialogOpen, setIsDateEditDialogOpen] = useState(false);
|
|
|
const [isActionLoading, setIsActionLoading] = useState(false);
|
|
|
const [pendingPersons, setPendingPersons] = useState<PendingPerson[]>([]);
|
|
|
- const [editingPerson, setEditingPerson] = useState<{
|
|
|
- personId: number;
|
|
|
- personName: string;
|
|
|
- joinDate?: string;
|
|
|
- leaveDate?: string | null;
|
|
|
- } | null>(null);
|
|
|
|
|
|
// 查询订单详情
|
|
|
const {
|
|
|
@@ -187,6 +186,70 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
},
|
|
|
});
|
|
|
|
|
|
+ // 更新人员入职日期
|
|
|
+ const updateJoinDateMutation = useMutation({
|
|
|
+ mutationFn: async ({
|
|
|
+ orderId,
|
|
|
+ personId,
|
|
|
+ joinDate,
|
|
|
+ }: {
|
|
|
+ orderId: number;
|
|
|
+ personId: number;
|
|
|
+ joinDate: string;
|
|
|
+ }) => {
|
|
|
+ const orderClient = orderClientManager.get();
|
|
|
+ const response = await orderClient.persons.dates.$put({
|
|
|
+ json: { orderId, personId, joinDate }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const error = await response.json();
|
|
|
+ throw new Error(error.message || "更新入职日期失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ return await response.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ toast.success("入职日期更新成功");
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ toast.error(error.message || "更新入职日期失败");
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新人员离职日期
|
|
|
+ const updateLeaveDateMutation = useMutation({
|
|
|
+ mutationFn: async ({
|
|
|
+ orderId,
|
|
|
+ personId,
|
|
|
+ leaveDate,
|
|
|
+ }: {
|
|
|
+ orderId: number;
|
|
|
+ personId: number;
|
|
|
+ leaveDate: string | null;
|
|
|
+ }) => {
|
|
|
+ const orderClient = orderClientManager.get();
|
|
|
+ const response = await orderClient.persons.dates.$put({
|
|
|
+ json: { orderId, personId, leaveDate }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ const error = await response.json();
|
|
|
+ throw new Error(error.message || "更新离职日期失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ return await response.json();
|
|
|
+ },
|
|
|
+ onSuccess: () => {
|
|
|
+ toast.success("离职日期更新成功");
|
|
|
+ refetch();
|
|
|
+ },
|
|
|
+ onError: (error) => {
|
|
|
+ toast.error(error.message || "更新离职日期失败");
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
// 批量添加人员
|
|
|
const batchAddPersonsMutation = useMutation({
|
|
|
mutationFn: async (persons: PendingPerson[]) => {
|
|
|
@@ -388,16 +451,20 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
updateWorkStatusMutation.mutate({ orderId, personId, workStatus });
|
|
|
};
|
|
|
|
|
|
- // 处理编辑人员日期
|
|
|
- const handleEditPersonDates = (
|
|
|
- personId: number,
|
|
|
- personName: string,
|
|
|
- joinDate?: string,
|
|
|
- leaveDate?: string | null
|
|
|
- ) => {
|
|
|
+ // 处理更新入职日期
|
|
|
+ const handleUpdateJoinDate = (personId: number, date: Date | undefined) => {
|
|
|
+ if (!orderId || !date) return;
|
|
|
+ // 使用 date-fns format 确保本地时区正确格式化
|
|
|
+ const joinDate = format(date, 'yyyy-MM-dd');
|
|
|
+ updateJoinDateMutation.mutate({ orderId, personId, joinDate });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理更新离职日期
|
|
|
+ const handleUpdateLeaveDate = (personId: number, date: Date | undefined) => {
|
|
|
if (!orderId) return;
|
|
|
- setEditingPerson({ personId, personName, joinDate, leaveDate });
|
|
|
- setIsDateEditDialogOpen(true);
|
|
|
+ // 使用 date-fns format 确保本地时区正确格式化
|
|
|
+ const leaveDate = date ? format(date, 'yyyy-MM-dd') : null;
|
|
|
+ updateLeaveDateMutation.mutate({ orderId, personId, leaveDate });
|
|
|
};
|
|
|
|
|
|
// 获取订单状态徽章样式
|
|
|
@@ -447,7 +514,7 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- <Dialog open={open} onOpenChange={onOpenChange}>
|
|
|
+ <Dialog open={open} onOpenChange={onOpenChange} modal={false}>
|
|
|
<DialogContent
|
|
|
className="max-w-[95vw] sm:max-w-7xl max-h-[90vh] overflow-y-auto"
|
|
|
data-testid="order-detail-dialog"
|
|
|
@@ -696,38 +763,56 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
<TableCell>{person.person?.disabilityType || '未知'}</TableCell>
|
|
|
<TableCell>{person.person?.phone || '未知'}</TableCell>
|
|
|
<TableCell>
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={() =>
|
|
|
- handleEditPersonDates(
|
|
|
- person.personId,
|
|
|
- person.person?.name || `人员${person.personId}`,
|
|
|
- person.joinDate?.toString(),
|
|
|
- person.leaveDate?.toString()
|
|
|
- )
|
|
|
- }
|
|
|
- className="text-left hover:text-primary hover:underline transition-colors"
|
|
|
- data-testid={`edit-join-date-${person.personId}`}
|
|
|
- >
|
|
|
- {formatDate(person.joinDate)}
|
|
|
- </button>
|
|
|
+ <Popover>
|
|
|
+ <PopoverTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ className="w-full justify-start font-normal h-auto py-1 px-2 text-left hover:text-primary hover:underline"
|
|
|
+ data-testid={`edit-join-date-${person.personId}`}
|
|
|
+ >
|
|
|
+ <CalendarIcon className="mr-2 h-3 w-3" />
|
|
|
+ {formatDate(person.joinDate)}
|
|
|
+ </Button>
|
|
|
+ </PopoverTrigger>
|
|
|
+ <PopoverContent className="w-auto p-0 z-[100]" align="start">
|
|
|
+ <CalendarComponent
|
|
|
+ mode="single"
|
|
|
+ selected={person.joinDate ? new Date(person.joinDate) : undefined}
|
|
|
+ onSelect={(date) => handleUpdateJoinDate(person.personId, date)}
|
|
|
+ disabled={(date) =>
|
|
|
+ date > new Date() || date < new Date("1900-01-01")
|
|
|
+ }
|
|
|
+ data-testid={`join-date-calendar-${person.personId}`}
|
|
|
+ />
|
|
|
+ </PopoverContent>
|
|
|
+ </Popover>
|
|
|
</TableCell>
|
|
|
<TableCell>
|
|
|
- <button
|
|
|
- type="button"
|
|
|
- onClick={() =>
|
|
|
- handleEditPersonDates(
|
|
|
- person.personId,
|
|
|
- person.person?.name || `人员${person.personId}`,
|
|
|
- person.joinDate?.toString(),
|
|
|
- person.leaveDate?.toString()
|
|
|
- )
|
|
|
- }
|
|
|
- className="text-left hover:text-primary hover:underline transition-colors"
|
|
|
- data-testid={`edit-leave-date-${person.personId}`}
|
|
|
- >
|
|
|
- {formatDate(person.leaveDate ? person.leaveDate.toString() : undefined)}
|
|
|
- </button>
|
|
|
+ <Popover>
|
|
|
+ <PopoverTrigger asChild>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="sm"
|
|
|
+ className="w-full justify-start font-normal h-auto py-1 px-2 text-left hover:text-primary hover:underline"
|
|
|
+ data-testid={`edit-leave-date-${person.personId}`}
|
|
|
+ >
|
|
|
+ <CalendarIcon className="mr-2 h-3 w-3" />
|
|
|
+ {formatDate(person.leaveDate ? person.leaveDate.toString() : undefined)}
|
|
|
+ </Button>
|
|
|
+ </PopoverTrigger>
|
|
|
+ <PopoverContent className="w-auto p-0 z-[100]" align="start">
|
|
|
+ <CalendarComponent
|
|
|
+ mode="single"
|
|
|
+ selected={person.leaveDate ? new Date(person.leaveDate) : undefined}
|
|
|
+ onSelect={(date) => handleUpdateLeaveDate(person.personId, date)}
|
|
|
+ disabled={(date) =>
|
|
|
+ date > new Date() || date < new Date("1900-01-01")
|
|
|
+ }
|
|
|
+ data-testid={`leave-date-calendar-${person.personId}`}
|
|
|
+ />
|
|
|
+ </PopoverContent>
|
|
|
+ </Popover>
|
|
|
</TableCell>
|
|
|
<TableCell>
|
|
|
<Select
|
|
|
@@ -807,9 +892,9 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
onClick={handleAttendanceExport}
|
|
|
variant="outline"
|
|
|
data-testid="order-detail-bottom-attendance-button"
|
|
|
- disabled={!order || order.orderPersons.length === 0}
|
|
|
+ disabled={!order || (order.orderPersons?.length ?? 0) === 0}
|
|
|
>
|
|
|
- <Calendar className="mr-2 h-4 w-4" />
|
|
|
+ <CalendarIcon className="mr-2 h-4 w-4" />
|
|
|
出勤导出
|
|
|
</Button>
|
|
|
</div>
|
|
|
@@ -873,7 +958,7 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
)}
|
|
|
|
|
|
{/* 出勤导出模态框 */}
|
|
|
- {orderId && order && (
|
|
|
+ {orderId && order && order.orderPersons && (
|
|
|
<AttendanceModal
|
|
|
orderId={orderId}
|
|
|
orderName={order.orderName || `订单${orderId}`}
|
|
|
@@ -888,20 +973,6 @@ const OrderDetailModal: React.FC<OrderDetailModalProps> = ({
|
|
|
/>
|
|
|
)}
|
|
|
|
|
|
- {/* 人员日期编辑对话框 */}
|
|
|
- {orderId && editingPerson && (
|
|
|
- <PersonDateEditDialog
|
|
|
- open={isDateEditDialogOpen}
|
|
|
- onOpenChange={setIsDateEditDialogOpen}
|
|
|
- orderId={orderId}
|
|
|
- personId={editingPerson.personId}
|
|
|
- personName={editingPerson.personName}
|
|
|
- initialJoinDate={editingPerson.joinDate}
|
|
|
- initialLeaveDate={editingPerson.leaveDate}
|
|
|
- onSuccess={() => refetch()}
|
|
|
- />
|
|
|
- )}
|
|
|
-
|
|
|
</>
|
|
|
);
|
|
|
};
|