feat: Initialize monorepo structure and comprehensive documentation
This commit establishes the new monorepo architecture for the KROW Workforce platform. Key changes include: - Reorganized project into `frontend-web`, `mobile-apps`, `firebase`, `scripts`, and `secrets` directories. - Updated `Makefile` to support the new monorepo layout and automate Base44 export integration. - Fixed `scripts/prepare-export.js` for ES module compatibility and global component import resolution. - Created and updated `CONTRIBUTING.md` for developer onboarding. - Restructured, renamed, and translated all `docs/` files for clarity and consistency. - Implemented an interactive internal launchpad with diagram viewing capabilities. - Configured base Firebase project files (`firebase.json`, security rules). - Updated `README.md` to reflect the new project structure and documentation overview.
This commit is contained in:
282
frontend-web/src/pages/ClientOrders.jsx
Normal file
282
frontend-web/src/pages/ClientOrders.jsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import React, { useState } from "react";
|
||||
import { base44 } from "@/api/base44Client";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Calendar as CalendarIcon, MapPin, Users, Clock, DollarSign, FileText, Plus, RefreshCw, Zap } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { createPageUrl } from "@/utils";
|
||||
import { format, addDays } from "date-fns";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import QuickReorderModal from "../components/events/QuickReorderModal";
|
||||
|
||||
export default function ClientOrders() {
|
||||
const navigate = useNavigate();
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
const [reorderModalOpen, setReorderModalOpen] = useState(false);
|
||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const { data: user } = useQuery({
|
||||
queryKey: ['current-user'],
|
||||
queryFn: () => base44.auth.me(),
|
||||
});
|
||||
|
||||
const { data: events } = useQuery({
|
||||
queryKey: ['client-events'],
|
||||
queryFn: () => base44.entities.Event.list('-date'),
|
||||
initialData: [],
|
||||
});
|
||||
|
||||
// Filter events by current client
|
||||
const clientEvents = events.filter(e =>
|
||||
e.client_email === user?.email || e.created_by === user?.email
|
||||
);
|
||||
|
||||
const filteredEvents = statusFilter === "all"
|
||||
? clientEvents
|
||||
: clientEvents.filter(e => {
|
||||
if (statusFilter === "rapid_request") return e.is_rapid_request;
|
||||
if (statusFilter === "pending") return e.status?.toLowerCase() === "pending" || e.status?.toLowerCase() === "draft";
|
||||
return e.status?.toLowerCase() === statusFilter;
|
||||
});
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const colors = {
|
||||
'pending': 'bg-yellow-100 text-yellow-700',
|
||||
'draft': 'bg-gray-100 text-gray-700',
|
||||
'confirmed': 'bg-green-100 text-green-700',
|
||||
'active': 'bg-blue-100 text-blue-700',
|
||||
'completed': 'bg-slate-100 text-slate-700',
|
||||
'canceled': 'bg-red-100 text-red-700',
|
||||
'cancelled': 'bg-red-100 text-red-700',
|
||||
};
|
||||
return colors[status?.toLowerCase()] || 'bg-slate-100 text-slate-700';
|
||||
};
|
||||
|
||||
const stats = {
|
||||
total: clientEvents.length,
|
||||
rapidRequest: clientEvents.filter(e => e.is_rapid_request).length,
|
||||
pending: clientEvents.filter(e => e.status === 'Pending' || e.status === 'Draft').length,
|
||||
confirmed: clientEvents.filter(e => e.status === 'Confirmed').length,
|
||||
completed: clientEvents.filter(e => e.status === 'Completed').length,
|
||||
};
|
||||
|
||||
const handleQuickReorder = (event) => {
|
||||
setSelectedEvent(event);
|
||||
setReorderModalOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-[#1C323E]">My Orders</h1>
|
||||
<p className="text-slate-500 mt-1">View and manage your event orders</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => navigate(createPageUrl("CreateEvent"))}
|
||||
className="bg-[#0A39DF] hover:bg-[#0A39DF]/90"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
New Order
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-6 mb-8">
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<FileText className="w-8 h-8 text-[#0A39DF]" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Total Orders</p>
|
||||
<p className="text-3xl font-bold text-[#1C323E]">{stats.total}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200 bg-gradient-to-br from-red-50 to-white">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Zap className="w-8 h-8 text-red-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Rapid Requests</p>
|
||||
<p className="text-3xl font-bold text-red-600">{stats.rapidRequest}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Clock className="w-8 h-8 text-yellow-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Pending</p>
|
||||
<p className="text-3xl font-bold text-yellow-600">{stats.pending}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<CalendarIcon className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Confirmed</p>
|
||||
<p className="text-3xl font-bold text-green-600">{stats.confirmed}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Users className="w-8 h-8 text-blue-600" />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500">Completed</p>
|
||||
<p className="text-3xl font-bold text-blue-600">{stats.completed}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Filter Tabs */}
|
||||
<div className="flex gap-2 mb-6 flex-wrap">
|
||||
<Button
|
||||
variant={statusFilter === "all" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("all")}
|
||||
className={statusFilter === "all" ? "bg-[#0A39DF]" : ""}
|
||||
>
|
||||
All
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "rapid_request" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("rapid_request")}
|
||||
className={statusFilter === "rapid_request" ? "bg-red-600 hover:bg-red-700" : ""}
|
||||
>
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
Rapid Request
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "pending" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("pending")}
|
||||
className={statusFilter === "pending" ? "bg-[#0A39DF]" : ""}
|
||||
>
|
||||
Pending
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "confirmed" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("confirmed")}
|
||||
className={statusFilter === "confirmed" ? "bg-[#0A39DF]" : ""}
|
||||
>
|
||||
Confirmed
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === "completed" ? "default" : "outline"}
|
||||
onClick={() => setStatusFilter("completed")}
|
||||
className={statusFilter === "completed" ? "bg-[#0A39DF]" : ""}
|
||||
>
|
||||
Completed
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Orders List */}
|
||||
<div className="grid grid-cols-1 gap-6">
|
||||
{filteredEvents.length > 0 ? (
|
||||
filteredEvents.map((event) => (
|
||||
<Card key={event.id} className="border-slate-200 hover:shadow-lg transition-shadow">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<h3 className="text-xl font-bold text-[#1C323E]">{event.event_name}</h3>
|
||||
<Badge className={getStatusColor(event.status)}>
|
||||
{event.status}
|
||||
</Badge>
|
||||
{event.is_rapid_request && (
|
||||
<Badge className="bg-red-100 text-red-700 border-red-200 border">
|
||||
<Zap className="w-3 h-3 mr-1" />
|
||||
Rapid Request
|
||||
</Badge>
|
||||
)}
|
||||
{event.include_backup && (
|
||||
<Badge className="bg-green-100 text-green-700 border-green-200 border">
|
||||
🛡️ {event.backup_staff_count || 0} Backup Staff
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm text-slate-600 mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarIcon className="w-4 h-4" />
|
||||
<span>{event.date ? format(new Date(event.date), 'PPP') : 'Date TBD'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>{event.event_location || event.hub || 'Location TBD'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4" />
|
||||
<span>{event.assigned || 0} of {event.requested || 0} staff</span>
|
||||
</div>
|
||||
{event.total && (
|
||||
<div className="flex items-center gap-2">
|
||||
<DollarSign className="w-4 h-4" />
|
||||
<span className="font-semibold">${event.total.toLocaleString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => navigate(createPageUrl("EventDetail") + `?id=${event.id}`)}
|
||||
variant="outline"
|
||||
className="hover:bg-[#0A39DF] hover:text-white"
|
||||
>
|
||||
View Details
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleQuickReorder(event)}
|
||||
className="bg-gradient-to-r from-green-600 to-green-700 hover:from-green-700 hover:to-green-800 text-white shadow-lg"
|
||||
size="lg"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5 mr-2" />
|
||||
Reorder
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{event.notes && (
|
||||
<div className="mt-3 p-3 bg-slate-50 rounded-lg">
|
||||
<p className="text-sm text-slate-600">{event.notes}</p>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
) : (
|
||||
<Card className="border-slate-200">
|
||||
<CardContent className="p-12 text-center">
|
||||
<FileText className="w-16 h-16 mx-auto text-slate-300 mb-4" />
|
||||
<h3 className="text-lg font-semibold text-slate-700 mb-2">No orders found</h3>
|
||||
<p className="text-slate-500 mb-6">Get started by creating your first order</p>
|
||||
<Button
|
||||
onClick={() => navigate(createPageUrl("CreateEvent"))}
|
||||
className="bg-[#0A39DF] hover:bg-[#0A39DF]/90"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Create Order
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Quick Reorder Modal */}
|
||||
{selectedEvent && (
|
||||
<QuickReorderModal
|
||||
event={selectedEvent}
|
||||
open={reorderModalOpen}
|
||||
onOpenChange={setReorderModalOpen}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user