Scaffold frontend/modules/loyalty with types, API client, design system, feature pages, and thin App Router routes. Wire all screens to backend Loyalty service via BFF proxy. Add loyalty docs and update progress. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
4.4 KiB
TypeScript
89 lines
4.4 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";
|
||
import { HospitalityStatGrid } from "@/modules/hospitality/design-system";
|
||
|
||
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, menus, qrSessions] = 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 }),
|
||
hospitalityApi.menus.list(tenantId!, { page: 1, page_size: 500 }),
|
||
hospitalityApi.qrOrderingSessions.list(tenantId!, { page: 1, page_size: 500 }),
|
||
]);
|
||
return {
|
||
venues: venues.length,
|
||
reservations: reservations.filter((r) => r.status === "confirmed" || r.status === "pending").length,
|
||
openOrders: tickets.filter((t) => t.status === "open" || t.status === "draft").length,
|
||
kitchenActive: kitchen.filter((k) => k.status !== "completed" && k.status !== "cancelled").length,
|
||
menus: menus.filter((m) => m.status === "published" || m.status === "active").length,
|
||
onlineOrders: qrSessions.filter((s) => s.status === "submitted" || s.status === "preparing").length,
|
||
};
|
||
},
|
||
enabled: !!tenantId,
|
||
refetchInterval: 30_000,
|
||
});
|
||
|
||
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="نمای اجرایی فروش، سفارشها و عملیات روزانه — داده زنده از API."
|
||
actions={
|
||
<div className="flex gap-2">
|
||
<Badge tone="success">Torbat Food v{caps.data?.version}</Badge>
|
||
<Button variant="outline" size="sm" onClick={() => countsQ.refetch()}>
|
||
بروزرسانی
|
||
</Button>
|
||
</div>
|
||
}
|
||
/>
|
||
<HospitalityStatGrid
|
||
stats={[
|
||
{ label: "مکانها", value: c.venues, hint: "Venues" },
|
||
{ label: "رزرو فعال", value: c.reservations, hint: "Reservations" },
|
||
{ label: "سفارش باز POS", value: c.openOrders, hint: "Open tickets" },
|
||
{ label: "آشپزخانه فعال", value: c.kitchenActive, hint: "Kitchen queue" },
|
||
{ label: "منوهای منتشر", value: c.menus, hint: "Menus" },
|
||
{ label: "سفارش آنلاین", value: c.onlineOrders, hint: "QR ordering" },
|
||
]}
|
||
/>
|
||
<Card>
|
||
<CardContent className="space-y-3 p-4">
|
||
<p className="text-sm font-medium text-secondary">عملیات سریع</p>
|
||
<div className="flex flex-wrap gap-2">
|
||
<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 Lite</Button></Link>
|
||
<Link href="/hospitality/menu"><Button variant="outline">منو</Button></Link>
|
||
<Link href="/hospitality/analytics"><Button variant="outline">آنالیتیکس</Button></Link>
|
||
<Link href="/hospitality/orders/online"><Button variant="outline">سفارش آنلاین</Button></Link>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ExecutiveDashboard;
|