'use client' import React, { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { Loader2, LogOut, Sun, Moon, Sunrise, ClipboardList, Users, Settings, Plus, Pencil, Eye, Send, Check, ChefHat, AlertTriangle, ChevronLeft, ChevronRight, Calendar, UserCheck, UserX, } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Progress } from '@/components/ui/progress' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' interface User { id: number name?: string email: string tenants?: Array<{ tenant: { id: number; name: string } | number roles?: string[] }> } interface MealOrder { id: number title: string date: string mealType: 'breakfast' | 'lunch' | 'dinner' status: 'draft' | 'submitted' | 'preparing' | 'completed' mealCount: number createdAt: string } interface Resident { id: number name: string room: string } interface Meal { id: number resident: number | { id: number; name: string } } interface OrderStats { draft: number submitted: number preparing: number completed: number total: number } interface PaginationInfo { totalDocs: number totalPages: number page: number limit: number hasNextPage: boolean hasPrevPage: boolean } const ITEMS_PER_PAGE = 10 export default function CaregiverDashboardPage() { const router = useRouter() const [user, setUser] = useState(null) const [orders, setOrders] = useState([]) const [residents, setResidents] = useState([]) const [stats, setStats] = useState({ draft: 0, submitted: 0, preparing: 0, completed: 0, total: 0, }) const [pagination, setPagination] = useState({ totalDocs: 0, totalPages: 1, page: 1, limit: ITEMS_PER_PAGE, hasNextPage: false, hasPrevPage: false, }) const [loading, setLoading] = useState(true) const [ordersLoading, setOrdersLoading] = useState(false) // Filters const [dateFilter, setDateFilter] = useState('') const [statusFilter, setStatusFilter] = useState('all') // Coverage dialog const [coverageDialogOpen, setCoverageDialogOpen] = useState(false) const [selectedOrder, setSelectedOrder] = useState(null) const [orderMeals, setOrderMeals] = useState([]) const [coverageLoading, setCoverageLoading] = useState(false) // Create order dialog const [createDialogOpen, setCreateDialogOpen] = useState(false) const [newOrderDate, setNewOrderDate] = useState(() => new Date().toISOString().split('T')[0]) const [newOrderMealType, setNewOrderMealType] = useState<'breakfast' | 'lunch' | 'dinner'>('breakfast') const [creating, setCreating] = useState(false) const fetchOrders = useCallback(async (page: number = 1) => { setOrdersLoading(true) try { let url = `/api/meal-orders?sort=-date,-createdAt&limit=${ITEMS_PER_PAGE}&page=${page}&depth=0` if (dateFilter) { url += `&where[date][equals]=${dateFilter}` } if (statusFilter !== 'all') { url += `&where[status][equals]=${statusFilter}` } const res = await fetch(url, { credentials: 'include' }) if (res.ok) { const data = await res.json() setOrders(data.docs || []) setPagination({ totalDocs: data.totalDocs || 0, totalPages: data.totalPages || 1, page: data.page || 1, limit: data.limit || ITEMS_PER_PAGE, hasNextPage: data.hasNextPage || false, hasPrevPage: data.hasPrevPage || false, }) } else if (res.status === 401) { router.push('/caregiver/login') } } catch (err) { console.error('Error fetching orders:', err) } finally { setOrdersLoading(false) } }, [router, dateFilter, statusFilter]) useEffect(() => { const fetchInitialData = async () => { try { // Fetch current user const userRes = await fetch('/api/users/me', { credentials: 'include' }) if (!userRes.ok) { router.push('/caregiver/login') return } const userData = await userRes.json() if (!userData.user) { router.push('/caregiver/login') return } setUser(userData.user) // Fetch residents const residentsRes = await fetch('/api/residents?where[active][equals]=true&limit=500', { credentials: 'include', }) if (residentsRes.ok) { const residentsData = await residentsRes.json() setResidents(residentsData.docs || []) } // Fetch stats (all orders from last 7 days) const weekAgo = new Date() weekAgo.setDate(weekAgo.getDate() - 7) const statsRes = await fetch( `/api/meal-orders?where[date][greater_than_equal]=${weekAgo.toISOString().split('T')[0]}&limit=1000&depth=0`, { credentials: 'include' } ) if (statsRes.ok) { const statsData = await statsRes.json() const allOrders = statsData.docs || [] setStats({ draft: allOrders.filter((o: MealOrder) => o.status === 'draft').length, submitted: allOrders.filter((o: MealOrder) => o.status === 'submitted').length, preparing: allOrders.filter((o: MealOrder) => o.status === 'preparing').length, completed: allOrders.filter((o: MealOrder) => o.status === 'completed').length, total: allOrders.length, }) } } catch (error) { console.error('Error fetching data:', error) } finally { setLoading(false) } } fetchInitialData() }, [router]) useEffect(() => { if (!loading) { fetchOrders(1) } }, [loading, fetchOrders]) const handleLogout = async () => { await fetch('/api/users/logout', { method: 'POST', credentials: 'include', }) router.push('/caregiver/login') } const handleCreateOrder = async () => { setCreating(true) try { const res = await fetch('/api/meal-orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: newOrderDate, mealType: newOrderMealType, status: 'draft', }), credentials: 'include', }) if (res.ok) { const data = await res.json() setCreateDialogOpen(false) router.push(`/caregiver/orders/${data.doc.id}`) } } catch (err) { console.error('Error creating order:', err) } finally { setCreating(false) } } const handleViewCoverage = async (order: MealOrder) => { setSelectedOrder(order) setCoverageDialogOpen(true) setCoverageLoading(true) try { const res = await fetch( `/api/meals?where[order][equals]=${order.id}&depth=1&limit=500`, { credentials: 'include' } ) if (res.ok) { const data = await res.json() setOrderMeals(data.docs || []) } } catch (err) { console.error('Error fetching meals:', err) } finally { setCoverageLoading(false) } } const getMealTypeLabel = (type: string) => { switch (type) { case 'breakfast': return 'Breakfast' case 'lunch': return 'Lunch' case 'dinner': return 'Dinner' default: return type } } const getMealTypeIcon = (type: string) => { switch (type) { case 'breakfast': return case 'lunch': return case 'dinner': return default: return null } } const getStatusBadge = (status: string) => { switch (status) { case 'draft': return ( Draft ) case 'submitted': return ( Submitted ) case 'preparing': return ( Preparing ) case 'completed': return ( Completed ) default: return {status} } } const getCoverageInfo = (order: MealOrder) => { const totalResidents = residents.length const coveredCount = order.mealCount const percentage = totalResidents > 0 ? Math.round((coveredCount / totalResidents) * 100) : 0 return { coveredCount, totalResidents, percentage } } const getCoverageColor = (percentage: number) => { if (percentage === 100) return 'bg-green-500' if (percentage >= 75) return 'bg-blue-500' if (percentage >= 50) return 'bg-yellow-500' return 'bg-red-500' } const formatDate = (dateStr: string) => { const date = new Date(dateStr) return date.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', }) } if (loading) { return (
) } const tenantName = user?.tenants?.[0]?.tenant && typeof user.tenants[0].tenant === 'object' ? user.tenants[0].tenant.name : 'Care Home' // Calculate covered residents for coverage dialog const coveredResidentIds = new Set( orderMeals.map((m) => (typeof m.resident === 'object' ? m.resident.id : m.resident)) ) const coveredResidents = residents.filter((r) => coveredResidentIds.has(r.id)) const uncoveredResidents = residents.filter((r) => !coveredResidentIds.has(r.id)) return (

{tenantName}

{user?.name || user?.email}

Dashboard

Manage meal orders for your care home

{/* Stats Cards */}
Total Orders
{stats.total}

Last 7 days

Draft
{stats.draft}

In progress

Submitted
{stats.submitted}

Sent to kitchen

Preparing
{stats.preparing}

Being prepared

Completed
{stats.completed}

Finished

{/* Quick Actions */} Quick Actions
All Orders Residents Admin Panel setCreateDialogOpen(true)} > New Order
{/* Meal Orders Table */}
Meal Orders View and manage all meal orders. Click on coverage to see resident details.
setDateFilter(e.target.value)} className="w-40" placeholder="Filter by date" />
{(dateFilter || statusFilter !== 'all') && ( )}
{ordersLoading ? (
) : orders.length === 0 ? (

No orders found.

Create a new order to get started.

) : ( <> Meal Date Coverage Status Actions {orders.map((order) => { const coverage = getCoverageInfo(order) return (
{getMealTypeIcon(order.mealType)} {getMealTypeLabel(order.mealType)}
{formatDate(order.date)}

Click to view coverage details

{getStatusBadge(order.status)}
{order.status === 'draft' ? ( ) : ( )}
) })}
{/* Pagination */} {pagination.totalPages > 1 && (
Showing {(pagination.page - 1) * pagination.limit + 1} to{' '} {Math.min(pagination.page * pagination.limit, pagination.totalDocs)} of{' '} {pagination.totalDocs} orders
{Array.from({ length: Math.min(5, pagination.totalPages) }, (_, i) => { let pageNum: number if (pagination.totalPages <= 5) { pageNum = i + 1 } else if (pagination.page <= 3) { pageNum = i + 1 } else if (pagination.page >= pagination.totalPages - 2) { pageNum = pagination.totalPages - 4 + i } else { pageNum = pagination.page - 2 + i } return ( ) })}
)} )}
{/* Create Order Dialog */} Create New Meal Order Select a date and meal type to create a new order.
setNewOrderDate(e.target.value)} />
{[ { value: 'breakfast', label: 'Breakfast', icon: Sunrise, color: 'text-orange-500' }, { value: 'lunch', label: 'Lunch', icon: Sun, color: 'text-yellow-500' }, { value: 'dinner', label: 'Dinner', icon: Moon, color: 'text-indigo-500' }, ].map(({ value, label, icon: Icon, color }) => ( ))}
{/* Coverage Dialog */} Resident Coverage {selectedOrder && ( <> {getMealTypeLabel(selectedOrder.mealType)} - {formatDate(selectedOrder.date)} )} {coverageLoading ? (
) : (
{/* Coverage Summary */}
Coverage Progress {coveredResidents.length}/{residents.length} residents
0 ? (coveredResidents.length / residents.length) * 100 : 0 } className="h-3" />
0 ? (coveredResidents.length / residents.length) * 100 : 0 ) )} > {residents.length > 0 ? Math.round((coveredResidents.length / residents.length) * 100) : 0} %
{/* Uncovered Residents */} {uncoveredResidents.length > 0 && (

Missing Meals ({uncoveredResidents.length})

{uncoveredResidents.map((resident) => (
{resident.name}
Room {resident.room}
))}
)} {/* Covered Residents */} {coveredResidents.length > 0 && (

Covered Residents ({coveredResidents.length})

{coveredResidents.map((resident) => (
{resident.name}
Room {resident.room}
))}
)}
)} {selectedOrder?.status === 'draft' && ( )}
) }