Files
Krow-workspace/apps/web/src/features/operations/orders/EditOrder.tsx

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>
);
}