From 475cc8ae8016a8f2e7ffdd933599a4904104d7be Mon Sep 17 00:00:00 2001 From: dhinesh-m24 Date: Thu, 5 Feb 2026 16:50:16 +0530 Subject: [PATCH] feat: Implement Order List View for Vendors --- .../operations/orders/ClientOrderList.tsx | 9 +- .../operations/orders/VendorOrderList.tsx | 248 ++++++++++++++++++ apps/web/src/routes.tsx | 4 +- 3 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/features/operations/orders/VendorOrderList.tsx diff --git a/apps/web/src/features/operations/orders/ClientOrderList.tsx b/apps/web/src/features/operations/orders/ClientOrderList.tsx index 2ee50b7a..c70c0cd7 100644 --- a/apps/web/src/features/operations/orders/ClientOrderList.tsx +++ b/apps/web/src/features/operations/orders/ClientOrderList.tsx @@ -70,7 +70,7 @@ export default function ClientOrderList() { const lower = searchTerm.toLowerCase(); filtered = filtered.filter(o => o.eventName?.toLowerCase().includes(lower) || - o.teamHub?.hubName?.toLowerCase().includes(lower) + o.business.businessName.toLowerCase().includes(lower) ); } @@ -79,7 +79,7 @@ export default function ClientOrderList() { } if (locationFilter !== "all") { - filtered = filtered.filter(o => o.teamHub?.hubName === locationFilter); + filtered = filtered.filter(o => o.business.businessName === locationFilter); } return filtered; @@ -88,7 +88,8 @@ export default function ClientOrderList() { const uniqueLocations = useMemo(() => { const locations = new Set(); orders.forEach(o => { - if (o.teamHub?.hubName) locations.add(o.teamHub.hubName); + const businessName = o.business.businessName; + if (businessName) locations.add(businessName); }); return Array.from(locations).sort(); }, [orders]); @@ -274,7 +275,7 @@ export default function ClientOrderList() { {order.eventName}
- {order.teamHub?.hubName || "No location"} + {order.business.businessName || "No location"}
diff --git a/apps/web/src/features/operations/orders/VendorOrderList.tsx b/apps/web/src/features/operations/orders/VendorOrderList.tsx new file mode 100644 index 00000000..97f4c9d9 --- /dev/null +++ b/apps/web/src/features/operations/orders/VendorOrderList.tsx @@ -0,0 +1,248 @@ +import { useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { format, parseISO, isValid } from "date-fns"; +import { Search, MapPin } from "lucide-react"; + +import { Card, CardContent } from "@/common/components/ui/card"; +import { Input } from "@/common/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/common/components/ui/table"; +import DashboardLayout from "@/features/layouts/DashboardLayout"; +import { useSelector } from "react-redux"; +import type { RootState } from "@/store/store"; +import { useListOrders, useGetVendorByUserId } from "@/dataconnect-generated/react"; +import { dataConnect } from "@/features/auth/firebase"; + +const safeParseDate = (dateString: any): Date | null => { + if (!dateString) return null; + try { + const date = typeof dateString === "string" ? parseISO(dateString) : new Date(dateString); + return isValid(date) ? date : null; + } catch { + return null; + } +}; + +const hasVendorAssignments = (order: any, vendorId?: string | null) => { + if (!vendorId) return false; + const assignedStaff = order?.assignedStaff; + if (!Array.isArray(assignedStaff)) return false; + + return assignedStaff.some((assignment: any) => { + const assignmentVendorId = + assignment?.vendorId || + assignment?.vendor_id || + assignment?.vendor?.id || + null; + return assignmentVendorId === vendorId; + }); +}; + +const getVendorPositionsSummary = (order: any, vendorId?: string | null) => { + if (!vendorId) return "—"; + const assignedStaff = order?.assignedStaff; + if (!Array.isArray(assignedStaff)) return "—"; + + const vendorAssignments = assignedStaff.filter((assignment: any) => { + const assignmentVendorId = + assignment?.vendorId || + assignment?.vendor_id || + assignment?.vendor?.id || + null; + return assignmentVendorId === vendorId; + }); + + if (vendorAssignments.length === 0) return "—"; + + const positions = Array.from( + new Set( + vendorAssignments + .map((assignment: any) => assignment.positionName || assignment.roleName || assignment.position || null) + .filter(Boolean), + ), + ) as string[]; + + if (positions.length === 0) return `${vendorAssignments.length} staff`; + + return positions.join(", "); +}; + +const getEstimatedRevenue = (order: any, vendorId?: string | null) => { + if (!vendorId) return "—"; + const assignedStaff = order?.assignedStaff; + if (!Array.isArray(assignedStaff)) return "—"; + + const vendorAssignments = assignedStaff.filter((assignment: any) => { + const assignmentVendorId = + assignment?.vendorId || + assignment?.vendor_id || + assignment?.vendor?.id || + null; + return assignmentVendorId === vendorId; + }); + + if (vendorAssignments.length === 0) return "—"; + + const total = vendorAssignments.reduce((sum: number, assignment: any) => { + const hours = Number( + assignment?.estimatedHours ?? assignment?.hours ?? assignment?.shiftHours ?? 0, + ); + const rate = Number( + assignment?.vendorBillRate ?? assignment?.billRate ?? assignment?.rate ?? 0, + ); + + if (!Number.isFinite(hours) || !Number.isFinite(rate)) return sum; + return sum + hours * rate; + }, 0); + + if (!Number.isFinite(total) || total <= 0) return "—"; + + return `$${Math.round(total).toLocaleString()}`; +}; + +export default function VendorOrderList() { + const navigate = useNavigate(); + const { user } = useSelector((state: RootState) => state.auth); + + const [searchTerm, setSearchTerm] = useState(""); + + // 1. Resolve the logged-in vendor from the current user + const { data: vendorData } = useGetVendorByUserId( + { userId: user?.uid || "" }, + { enabled: !!user?.uid }, + ); + + const vendor = vendorData?.vendors?.[0]; + const vendorId: string | null = vendor?.id ?? null; + + // 2. Load all orders from Data Connect + const { data: orderData, isLoading } = useListOrders(dataConnect); + const orders = orderData?.orders || []; + + // 3. Filter to only orders where this vendor has assigned staff + const vendorOrders = useMemo(() => { + if (!vendorId) return []; + return orders.filter((order: any) => hasVendorAssignments(order, vendorId)); + }, [orders, vendorId]); + + // 4. Apply search filter (Order #, Client, Event) + const filteredOrders = useMemo(() => { + const lowerSearch = searchTerm.trim().toLowerCase(); + if (!lowerSearch) return vendorOrders; + + return vendorOrders.filter((order: any) => { + const orderId = order.id?.toString().toLowerCase() ?? ""; + const eventName = order.eventName?.toLowerCase?.() ?? ""; + const clientName = order.business?.businessName?.toLowerCase?.() ?? ""; + + return ( + orderId.includes(lowerSearch) || + eventName.includes(lowerSearch) || + clientName.includes(lowerSearch) + ); + }); + }, [vendorOrders, searchTerm]); + + return ( + +
+ {/* Search */} + + +
+ + setSearchTerm(e.target.value)} + className="pl-9" + /> +
+
+
+ + {/* Orders Table */} +
+ + + + Order # + Client + Event Date + Your Positions + Estimated Revenue + + + + {isLoading ? ( + + + Loading vendor orders... + + + ) : filteredOrders.length === 0 ? ( + + + {vendorId + ? "No orders found where your team is assigned." + : "No vendor profile found for this user."} + + + ) : ( + filteredOrders.map((order: any) => { + const eventDate = safeParseDate(order.date); + const positionsSummary = getVendorPositionsSummary(order, vendorId); + const estimatedRevenue = getEstimatedRevenue(order, vendorId); + + return ( + navigate(`/orders/${order.id}`)} + > + + {order.id?.toString().substring(0, 8)} + + + {order.business?.businessName || "Unknown client"} +
+ + {order.business?.businessName || "No location"} +
+
+ +
+ + {eventDate ? format(eventDate, "MMM dd, yyyy") : "No date"} + + + {eventDate ? format(eventDate, "EEEE") : ""} + +
+
+ + {positionsSummary} + + + {estimatedRevenue} + +
+ ); + }) + )} +
+
+
+
+
+ ); +} diff --git a/apps/web/src/routes.tsx b/apps/web/src/routes.tsx index bfadf1a6..be528ecf 100644 --- a/apps/web/src/routes.tsx +++ b/apps/web/src/routes.tsx @@ -19,6 +19,7 @@ import ServiceRates from './features/business/rates/ServiceRates'; import OrderList from './features/operations/orders/OrderList'; import OrderDetail from './features/operations/orders/OrderDetail'; import ClientOrderList from './features/operations/orders/ClientOrderList'; +import VendorOrderList from './features/operations/orders/VendorOrderList'; /** * AppRoutes Component @@ -97,7 +98,8 @@ const AppRoutes: React.FC = () => { } /> } /> } /> - + } /> + } />