'use client';
import { useQuery } from '@tanstack/react-query';
import { PermissionGuard } from '@/components/layout/PermissionGuard';
import { PERMS } from '@/lib/permissions';
import { Ticket, AlertCircle, BookOpen, Banknote, ArrowRight, ScanLine } from 'lucide-react';
import { dashboardApi } from '@/lib/api/dashboard';
import { apiClient } from '@/lib/api-client';
import { formatCurrency } from '@/lib/utils';
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer } from 'recharts';
import Link from 'next/link';
const COLORS = ['#2563eb', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'];
function StatCard({
icon, iconBg, label, total, loading, rows, href,
}: {
icon: React.ReactNode;
iconBg: string;
label: string;
total: number;
loading: boolean;
rows: { label: string; value: number; icon?: React.ReactNode; href: string }[];
href: string;
}) {
return (
{loading ? '—' : total.toLocaleString()}
{rows.map((r) => (
{r.icon}{r.label}
{loading ? '—' : r.value.toLocaleString()}
))}
View all
);
}
function RevenueSection({
label, bookingCount, rows, subtotal, loading, renderRow,
}: {
label: React.ReactNode;
bookingCount: number;
rows: { currency: string; totalMinor: number }[];
subtotal: number;
loading: boolean;
renderRow: (r: { currency: string; totalMinor: number }) => React.ReactNode;
}) {
return (
{label}
{loading ? '—' : bookingCount.toLocaleString()} bookings
{rows.length === 0
?
No revenue yet
: rows.map(renderRow)}
{rows.length > 0 && (
Subtotal
{formatCurrency(subtotal, 'ETB')}
)}
);
}
function DashboardPageContent() {
const { data: exchangeRates = [] } = useQuery({
queryKey: ['currencies'],
queryFn: () => apiClient.get('/currencies'),
select: (d: any) => (Array.isArray(d) ? d : d?.data ?? d?.items ?? []),
});
const toEtbRate = (currency: string): number | null => {
if (currency === 'ETB') return 1;
const r = exchangeRates.find((x: any) => x.fromCurrency === 'ETB' && x.toCurrency === currency);
return r ? 1 / r.rate : null;
};
const { data: stats, isLoading: statsLoading, error: statsError } = useQuery({
queryKey: ['backoffice-stats'],
queryFn: dashboardApi.getBackofficeStats,
retry: 1,
staleTime: 60000,
});
const { data: paymentMethods } = useQuery({
queryKey: ['payment-methods'],
queryFn: dashboardApi.getPaymentMethods,
retry: 1,
});
const calcGrand = (rows: { currency: string; totalMinor: number }[]) =>
rows.reduce((sum, { currency, totalMinor }) => {
const rate = toEtbRate(currency);
return rate !== null ? sum + Math.round(totalMinor * rate) : sum;
}, 0);
const normalRows = stats?.revenueByCurrency ?? [];
const packageRows = stats?.packageRevenueByCurrency ?? [];
const normalGrand = calcGrand(normalRows);
const packageGrand = calcGrand(packageRows);
const overallGrand = normalGrand + packageGrand;
const renderRevenueRow = ({ currency, totalMinor }: { currency: string; totalMinor: number }) => {
const rate = toEtbRate(currency);
const etbMinor = rate !== null ? Math.round(totalMinor * rate) : null;
return (
{currency}
{formatCurrency(totalMinor, currency)}
{currency !== 'ETB' && etbMinor !== null && (
({formatCurrency(etbMinor, 'ETB')})
)}
);
};
return (
Dashboard
Welcome back! Here's your operational summary.
Boarding
{statsError && (
Some data may be outdated
Unable to fetch live data. Showing cached or sample information.
)}
{/* Stat cards */}
}
iconBg="bg-blue-100 dark:bg-blue-900/30"
label="Bookings"
total={stats?.totalBookings ?? 0}
loading={statsLoading}
href="/bookings"
rows={[
{ label: 'Regular', value: stats?.totalNormalBookings ?? 0, href: '/bookings' },
{ label: 'Package', value: stats?.totalPackageBookings ?? 0, href: '/package-bookings' },
]}
/>
}
iconBg="bg-emerald-100 dark:bg-emerald-900/30"
label="Tickets"
total={stats?.totalTickets ?? 0}
loading={statsLoading}
href="/tickets"
rows={[
{ label: 'Regular', value: stats?.totalNormalTickets ?? 0, href: '/tickets' },
{ label: 'Package', value: stats?.totalPackageTickets ?? 0, href: '/tickets' },
]}
/>
{/* Revenue card */}
{statsLoading ? (
Loading…
) : (
<>
{formatCurrency(overallGrand, 'ETB')}
Regular
{formatCurrency(normalGrand, 'ETB')}
Package
{formatCurrency(packageGrand, 'ETB')}
View payments
>
)}
{/* Revenue breakdown */}
Revenue Breakdown
{statsLoading ? (
Loading…
) : !normalRows.length && !packageRows.length ? (
No revenue data yet.
) : (
)}
{/* Payment Methods Distribution */}
{paymentMethods && paymentMethods.length > 0 && (
Payment Methods Distribution
{paymentMethods.map((_: any, index: number) => (
|
))}
)}
);
}
export default function DashboardPage() {
return (
);
}