Files
Krow-workspace/frontend-web/src/pages/EventDetail.jsx
2025-11-18 21:32:16 -05:00

363 lines
14 KiB
JavaScript

import React, { useState } from "react";
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Link, useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { ArrowLeft, Calendar, MapPin, Users, DollarSign, Send, Edit3, X, AlertTriangle } from "lucide-react";
import ShiftCard from "@/components/events/ShiftCard";
import OrderStatusBadge from "@/components/orders/OrderStatusBadge";
import { useToast } from "@/components/ui/use-toast";
import { format } from "date-fns";
const safeFormatDate = (dateString) => {
if (!dateString) return "—";
try {
return format(new Date(dateString), "MMMM d, yyyy");
} catch {
return "—";
}
};
export default function EventDetail() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { toast } = useToast();
const [notifyDialog, setNotifyDialog] = useState(false);
const [cancelDialog, setCancelDialog] = useState(false);
const urlParams = new URLSearchParams(window.location.search);
const eventId = urlParams.get("id");
const { data: user } = useQuery({
queryKey: ['current-user-event-detail'],
queryFn: () => base44.auth.me(),
});
const { data: allEvents, isLoading } = useQuery({
queryKey: ['events'],
queryFn: () => base44.entities.Event.list(),
initialData: [],
});
const event = allEvents.find(e => e.id === eventId);
// Cancel order mutation
const cancelOrderMutation = useMutation({
mutationFn: () => base44.entities.Event.update(eventId, { status: "Canceled" }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['events'] });
queryClient.invalidateQueries({ queryKey: ['all-events-client'] });
toast({
title: "✅ Order Canceled",
description: "Your order has been canceled successfully",
});
setCancelDialog(false);
navigate(createPageUrl("ClientOrders"));
},
onError: () => {
toast({
title: "❌ Failed to Cancel",
description: "Could not cancel order. Please try again.",
variant: "destructive",
});
},
});
const handleNotifyStaff = async () => {
const assignedStaff = event?.assigned_staff || [];
for (const staff of assignedStaff) {
try {
await base44.integrations.Core.SendEmail({
to: staff.email || `${staff.staff_name}@example.com`,
subject: `Shift Update: ${event.event_name}`,
body: `You have an update for: ${event.event_name}\nDate: ${event.date}\nLocation: ${event.event_location || event.hub}\n\nPlease check the platform for details.`
});
} catch (error) {
console.error("Failed to send email:", error);
}
}
toast({
title: "✅ Notifications Sent",
description: `Notified ${assignedStaff.length} staff members`,
});
setNotifyDialog(false);
};
const isClient = user?.user_role === 'client' ||
event?.created_by === user?.email ||
event?.client_email === user?.email;
const canEditOrder = () => {
if (!event) return false;
const eventDate = new Date(event.date);
const now = new Date();
return isClient &&
event.status !== "Completed" &&
event.status !== "Canceled" &&
eventDate > now;
};
const canCancelOrder = () => {
if (!event) return false;
return isClient &&
event.status !== "Completed" &&
event.status !== "Canceled";
};
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600" />
</div>
);
}
if (!event) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<p className="text-xl font-semibold text-slate-900 mb-4">Event not found</p>
<Link to={createPageUrl("Events")}>
<Button variant="outline">Back to Events</Button>
</Link>
</div>
);
}
// Get shifts from event.shifts array (primary source)
const eventShifts = event.shifts || [];
return (
<div className="p-4 md:p-8">
<div className="max-w-7xl mx-auto space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="icon"
onClick={() => navigate(-1)}
>
<ArrowLeft className="w-5 h-5" />
</Button>
<div>
<h1 className="text-3xl font-bold text-slate-900">{event.event_name}</h1>
<p className="text-slate-600 mt-1">Order Details & Information</p>
</div>
</div>
<div className="flex items-center gap-3">
<OrderStatusBadge order={event} />
{canEditOrder() && (
<button
onClick={() => navigate(createPageUrl(`EditEvent?id=${event.id}`))}
className="flex items-center gap-2 px-5 py-2.5 bg-white hover:bg-blue-50 border-2 border-blue-200 rounded-full text-blue-600 font-semibold text-base transition-all shadow-md hover:shadow-lg"
>
<Edit3 className="w-5 h-5" />
Edit
</button>
)}
{canCancelOrder() && (
<button
onClick={() => setCancelDialog(true)}
className="flex items-center gap-2 px-5 py-2.5 bg-white hover:bg-red-50 border-2 border-red-200 rounded-full text-red-600 font-semibold text-base transition-all shadow-md hover:shadow-lg"
>
<X className="w-5 h-5" />
cancel
</button>
)}
{!isClient && event.assigned_staff?.length > 0 && (
<Button
onClick={() => setNotifyDialog(true)}
className="bg-blue-600 hover:bg-blue-700"
>
<Send className="w-4 h-4 mr-2" />
Notify Staff
</Button>
)}
</div>
</div>
{/* Order Details Card */}
<Card className="bg-white border border-slate-200 shadow-md">
<CardHeader className="border-b border-slate-100">
<CardTitle className="text-lg font-bold text-slate-900">Order Information</CardTitle>
</CardHeader>
<CardContent className="p-6">
<div className="grid grid-cols-4 gap-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-50 rounded-lg flex items-center justify-center">
<Calendar className="w-5 h-5 text-blue-600" />
</div>
<div>
<p className="text-xs text-slate-500">Event Date</p>
<p className="font-bold text-slate-900">{safeFormatDate(event.date)}</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-purple-50 rounded-lg flex items-center justify-center">
<MapPin className="w-5 h-5 text-purple-600" />
</div>
<div>
<p className="text-xs text-slate-500">Location</p>
<p className="font-bold text-slate-900">{event.hub || event.event_location || "—"}</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-green-50 rounded-lg flex items-center justify-center">
<Users className="w-5 h-5 text-green-600" />
</div>
<div>
<p className="text-xs text-slate-500">Staff Assigned</p>
<p className="font-bold text-slate-900">
{event.assigned_staff?.length || 0} / {event.requested || 0}
</p>
</div>
</div>
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-amber-50 rounded-lg flex items-center justify-center">
<DollarSign className="w-5 h-5 text-amber-600" />
</div>
<div>
<p className="text-xs text-slate-500">Total Cost</p>
<p className="font-bold text-slate-900">${(event.total || 0).toLocaleString()}</p>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Client Information (if not client viewing) */}
{!isClient && (
<Card className="bg-white border border-slate-200 shadow-md">
<CardHeader className="border-b border-slate-100">
<CardTitle className="text-lg font-bold text-slate-900">Client Information</CardTitle>
</CardHeader>
<CardContent className="p-6">
<div className="grid grid-cols-3 gap-6">
<div>
<p className="text-xs text-slate-500 mb-1">Business Name</p>
<p className="font-bold text-slate-900">{event.business_name || "—"}</p>
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Contact Name</p>
<p className="font-bold text-slate-900">{event.client_name || "—"}</p>
</div>
<div>
<p className="text-xs text-slate-500 mb-1">Contact Email</p>
<p className="font-bold text-slate-900">{event.client_email || "—"}</p>
</div>
</div>
</CardContent>
</Card>
)}
{/* Shifts - Using event.shifts array */}
<div className="space-y-4">
<h2 className="text-xl font-bold text-slate-900">Event Shifts & Staff Assignment</h2>
{eventShifts.length > 0 ? (
eventShifts.map((shift, idx) => (
<ShiftCard key={idx} shift={shift} event={event} />
))
) : (
<Card className="bg-white border border-slate-200">
<CardContent className="p-12 text-center">
<Users className="w-12 h-12 mx-auto mb-4 text-slate-400" />
<p className="text-slate-600 font-medium mb-2">No shifts defined for this event</p>
<p className="text-slate-500 text-sm">Add roles and staff requirements to get started</p>
</CardContent>
</Card>
)}
</div>
{/* Notes */}
{event.notes && (
<Card className="bg-white border border-slate-200 shadow-md">
<CardHeader className="border-b border-slate-100">
<CardTitle className="text-lg font-bold text-slate-900">Additional Notes</CardTitle>
</CardHeader>
<CardContent className="p-6">
<p className="text-slate-700 whitespace-pre-wrap">{event.notes}</p>
</CardContent>
</Card>
)}
</div>
{/* Notify Staff Dialog */}
<Dialog open={notifyDialog} onOpenChange={setNotifyDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle>Notify Assigned Staff</DialogTitle>
<DialogDescription>
Send notification to all {event.assigned_staff?.length || 0} assigned staff members about this event.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setNotifyDialog(false)}>
Cancel
</Button>
<Button onClick={handleNotifyStaff} className="bg-blue-600 hover:bg-blue-700">
<Send className="w-4 h-4 mr-2" />
Send Notifications
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Cancel Order Dialog */}
<Dialog open={cancelDialog} onOpenChange={setCancelDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2 text-red-600">
<AlertTriangle className="w-5 h-5" />
Cancel Order?
</DialogTitle>
<DialogDescription>
Are you sure you want to cancel this order? This action cannot be undone and the vendor will be notified immediately.
</DialogDescription>
</DialogHeader>
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
<p className="font-bold text-slate-900">{event.event_name}</p>
<div className="flex items-center gap-2 text-sm text-slate-600">
<Calendar className="w-4 h-4" />
{safeFormatDate(event.date)}
</div>
<div className="flex items-center gap-2 text-sm text-slate-600">
<MapPin className="w-4 h-4" />
{event.hub || event.event_location}
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setCancelDialog(false)}
>
Keep Order
</Button>
<Button
variant="destructive"
onClick={() => cancelOrderMutation.mutate()}
disabled={cancelOrderMutation.isPending}
>
{cancelOrderMutation.isPending ? "Canceling..." : "Yes, Cancel Order"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}