TorbatYar/frontend/modules/hospitality/features/dashboard.tsx
2026-07-28 20:39:10 +03:30

128 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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 { useHospitalityProductBrand } from "@/modules/hospitality/hooks/useHospitalityProductBrand";
import { HospitalityPageLoader, HospitalityPageError } from "@/modules/hospitality/pages/shared";
import { PageHeader, 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 brand = useHospitalityProductBrand();
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,
needsOnboarding: venues.length === 0,
};
},
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!;
const productLabel = brand.data?.displayName || "Hospitality";
return (
<div className="space-y-6">
<HospitalityBreadcrumbs current="داشبورد" />
<PageHeader
title="داشبورد مدیریتی"
description={`نمای اجرایی ${productLabel} — داده زنده از API.`}
actions={
<div className="flex gap-2">
<Badge tone="success">
{productLabel}
{caps.data?.version ? ` · v${caps.data.version}` : ""}
</Badge>
<Button variant="outline" size="sm" onClick={() => countsQ.refetch()}>
بروزرسانی
</Button>
</div>
}
/>
{c.needsOnboarding ? (
<Card>
<CardContent className="flex flex-wrap items-center justify-between gap-3 p-4">
<div>
<p className="font-medium text-secondary">راهاندازی اولیه لازم است</p>
<p className="mt-1 text-sm text-[var(--muted)]">
هنوز مکانی ثبت نشده. ویزارد رستوران را کامل کنید.
</p>
</div>
<Link href="/hospitality/onboarding">
<Button>شروع راهاندازی</Button>
</Link>
</CardContent>
</Card>
) : null}
<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/ops">
<Button variant="outline">مرکز عملیات</Button>
</Link>
<Link href="/hospitality/reservations">
<Button variant="outline">رزروها</Button>
</Link>
<Link href="/hospitality/ops/kitchen">
<Button variant="outline">آشپزخانه</Button>
</Link>
<Link href="/hospitality/ops/cashier">
<Button variant="outline">صندوقدار</Button>
</Link>
<Link href="/hospitality/menu">
<Button variant="outline">منو</Button>
</Link>
<Link href="/hospitality/analytics">
<Button variant="outline">آنالیتیکس</Button>
</Link>
<Link href="/hospitality/onboarding">
<Button variant="outline">راهاندازی</Button>
</Link>
</div>
</CardContent>
</Card>
</div>
);
};
export default ExecutiveDashboard;