232 lines
8.0 KiB
TypeScript
232 lines
8.0 KiB
TypeScript
import { useState, useEffect, useMemo } from "react";
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
import { useNavigate, useParams } from "react-router-dom";
|
|
import { useSelector } from "react-redux";
|
|
import { createPageUrl } from "@/lib/index";
|
|
import { Button } from "@/common/components/ui/button";
|
|
import { Loader2, AlertTriangle } from "lucide-react";
|
|
import DashboardLayout from "@/features/layouts/DashboardLayout";
|
|
import OrderReductionAlert from "./components/OrderReductionAlert";
|
|
import EventFormWizard from "./components/EventFormWizard";
|
|
import { useToast } from "@/common/components/ui/use-toast";
|
|
import { useGetOrderById, useUpdateOrder, useListStaff } from "@/dataconnect-generated/react";
|
|
import { dataConnect } from "@/features/auth/firebase";
|
|
import type { RootState } from "@/store/store";
|
|
|
|
|
|
export default function EditOrder() {
|
|
const navigate = useNavigate();
|
|
const queryClient = useQueryClient();
|
|
const { toast } = useToast();
|
|
const { id: eventId } = useParams<{ id: string }>();
|
|
const { user } = useSelector((state: RootState) => state.auth);
|
|
|
|
const [showReductionAlert, setShowReductionAlert] = useState(false);
|
|
const [pendingUpdate, setPendingUpdate] = useState<any>(null);
|
|
const [originalRequested, setOriginalRequested] = useState(0);
|
|
|
|
const { data: orderData, isLoading: isOrderLoading } = useGetOrderById(
|
|
dataConnect,
|
|
{ id: eventId || "" },
|
|
{ enabled: !!eventId }
|
|
);
|
|
|
|
const event = orderData?.order;
|
|
|
|
const { data: staffData } = useListStaff(dataConnect);
|
|
const allStaff = staffData?.staffs || [];
|
|
|
|
useEffect(() => {
|
|
if (event) {
|
|
setOriginalRequested(event.requested || 0);
|
|
}
|
|
}, [event]);
|
|
|
|
const updateOrderMutation = useUpdateOrder(dataConnect, {
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["listOrders"] });
|
|
queryClient.invalidateQueries({ queryKey: ["getOrderById", { id: eventId }] });
|
|
toast({
|
|
title: "✅ Order Updated",
|
|
description: "Your changes have been saved successfully.",
|
|
});
|
|
navigate(createPageUrl("Events"));
|
|
},
|
|
onError: (error) => {
|
|
toast({
|
|
title: "❌ Update Failed",
|
|
description: error.message || "Could not update the order. Please try again.",
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const canModify = useMemo(() => {
|
|
if (!event) return false;
|
|
if (!event.startDate) return true;
|
|
const startTime = new Date(event.startDate);
|
|
return startTime > new Date();
|
|
}, [event]);
|
|
|
|
const handleSubmit = (eventData: any) => {
|
|
if (!canModify) {
|
|
toast({
|
|
title: "Cannot Edit Order",
|
|
description: "This order has already started and cannot be modified.",
|
|
variant: "destructive",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// CRITICAL: Recalculate requested count from current roles
|
|
const totalRequested = eventData.shifts.reduce((sum: number, shift: any) => {
|
|
const roles = Array.isArray(shift.roles) ? shift.roles : [];
|
|
return sum + roles.reduce((roleSum: number, role: any) => roleSum + (parseInt(role.count) || 0), 0);
|
|
}, 0);
|
|
|
|
const assignedStaff = Array.isArray(event?.assignedStaff) ? event!.assignedStaff : [];
|
|
const assignedCount = assignedStaff.length;
|
|
const isVendor = user?.userRole === 'vendor' || (user as any)?.role === 'vendor';
|
|
|
|
// If client is reducing headcount and vendor has already assigned staff
|
|
if (!isVendor && totalRequested < originalRequested && assignedCount > totalRequested) {
|
|
setPendingUpdate({ ...eventData, requested: totalRequested });
|
|
setShowReductionAlert(true);
|
|
|
|
toast({
|
|
title: "⚠️ Headcount Reduced",
|
|
description: "Assigned staff exceeds new headcount. Please resolve assignments.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (eventId) {
|
|
// Normal update
|
|
updateOrderMutation.mutate({
|
|
id: eventId,
|
|
eventName: eventData.event_name,
|
|
date: eventData.date,
|
|
startDate: eventData.startDate || eventData.date,
|
|
endDate: eventData.endDate,
|
|
notes: eventData.notes,
|
|
shifts: eventData.shifts,
|
|
requested: totalRequested,
|
|
total: eventData.total,
|
|
poReference: eventData.po_reference,
|
|
} as any);
|
|
}
|
|
};
|
|
|
|
const handleAutoUnassign = async () => {
|
|
if (!pendingUpdate || !event || !eventId) return;
|
|
|
|
const assignedStaff = Array.isArray(event.assignedStaff) ? (event.assignedStaff as any[]) : [];
|
|
const excessCount = assignedStaff.length - pendingUpdate.requested;
|
|
|
|
// Calculate reliability scores for assigned staff
|
|
const staffWithScores = assignedStaff.map(assigned => {
|
|
const staffInfo = allStaff.find(s => s.id === assigned.staff_id || s.id === assigned.staffId);
|
|
return {
|
|
...assigned,
|
|
reliability: staffInfo?.averageRating ? staffInfo.averageRating * 20 : 50, // Convert 0-5 to 0-100
|
|
};
|
|
});
|
|
|
|
// Sort by reliability (lowest first)
|
|
staffWithScores.sort((a: any, b: any) => a.reliability - b.reliability);
|
|
|
|
// Remove lowest reliability staff
|
|
const staffToKeep = staffWithScores.slice(excessCount);
|
|
|
|
await updateOrderMutation.mutateAsync({
|
|
id: eventId,
|
|
...pendingUpdate,
|
|
assignedStaff: staffToKeep.map((s: any) => ({
|
|
staffId: s.staffId || s.staff_id,
|
|
staffName: s.staffName || s.staff_name,
|
|
role: s.role
|
|
}))
|
|
} as any);
|
|
|
|
setShowReductionAlert(false);
|
|
setPendingUpdate(null);
|
|
|
|
toast({
|
|
title: "✅ Staff Auto-Unassigned",
|
|
description: `Removed ${excessCount} lowest reliability staff members`,
|
|
});
|
|
};
|
|
|
|
const handleManualUnassign = () => {
|
|
setShowReductionAlert(false);
|
|
toast({
|
|
title: "Manual Adjustment Required",
|
|
description: "Please manually remove excess staff from the order",
|
|
});
|
|
};
|
|
|
|
if (isOrderLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!event) {
|
|
return (
|
|
<div className="p-12 text-center">
|
|
<h2 className="text-2xl font-bold text-primary-text mb-4">Event Not Found</h2>
|
|
<Button onClick={() => navigate(createPageUrl("Events"))}>
|
|
Back to Events
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<DashboardLayout
|
|
title={`Edit ${event.eventName || "Order"}`}
|
|
subtitle="Update information for your order"
|
|
>
|
|
<div className="max-w-7xl mx-auto">
|
|
{!canModify && (
|
|
<div className="mb-6 p-4 bg-amber-50 border border-amber-200 rounded-xl flex items-center gap-3 text-amber-800">
|
|
<AlertTriangle className="w-5 h-5 flex-shrink-0" />
|
|
<p className="text-sm font-medium">
|
|
This order has already started. Some details may no longer be editable for security and tracking purposes.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{showReductionAlert && pendingUpdate && (
|
|
<div className="mb-6">
|
|
<OrderReductionAlert
|
|
originalRequested={originalRequested}
|
|
newRequested={pendingUpdate.requested}
|
|
currentAssigned={Array.isArray(event.assignedStaff) ? event.assignedStaff.length : 0}
|
|
onAutoUnassign={handleAutoUnassign}
|
|
onManualUnassign={handleManualUnassign}
|
|
lowReliabilityStaff={(Array.isArray(event.assignedStaff) ? event.assignedStaff : []).map((assigned: any) => {
|
|
const staffInfo = allStaff.find(s => s.id === assigned.staffId || s.id === assigned.staff_id);
|
|
return {
|
|
name: assigned.staffName || assigned.staff_name,
|
|
reliability: staffInfo?.averageRating ? staffInfo.averageRating * 20 : 50
|
|
};
|
|
}).sort((a, b) => a.reliability - b.reliability)}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<EventFormWizard
|
|
event={event as any}
|
|
onSubmit={handleSubmit}
|
|
isSubmitting={updateOrderMutation.isPending}
|
|
currentUser={user as any}
|
|
onCancel={() => navigate(createPageUrl("Events"))}
|
|
/>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
}
|