diff --git a/apps/web/src/features/dashboard/AdminDashboard.tsx b/apps/web/src/features/dashboard/AdminDashboard.tsx
index 4f61639c..7f8263bf 100644
--- a/apps/web/src/features/dashboard/AdminDashboard.tsx
+++ b/apps/web/src/features/dashboard/AdminDashboard.tsx
@@ -1,8 +1,336 @@
+import {
+ useListShifts,
+ useListApplications,
+ useListInvoices,
+ useListStaff,
+ useListStaffDocumentsByStatus
+} from '@/dataconnect-generated/react';
+import { useNavigate } from 'react-router-dom';
+import { useState } from 'react';
+import { Card, CardContent, CardHeader, CardTitle } from '@/common/components/ui/card';
+import { Button } from '@/common/components/ui/button';
+import { Alert, AlertDescription, AlertTitle } from '@/common/components/ui/alert';
+import {
+ Clock,
+ FileText,
+ TrendingUp,
+ Users,
+ AlertTriangle,
+ PlusCircle,
+ UserPlus
+} from 'lucide-react';
+import { format } from 'date-fns';
+import { DocumentStatus } from '@/dataconnect-generated';
+import CreateOrderDialog from '@/features/operations/orders/components/CreateOrderDialog';
+import { motion, type Variants } from 'framer-motion';
+
+// Animation variants
+const containerVariants: Variants = {
+ hidden: { opacity: 0 },
+ visible: {
+ opacity: 1,
+ transition: {
+ staggerChildren: 0.1,
+ delayChildren: 0.2
+ }
+ }
+};
+
+const itemVariants: Variants = {
+ hidden: { y: 20, opacity: 0 },
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: {
+ type: "spring",
+ stiffness: 100,
+ damping: 12
+ }
+ }
+};
+
+const headerVariants: Variants = {
+ hidden: { y: -20, opacity: 0 },
+ visible: {
+ y: 0,
+ opacity: 1,
+ transition: {
+ type: "spring",
+ stiffness: 120,
+ damping: 14
+ }
+ }
+};
+
+const alertVariants: Variants = {
+ hidden: { scale: 0.95, opacity: 0 },
+ visible: {
+ scale: 1,
+ opacity: 1,
+ transition: {
+ type: "spring",
+ stiffness: 100,
+ damping: 15
+ }
+ }
+};
const AdminDashboard = () => {
- return (
-
Admin Dashboard
- )
-}
+ const navigate = useNavigate();
+ const [isOrderDialogOpen, setIsOrderDialogOpen] = useState(false);
-export default AdminDashboard
\ No newline at end of file
+ // Data fetching
+ const { data: shiftsData } = useListShifts();
+ const { data: applicationsData } = useListApplications();
+ const { data: invoicesData } = useListInvoices();
+ const { data: staffData } = useListStaff();
+ const { data: complianceData } = useListStaffDocumentsByStatus({ status: DocumentStatus.EXPIRING });
+
+ // Metrics calculation
+ const today = new Date();
+ const todayStart = new Date(today.setHours(0, 0, 0, 0));
+ const todayEnd = new Date(today.setHours(23, 59, 59, 999));
+
+ const activeShiftsToday = shiftsData?.shifts.filter(shift => {
+ if (!shift.startTime) return false;
+ const shiftDate = new Date(shift.startTime);
+ return shiftDate >= todayStart && shiftDate <= todayEnd;
+ }).length || 0;
+
+ const pendingApplications = applicationsData?.applications.filter(app => app.status === 'PENDING').length || 0;
+ // Assuming timesheets are also pending approvals if they exist in a similar way
+ const pendingApprovals = pendingApplications; // Extend if timesheet hook found
+
+ const monthlyRevenue = invoicesData?.invoices
+ .filter(inv => {
+ if (!inv.issueDate) return false;
+ const invDate = new Date(inv.issueDate);
+ return invDate.getMonth() === new Date().getMonth() &&
+ invDate.getFullYear() === new Date().getFullYear();
+ })
+ .reduce((sum, inv) => sum + (inv.amount || 0), 0) || 0;
+
+ const totalStaff = staffData?.staffs.length || 0;
+ const staffUtilization = totalStaff > 0 ?
+ Math.round((activeShiftsToday / totalStaff) * 100) : 0;
+
+ const unfilledPositions = shiftsData?.shifts.filter(shift =>
+ shift.status === 'OPEN' || (shift.workersNeeded || 0) > (shift.filled || 0)
+ ).length || 0;
+ const complianceIssues = complianceData?.staffDocuments.length || 0;
+
+ const metrics = [
+ {
+ title: "Today's Active Shifts",
+ value: activeShiftsToday,
+ description: "Running shifts for today",
+ icon: Clock,
+ color: "text-blue-500"
+ },
+ {
+ title: "Pending Approvals",
+ value: pendingApprovals,
+ description: "Applications & Timesheets",
+ icon: FileText,
+ color: "text-amber-500"
+ },
+ {
+ title: "Monthly Revenue",
+ value: `$${monthlyRevenue.toLocaleString()}`,
+ description: `For ${format(today, 'MMMM yyyy')}`,
+ icon: TrendingUp,
+ color: "text-green-500"
+ },
+ {
+ title: "Staff Utilization",
+ value: `${staffUtilization}%`,
+ description: null,
+ icon: Users,
+ color: "text-purple-500",
+ showProgress: true,
+ progress: staffUtilization
+ }
+ ];
+
+ return (
+
+
+ Admin Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Metrics Widgets */}
+
+ {metrics.map((metric, index) => {
+ const Icon = metric.icon;
+ return (
+
+
+
+
+ {metric.title}
+
+
+
+
+
+
+ {metric.value}
+
+ {metric.description && (
+
+ {metric.description}
+
+ )}
+ {metric.showProgress && (
+
+
+
+ )}
+
+
+
+ );
+ })}
+
+
+ {/* Alerts Section */}
+ {(unfilledPositions > 0 || complianceIssues > 0) && (
+
+
+
+
+
+ Critical Alerts
+
+
+ {unfilledPositions > 0 && (
+
+
+
+ Unfilled Positions
+
+ There are {unfilledPositions} shifts that currently have no staff assigned.
+
+
+
+ )}
+ {complianceIssues > 0 && (
+
+
+
+ Compliance Issues
+
+ {complianceIssues} staff members have expired or missing documentation.
+
+
+
+ )}
+
+
+ )}
+
+ );
+};
+
+export default AdminDashboard;
\ No newline at end of file
diff --git a/apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx b/apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx
new file mode 100644
index 00000000..718e6e82
--- /dev/null
+++ b/apps/web/src/features/operations/orders/components/CreateOrderDialog.tsx
@@ -0,0 +1,132 @@
+import React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/common/components/ui/dialog";
+import EventFormWizard from "./EventFormWizard";
+import { useCreateOrder, useListBusinesses, useListHubs } from "@/dataconnect-generated/react";
+import { OrderType, OrderStatus } from "@/dataconnect-generated";
+import { dataConnect } from "@/features/auth/firebase";
+import { useToast } from "@/common/components/ui/use-toast";
+import { useQueryClient } from "@tanstack/react-query";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/common/components/ui/select";
+import { Label } from "@/common/components/ui/label";
+
+interface CreateOrderDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export default function CreateOrderDialog({ open, onOpenChange }: CreateOrderDialogProps) {
+ const { toast } = useToast();
+ const queryClient = useQueryClient();
+ const [selectedBusinessId, setSelectedBusinessId] = React.useState("");
+ const [selectedHubId, setSelectedHubId] = React.useState("");
+
+ const { data: businessesData } = useListBusinesses(dataConnect);
+ const { data: hubsData } = useListHubs(dataConnect);
+
+ const createOrderMutation = useCreateOrder(dataConnect, {
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ["listOrders"] });
+ toast({
+ title: "✅ Order Created",
+ description: "Your new order has been created successfully.",
+ });
+ onOpenChange(false);
+ },
+ onError: (error) => {
+ toast({
+ title: "❌ Creation Failed",
+ description: error.message || "Could not create the order. Please try again.",
+ variant: "destructive",
+ });
+ },
+ });
+
+ const handleSubmit = (eventData: Record) => {
+ if (!selectedBusinessId || !selectedHubId) {
+ toast({
+ title: "Missing Information",
+ description: "Please select a business and a hub.",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ // 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);
+
+ createOrderMutation.mutate({
+ eventName: eventData.event_name,
+ businessId: selectedBusinessId,
+ teamHubId: selectedHubId,
+ orderType: OrderType.RAPID, // Defaulting to RAPID as per common use in this app
+ 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,
+ status: OrderStatus.POSTED
+ });
+ };
+
+ return (
+
+ );
+}