import React, { useEffect } from "react"; import { base44 } from "@/api/base44Client"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/components/ui/use-toast"; import { hasTimeOverlap, checkDoubleBooking } from "./SmartAssignmentEngine"; import { format, addDays } from "date-fns"; /** * Automation Engine * Handles background automations to reduce manual work */ export function AutomationEngine() { const queryClient = useQueryClient(); const { toast } = useToast(); const { data: events } = useQuery({ queryKey: ['events-automation'], queryFn: () => base44.entities.Event.list(), initialData: [], refetchInterval: 30000, // Check every 30s }); const { data: allStaff } = useQuery({ queryKey: ['staff-automation'], queryFn: () => base44.entities.Staff.list(), initialData: [], refetchInterval: 60000, }); const { data: existingInvoices } = useQuery({ queryKey: ['invoices-automation'], queryFn: () => base44.entities.Invoice.list(), initialData: [], refetchInterval: 60000, }); // Auto-create invoice when event is marked as Completed useEffect(() => { const autoCreateInvoices = async () => { const completedEvents = events.filter(e => e.status === 'Completed' && !e.invoice_id && !existingInvoices.some(inv => inv.event_id === e.id) ); for (const event of completedEvents) { try { const invoiceNumber = `INV-${format(new Date(), 'yyMMddHHmmss')}`; const issueDate = format(new Date(), 'yyyy-MM-dd'); const dueDate = format(addDays(new Date(), 30), 'yyyy-MM-dd'); // Net 30 const invoice = await base44.entities.Invoice.create({ invoice_number: invoiceNumber, event_id: event.id, event_name: event.event_name, business_name: event.business_name || event.client_name, vendor_name: event.vendor_name, manager_name: event.client_name, hub: event.hub, cost_center: event.cost_center, amount: event.total || 0, item_count: event.assigned_staff?.length || 0, status: 'Open', issue_date: issueDate, due_date: dueDate, notes: `Auto-generated invoice for completed event: ${event.event_name}` }); // Update event with invoice_id await base44.entities.Event.update(event.id, { invoice_id: invoice.id }); queryClient.invalidateQueries({ queryKey: ['invoices'] }); queryClient.invalidateQueries({ queryKey: ['events'] }); } catch (error) { console.error('Auto-invoice creation failed:', error); } } }; if (events.length > 0) { autoCreateInvoices(); } }, [events, existingInvoices, queryClient]); // Auto-confirm workers (24 hours before shift) useEffect(() => { const autoConfirmWorkers = async () => { const now = new Date(); const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000); const upcomingEvents = events.filter(e => { const eventDate = new Date(e.date); return eventDate >= now && eventDate <= tomorrow && e.status === 'Assigned'; }); for (const event of upcomingEvents) { if (event.assigned_staff?.length > 0) { try { await base44.entities.Event.update(event.id, { status: 'Confirmed' }); // Send confirmation emails for (const staff of event.assigned_staff) { await base44.integrations.Core.SendEmail({ to: staff.email, subject: `Shift Confirmed - ${event.event_name}`, body: `Your shift at ${event.event_name} on ${event.date} has been confirmed. See you there!` }); } } catch (error) { console.error('Auto-confirm failed:', error); } } } }; if (events.length > 0) { autoConfirmWorkers(); } }, [events]); // Auto-send reminders (2 hours before shift) useEffect(() => { const sendReminders = async () => { const now = new Date(); const twoHoursLater = new Date(now.getTime() + 2 * 60 * 60 * 1000); const upcomingEvents = events.filter(e => { const eventDate = new Date(e.date); return eventDate >= now && eventDate <= twoHoursLater; }); for (const event of upcomingEvents) { if (event.assigned_staff?.length > 0 && event.status === 'Confirmed') { for (const staff of event.assigned_staff) { try { await base44.integrations.Core.SendEmail({ to: staff.email, subject: `Reminder: Your shift starts in 2 hours`, body: `Reminder: Your shift at ${event.event_name} starts in 2 hours. Location: ${event.event_location || event.hub}` }); } catch (error) { console.error('Reminder failed:', error); } } } } }; if (events.length > 0) { sendReminders(); } }, [events]); // Auto-detect overlapping shifts useEffect(() => { const detectOverlaps = () => { const conflicts = []; allStaff.forEach(staff => { const staffEvents = events.filter(e => e.assigned_staff?.some(s => s.staff_id === staff.id) ); for (let i = 0; i < staffEvents.length; i++) { for (let j = i + 1; j < staffEvents.length; j++) { const e1 = staffEvents[i]; const e2 = staffEvents[j]; const d1 = new Date(e1.date); const d2 = new Date(e2.date); if (d1.toDateString() === d2.toDateString()) { const shift1 = e1.shifts?.[0]?.roles?.[0]; const shift2 = e2.shifts?.[0]?.roles?.[0]; if (shift1 && shift2 && hasTimeOverlap(shift1, shift2)) { conflicts.push({ staff: staff.employee_name, event1: e1.event_name, event2: e2.event_name, date: e1.date }); } } } } }); if (conflicts.length > 0) { toast({ title: `⚠️ ${conflicts.length} Double-Booking Detected`, description: `${conflicts[0].staff} has overlapping shifts`, variant: "destructive", }); } }; if (events.length > 0 && allStaff.length > 0) { detectOverlaps(); } }, [events, allStaff]); return null; // Background service } export default AutomationEngine;