import { useState, useMemo, useRef } from 'react'; import { DollarSign, TrendingUp, Users, ShoppingCart, Wallet, Percent, Coffee, UtensilsCrossed, Download } from 'lucide-react'; import { LocationFilter } from './components/LocationFilter'; import { PeriodSelector } from './components/PeriodSelector'; import { KPICard } from './components/KPICard'; import { KPITrendChart } from './components/KPITrendChart'; import { DataTrendChart } from './components/DataTrendChart'; import { locations, getFinancialDataByLocationAndPeriod, getAggregatedDataByTypeAndPeriod, getHistoricalData, calculateComparison, getAvailablePeriods, financialData } from './data/mockData'; import { FinancialData, CoffeeBarData, CafeData, CateringData, PrivateDiningData } from './types/financial'; import html2canvas from 'html2canvas'; import jsPDF from 'jspdf'; export default function App() { const [selectedType, setSelectedType] = useState('coffee-bar'); const [selectedLocation, setSelectedLocation] = useState('all'); const [selectedYear, setSelectedYear] = useState(2025); const [selectedMonth, setSelectedMonth] = useState(11); const [selectedKPI, setSelectedKPI] = useState('participationRate'); const [selectedData, setSelectedData] = useState('totalSales'); const [isExporting, setIsExporting] = useState(false); const contentRef = useRef(null); const availablePeriods = useMemo(() => getAvailablePeriods(), []); const currentData = useMemo((): FinancialData | null => { if (selectedLocation !== 'all') { return getFinancialDataByLocationAndPeriod(selectedLocation, selectedYear, selectedMonth) || null; } return getAggregatedDataByTypeAndPeriod(selectedType, selectedYear, selectedMonth); }, [selectedType, selectedLocation, selectedYear, selectedMonth]); const previousMonthData = useMemo((): FinancialData | null => { let prevYear = selectedYear; let prevMonth = selectedMonth - 1; if (prevMonth === 0) { prevMonth = 12; prevYear -= 1; } if (selectedLocation !== 'all') { return getFinancialDataByLocationAndPeriod(selectedLocation, prevYear, prevMonth) || null; } return getAggregatedDataByTypeAndPeriod(selectedType, prevYear, prevMonth); }, [selectedType, selectedLocation, selectedYear, selectedMonth]); const previousYearData = useMemo((): FinancialData | null => { const prevYear = selectedYear - 1; if (selectedLocation !== 'all') { return getFinancialDataByLocationAndPeriod(selectedLocation, prevYear, selectedMonth) || null; } return getAggregatedDataByTypeAndPeriod(selectedType, prevYear, selectedMonth); }, [selectedType, selectedLocation, selectedYear, selectedMonth]); const previousQuarterData = useMemo((): FinancialData | null => { let prevYear = selectedYear; let prevMonth = selectedMonth - 3; if (prevMonth <= 0) { prevMonth += 12; prevYear -= 1; } if (selectedLocation !== 'all') { return getFinancialDataByLocationAndPeriod(selectedLocation, prevYear, prevMonth) || null; } return getAggregatedDataByTypeAndPeriod(selectedType, prevYear, prevMonth); }, [selectedType, selectedLocation, selectedYear, selectedMonth]); const historicalData = useMemo(() => { return getHistoricalData( selectedLocation !== 'all' ? selectedLocation : null, selectedType, 12 ); }, [selectedType, selectedLocation]); const locationNameMap = useMemo(() => { return locations.reduce((acc, loc) => { acc[loc.id] = loc.name; return acc; }, {} as { [key: string]: string }); }, []); const getLocationTitle = () => { if (selectedLocation !== 'all') { const location = locations.find(loc => loc.id === selectedLocation); return location ? location.name : 'Unknown Location'; } const typeLabels: { [key: string]: string } = { 'all': 'All Locations', 'cafe': 'All Cafes', 'coffee-bar': 'All Coffee Bars', 'catering': 'All Catering Locations', 'private-dining': 'Private Dining', }; return typeLabels[selectedType] || 'All Locations'; }; const calculateMoM = (getValue: (data: FinancialData) => number) => { if (!currentData || !previousMonthData) return undefined; const current = getValue(currentData); const previous = getValue(previousMonthData); return calculateComparison(current, previous); }; const calculateYoY = (getValue: (data: FinancialData) => number) => { if (!currentData || !previousYearData) return undefined; const current = getValue(currentData); const previous = getValue(previousYearData); return calculateComparison(current, previous); }; const calculateQoQ = (getValue: (data: FinancialData) => number) => { if (!currentData || !previousQuarterData) return undefined; const current = getValue(currentData); const previous = getValue(previousQuarterData); return calculateComparison(current, previous); }; if (!currentData) { return (

No Data Available

Please select a specific location type to view KPIs.

); } const locationType = currentData.type; // Get available KPIs based on location type const getAvailableKPIs = () => { if (locationType === 'coffee-bar') { return [ { value: 'participationRate', label: 'Participation Rate' }, { value: 'checkAverage', label: 'Check Average' }, { value: 'productCostPerTransaction', label: 'Product Cost per Transaction' }, { value: 'laborCostPerTransaction', label: 'Labor Cost per Transaction' }, { value: 'overtimeCostPerTransaction', label: 'Overtime Cost per Transaction' }, { value: 'temporaryLaborCostPerTransaction', label: 'Temporary Labor Cost per Transaction' }, { value: 'indirectCostPerTransaction', label: 'Indirect Cost per Transaction' }, { value: 'excessDeficitCostPerTransaction', label: 'Excess/Deficit Cost per Transaction' }, ]; } else if (locationType === 'cafe') { return [ { value: 'breakfastCheckAverage', label: 'Breakfast Check Average' }, { value: 'lunchCheckAverage', label: 'Lunch Check Average' }, { value: 'breakfastParticipation', label: 'Breakfast Participation' }, { value: 'lunchParticipation', label: 'Lunch Participation' }, { value: 'productCostPerTransaction', label: 'Product Cost per Transaction' }, { value: 'laborCostPerTransaction', label: 'Labor Cost per Transaction' }, { value: 'overtimeCostPerTransaction', label: 'Overtime Cost per Transaction' }, { value: 'temporaryLaborCostPerTransaction', label: 'Temporary Labor Cost per Transaction' }, { value: 'indirectCostPerTransaction', label: 'Indirect Cost per Transaction' }, { value: 'excessDeficitCostPerTransaction', label: 'Excess/Deficit Cost per Transaction' }, ]; } else { return [ { value: 'productCostPercent', label: 'Product Cost %' }, { value: 'laborCostPercent', label: 'Labor Cost %' }, { value: 'overtimeCostPercent', label: 'Overtime Cost %' }, { value: 'temporaryLaborCostPercent', label: 'Temporary Labor %' }, { value: 'expensesCostPercent', label: 'Expenses Cost %' }, { value: 'excessDeficitPercent', label: 'Excess/Deficit %' }, ]; } }; const availableKPIs = getAvailableKPIs(); // Get available data metrics based on location type const getAvailableDataMetrics = () => { if (locationType === 'coffee-bar') { return [ { value: 'totalSales', label: 'Total Sales' }, { value: 'totalTransactions', label: 'Total Transactions' }, { value: 'productCostAmount', label: 'Product Cost' }, { value: 'laborCostAmount', label: 'Labor Cost' }, { value: 'overtimeCostAmount', label: 'Overtime Cost' }, { value: 'excessDeficitAmount', label: 'Excess/Deficit' }, ]; } else if (locationType === 'cafe') { return [ { value: 'breakfastSales', label: 'Breakfast Sales' }, { value: 'lunchSales', label: 'Lunch Sales' }, { value: 'totalSales', label: 'Total Sales' }, { value: 'breakfastTransactions', label: 'Breakfast Transactions' }, { value: 'lunchTransactions', label: 'Lunch Transactions' }, { value: 'totalTransactions', label: 'Total Transactions' }, { value: 'productCostAmount', label: 'Product Cost' }, { value: 'laborCostAmount', label: 'Labor Cost' }, { value: 'excessDeficitAmount', label: 'Excess/Deficit' }, ]; } else { return [ { value: 'totalRevenue', label: 'Total Revenue' }, { value: 'productCostAmount', label: 'Product Cost' }, { value: 'laborCostAmount', label: 'Labor Cost' }, { value: 'overtimeCostAmount', label: 'Overtime Cost' }, { value: 'temporaryLaborCostAmount', label: 'Temporary Labor Cost' }, { value: 'expensesCostAmount', label: 'Expenses Cost' }, { value: 'excessDeficitAmount', label: 'Excess/Deficit' }, ]; } }; const availableDataMetrics = getAvailableDataMetrics(); // Reset selected KPI when location type changes useMemo(() => { const kpiValues = availableKPIs.map(k => k.value); if (!kpiValues.includes(selectedKPI)) { setSelectedKPI(kpiValues[0]); } }, [locationType, selectedKPI]); const exportPDF = async () => { setIsExporting(true); try { const element = contentRef.current; if (!element) return; // Use html2canvas to capture the content const canvas = await html2canvas(element, { scale: 2, useCORS: true, logging: false, backgroundColor: '#f9fafb' }); const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = pdf.internal.pageSize.getHeight(); const imgWidth = pdfWidth; const imgHeight = (canvas.height * pdfWidth) / canvas.width; let heightLeft = imgHeight; let position = 0; // Add first page pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pdfHeight; // Add additional pages if content is longer than one page while (heightLeft > 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); heightLeft -= pdfHeight; } // Generate filename with current date and location const dateStr = new Date().toISOString().split('T')[0]; const locationStr = getLocationTitle().replace(/\s+/g, '_'); pdf.save(`WorldBank_Dashboard_${locationStr}_${dateStr}.pdf`); } catch (error) { console.error('Error generating PDF:', error); alert('Failed to generate PDF. Please try again.'); } finally { setIsExporting(false); } }; return (
{/* Header */}

The World Bank

Corporate Dining Financial Dashboard

Reporting Period

{selectedYear}

{/* Main Content */}
{/* Filters */} {/* Period Selector */} { setSelectedYear(year); setSelectedMonth(month); }} availablePeriods={availablePeriods} /> {/* Current Selection Title */}

{getLocationTitle()}

{currentData.period}

{/* Coffee Bar KPIs */} {locationType === 'coffee-bar' && ( <>

Revenue KPIs

(d as CoffeeBarData).participationRate)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).participationRate)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).participationRate)} icon={} /> (d as CoffeeBarData).checkAverage)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).checkAverage)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).checkAverage)} icon={} />

Cost KPIs

(d as CoffeeBarData).productCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).productCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).productCostPerTransaction)} icon={} /> (d as CoffeeBarData).laborCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).laborCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).laborCostPerTransaction)} icon={} /> (d as CoffeeBarData).overtimeCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).overtimeCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).overtimeCostPerTransaction)} icon={} /> (d as CoffeeBarData).temporaryLaborCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).temporaryLaborCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).temporaryLaborCostPerTransaction)} icon={} /> (d as CoffeeBarData).indirectCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).indirectCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).indirectCostPerTransaction)} icon={} /> (d as CoffeeBarData).excessDeficitCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CoffeeBarData).excessDeficitCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CoffeeBarData).excessDeficitCostPerTransaction)} icon={} isNegativeIndicator={true} />
)} {/* Cafe KPIs */} {locationType === 'cafe' && ( <>

Revenue KPIs

(d as CafeData).breakfastCheckAverage)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).breakfastCheckAverage)} yearOverYear={calculateYoY((d) => (d as CafeData).breakfastCheckAverage)} icon={} /> (d as CafeData).lunchCheckAverage)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).lunchCheckAverage)} yearOverYear={calculateYoY((d) => (d as CafeData).lunchCheckAverage)} icon={} /> (d as CafeData).breakfastParticipation)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).breakfastParticipation)} yearOverYear={calculateYoY((d) => (d as CafeData).breakfastParticipation)} icon={} /> (d as CafeData).lunchParticipation)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).lunchParticipation)} yearOverYear={calculateYoY((d) => (d as CafeData).lunchParticipation)} icon={} />

Cost KPIs

(d as CafeData).productCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).productCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).productCostPerTransaction)} icon={} /> (d as CafeData).laborCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).laborCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).laborCostPerTransaction)} icon={} /> (d as CafeData).overtimeCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).overtimeCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).overtimeCostPerTransaction)} icon={} /> (d as CafeData).temporaryLaborCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).temporaryLaborCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).temporaryLaborCostPerTransaction)} icon={} /> (d as CafeData).indirectCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).indirectCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).indirectCostPerTransaction)} icon={} /> (d as CafeData).excessDeficitCostPerTransaction)} quarterOverQuarter={calculateQoQ((d) => (d as CafeData).excessDeficitCostPerTransaction)} yearOverYear={calculateYoY((d) => (d as CafeData).excessDeficitCostPerTransaction)} icon={} isNegativeIndicator={true} />
)} {/* Catering & Private Dining KPIs */} {(locationType === 'catering' || locationType === 'private-dining') && (

Cost KPIs (% of Revenue)

(d as CateringData).productCostPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).productCostPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).productCostPercent)} icon={} /> (d as CateringData).laborCostPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).laborCostPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).laborCostPercent)} icon={} /> (d as CateringData).overtimeCostPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).overtimeCostPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).overtimeCostPercent)} icon={} /> (d as CateringData).temporaryLaborCostPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).temporaryLaborCostPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).temporaryLaborCostPercent)} icon={} /> (d as CateringData).expensesCostPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).expensesCostPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).expensesCostPercent)} icon={} /> (d as CateringData).excessDeficitPercent)} quarterOverQuarter={calculateQoQ((d) => (d as CateringData).excessDeficitPercent)} yearOverYear={calculateYoY((d) => (d as CateringData).excessDeficitPercent)} icon={} isNegativeIndicator={true} />
)} {/* KPI Trend Over Time */}

KPI Trend Over Time

{/* Data Trend Over Time */}

Data Components Trend Over Time

); }