Introduces modules/hospitality with 54 routes, BFF proxy, capability-gated nav, CRUD factory, and architecture validation docs. Co-authored-by: Cursor <cursoragent@cursor.com>
67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
"use client";
|
||
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import Link from "next/link";
|
||
import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
|
||
import { useTenantId } from "@/hooks/useTenantId";
|
||
import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
|
||
import { HospitalityPageLoader, HospitalityPageError } from "@/modules/hospitality/pages/shared";
|
||
import { PageHeader, StatCard, Card, CardContent, Button, Badge } from "@/components/ds";
|
||
import { HospitalityBreadcrumbs } from "@/modules/hospitality/components/HospitalityBreadcrumbs";
|
||
|
||
export const ExecutiveDashboard = function HospitalityDashboard() {
|
||
const { tenantId } = useTenantId();
|
||
const caps = useHospitalityCapabilities();
|
||
|
||
const countsQ = useQuery({
|
||
queryKey: ["hospitality", tenantId, "dashboard-counts"],
|
||
queryFn: async () => {
|
||
const [venues, reservations, tickets, kitchen] = await Promise.all([
|
||
hospitalityApi.venues.list(tenantId!, { page: 1, page_size: 500 }),
|
||
hospitalityApi.reservations.list(tenantId!, { page: 1, page_size: 500 }),
|
||
hospitalityApi.posTickets.list(tenantId!, { page: 1, page_size: 500 }),
|
||
hospitalityApi.kitchenTickets.list(tenantId!, { page: 1, page_size: 500 }),
|
||
]);
|
||
return {
|
||
venues: venues.length,
|
||
reservations: reservations.length,
|
||
openOrders: tickets.filter((t) => t.status === "open" || t.status === "draft").length,
|
||
kitchenActive: kitchen.filter((k) => k.status !== "completed" && k.status !== "cancelled").length,
|
||
};
|
||
},
|
||
enabled: !!tenantId,
|
||
});
|
||
|
||
if (!tenantId || countsQ.isLoading || caps.isLoading) return <HospitalityPageLoader />;
|
||
if (countsQ.error) return <HospitalityPageError error={countsQ.error} onRetry={() => countsQ.refetch()} />;
|
||
|
||
const c = countsQ.data!;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<HospitalityBreadcrumbs current="داشبورد" />
|
||
<PageHeader
|
||
title="داشبورد مدیریتی"
|
||
description="نمای اجرایی فروش، سفارشها و عملیات روزانه."
|
||
actions={<Badge tone="success">Torbat Food v{caps.data?.version}</Badge>}
|
||
/>
|
||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||
<StatCard label="مکانها" value={String(c.venues)} hint="Venues" />
|
||
<StatCard label="رزرو امروز" value={String(c.reservations)} hint="Reservations" />
|
||
<StatCard label="سفارش باز" value={String(c.openOrders)} hint="Open tickets" />
|
||
<StatCard label="آشپزخانه فعال" value={String(c.kitchenActive)} hint="Kitchen queue" />
|
||
</div>
|
||
<Card>
|
||
<CardContent className="flex flex-wrap gap-2 p-4">
|
||
<Link href="/hospitality/reservations"><Button variant="outline">رزروها</Button></Link>
|
||
<Link href="/hospitality/kitchen/display"><Button variant="outline">آشپزخانه</Button></Link>
|
||
<Link href="/hospitality/pos/lite"><Button variant="outline">POS</Button></Link>
|
||
<Link href="/hospitality/analytics"><Button variant="outline">آنالیتیکس</Button></Link>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ExecutiveDashboard;
|