diff --git a/frontend/app/api/hospitality/[...path]/route.ts b/frontend/app/api/hospitality/[...path]/route.ts
new file mode 100644
index 0000000..b3d0d02
--- /dev/null
+++ b/frontend/app/api/hospitality/[...path]/route.ts
@@ -0,0 +1,53 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const UPSTREAM =
+ process.env.HOSPITALITY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_HOSPITALITY_API_URL ||
+ "http://localhost:8009";
+
+async function proxy(req: NextRequest, pathSegments: string[]) {
+ const p = pathSegments.join("/");
+ const url = new URL(req.url);
+ const target = `${UPSTREAM.replace(/\/$/, "")}/${p}${url.search}`;
+ const headers = new Headers();
+ for (const h of ["authorization", "content-type", "x-tenant-id", "accept"]) {
+ const v = req.headers.get(h);
+ if (v) headers.set(h, v);
+ }
+ let body: ArrayBuffer | undefined;
+ if (req.method !== "GET" && req.method !== "HEAD") body = await req.arrayBuffer();
+ try {
+ const res = await fetch(target, { method: req.method, headers, body, cache: "no-store" });
+ const out = new Headers();
+ const ct = res.headers.get("content-type");
+ if (ct) out.set("content-type", ct);
+ return new NextResponse(await res.arrayBuffer(), { status: res.status, headers: out });
+ } catch (err) {
+ const detail = err instanceof Error ? err.message : "upstream_unreachable";
+ return NextResponse.json(
+ { error: { code: "hospitality_proxy_error", message: `پروکسی تربت فود — ${detail}` } },
+ { status: 502 }
+ );
+ }
+}
+
+export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
+ const { path: p } = await ctx.params;
+ return proxy(req, p);
+}
+export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
+ const { path: p } = await ctx.params;
+ return proxy(req, p);
+}
+export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
+ const { path: p } = await ctx.params;
+ return proxy(req, p);
+}
+export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
+ const { path: p } = await ctx.params;
+ return proxy(req, p);
+}
+export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) {
+ const { path: p } = await ctx.params;
+ return proxy(req, p);
+}
diff --git a/frontend/app/hospitality/analytics/error.tsx b/frontend/app/hospitality/analytics/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/analytics/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/analytics/loading.tsx b/frontend/app/hospitality/analytics/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/analytics/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/analytics/page.tsx b/frontend/app/hospitality/analytics/page.tsx
new file mode 100644
index 0000000..c85b504
--- /dev/null
+++ b/frontend/app/hospitality/analytics/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AnalyticsPage as default } from "@/modules/hospitality/features/analyticsSnapshots";
diff --git a/frontend/app/hospitality/audit/error.tsx b/frontend/app/hospitality/audit/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/audit/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/audit/loading.tsx b/frontend/app/hospitality/audit/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/audit/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/audit/page.tsx b/frontend/app/hospitality/audit/page.tsx
new file mode 100644
index 0000000..a25a8c3
--- /dev/null
+++ b/frontend/app/hospitality/audit/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AuditLogsPage as default } from "@/modules/hospitality/features/events";
diff --git a/frontend/app/hospitality/branches/error.tsx b/frontend/app/hospitality/branches/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/branches/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/branches/list/error.tsx b/frontend/app/hospitality/branches/list/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/branches/list/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/branches/list/loading.tsx b/frontend/app/hospitality/branches/list/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/branches/list/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/branches/list/page.tsx b/frontend/app/hospitality/branches/list/page.tsx
new file mode 100644
index 0000000..37ef0d5
--- /dev/null
+++ b/frontend/app/hospitality/branches/list/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { BranchListPage as default } from "@/modules/hospitality/features/branches";
diff --git a/frontend/app/hospitality/branches/loading.tsx b/frontend/app/hospitality/branches/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/branches/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/branches/page.tsx b/frontend/app/hospitality/branches/page.tsx
new file mode 100644
index 0000000..130fe57
--- /dev/null
+++ b/frontend/app/hospitality/branches/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { BranchesPage as default } from "@/modules/hospitality/features/venues";
diff --git a/frontend/app/hospitality/bundles/error.tsx b/frontend/app/hospitality/bundles/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/bundles/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/bundles/loading.tsx b/frontend/app/hospitality/bundles/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/bundles/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/bundles/page.tsx b/frontend/app/hospitality/bundles/page.tsx
new file mode 100644
index 0000000..a8ebb50
--- /dev/null
+++ b/frontend/app/hospitality/bundles/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { BundlesPage as default } from "@/modules/hospitality/features/tenantBundles";
diff --git a/frontend/app/hospitality/capabilities/error.tsx b/frontend/app/hospitality/capabilities/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/capabilities/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/capabilities/loading.tsx b/frontend/app/hospitality/capabilities/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/capabilities/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/capabilities/page.tsx b/frontend/app/hospitality/capabilities/page.tsx
new file mode 100644
index 0000000..ad61cf4
--- /dev/null
+++ b/frontend/app/hospitality/capabilities/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CapabilitiesPage as default } from "@/modules/hospitality/features/capabilities";
diff --git a/frontend/app/hospitality/customers/error.tsx b/frontend/app/hospitality/customers/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/customers/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/customers/loading.tsx b/frontend/app/hospitality/customers/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/customers/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/customers/page.tsx b/frontend/app/hospitality/customers/page.tsx
new file mode 100644
index 0000000..8525d06
--- /dev/null
+++ b/frontend/app/hospitality/customers/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CustomersPage as default } from "@/modules/hospitality/features/connectorRegistrationsCrm";
diff --git a/frontend/app/hospitality/dining-areas/error.tsx b/frontend/app/hospitality/dining-areas/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/dining-areas/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/dining-areas/loading.tsx b/frontend/app/hospitality/dining-areas/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/dining-areas/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/dining-areas/page.tsx b/frontend/app/hospitality/dining-areas/page.tsx
new file mode 100644
index 0000000..972419a
--- /dev/null
+++ b/frontend/app/hospitality/dining-areas/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DiningAreasPage as default } from "@/modules/hospitality/features/diningAreas";
diff --git a/frontend/app/hospitality/error.tsx b/frontend/app/hospitality/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/floor-map/error.tsx b/frontend/app/hospitality/floor-map/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/floor-map/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/floor-map/loading.tsx b/frontend/app/hospitality/floor-map/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/floor-map/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/floor-map/page.tsx b/frontend/app/hospitality/floor-map/page.tsx
new file mode 100644
index 0000000..b852c97
--- /dev/null
+++ b/frontend/app/hospitality/floor-map/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { FloorMapPage as default } from "@/modules/hospitality/features/posFloorPlans";
diff --git a/frontend/app/hospitality/guests/error.tsx b/frontend/app/hospitality/guests/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/guests/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/guests/loading.tsx b/frontend/app/hospitality/guests/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/guests/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/guests/page.tsx b/frontend/app/hospitality/guests/page.tsx
new file mode 100644
index 0000000..6cb5307
--- /dev/null
+++ b/frontend/app/hospitality/guests/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { GuestsPage as default } from "@/modules/hospitality/features/qrMenuSessions";
diff --git a/frontend/app/hospitality/health/error.tsx b/frontend/app/hospitality/health/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/health/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/health/loading.tsx b/frontend/app/hospitality/health/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/health/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/health/page.tsx b/frontend/app/hospitality/health/page.tsx
new file mode 100644
index 0000000..e71af63
--- /dev/null
+++ b/frontend/app/hospitality/health/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { HealthPage as default } from "@/modules/hospitality/features/health";
diff --git a/frontend/app/hospitality/hub/error.tsx b/frontend/app/hospitality/hub/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/hub/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/hub/loading.tsx b/frontend/app/hospitality/hub/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/hub/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/hub/page.tsx b/frontend/app/hospitality/hub/page.tsx
new file mode 100644
index 0000000..d26e946
--- /dev/null
+++ b/frontend/app/hospitality/hub/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { HospitalityHub as default } from "@/modules/hospitality/features/hub";
diff --git a/frontend/app/hospitality/integrations/communication/error.tsx b/frontend/app/hospitality/integrations/communication/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/communication/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/communication/loading.tsx b/frontend/app/hospitality/integrations/communication/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/communication/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/communication/page.tsx b/frontend/app/hospitality/integrations/communication/page.tsx
new file mode 100644
index 0000000..9295850
--- /dev/null
+++ b/frontend/app/hospitality/integrations/communication/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CommunicationIntegrationPage as default } from "@/modules/hospitality/features/connectorCommunication";
diff --git a/frontend/app/hospitality/integrations/crm/error.tsx b/frontend/app/hospitality/integrations/crm/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/crm/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/crm/loading.tsx b/frontend/app/hospitality/integrations/crm/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/crm/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/crm/page.tsx b/frontend/app/hospitality/integrations/crm/page.tsx
new file mode 100644
index 0000000..51772e5
--- /dev/null
+++ b/frontend/app/hospitality/integrations/crm/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CrmIntegrationPage as default } from "@/modules/hospitality/features/connectorCrm";
diff --git a/frontend/app/hospitality/integrations/delivery/error.tsx b/frontend/app/hospitality/integrations/delivery/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/delivery/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/delivery/loading.tsx b/frontend/app/hospitality/integrations/delivery/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/delivery/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/delivery/page.tsx b/frontend/app/hospitality/integrations/delivery/page.tsx
new file mode 100644
index 0000000..b2d351b
--- /dev/null
+++ b/frontend/app/hospitality/integrations/delivery/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DeliveryIntegrationPage as default } from "@/modules/hospitality/features/connectorDelivery";
diff --git a/frontend/app/hospitality/integrations/loyalty/error.tsx b/frontend/app/hospitality/integrations/loyalty/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/loyalty/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/loyalty/loading.tsx b/frontend/app/hospitality/integrations/loyalty/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/loyalty/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/loyalty/page.tsx b/frontend/app/hospitality/integrations/loyalty/page.tsx
new file mode 100644
index 0000000..38c2584
--- /dev/null
+++ b/frontend/app/hospitality/integrations/loyalty/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { LoyaltyIntegrationPage as default } from "@/modules/hospitality/features/connectorLoyalty";
diff --git a/frontend/app/hospitality/integrations/marketplace/error.tsx b/frontend/app/hospitality/integrations/marketplace/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/marketplace/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/marketplace/loading.tsx b/frontend/app/hospitality/integrations/marketplace/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/marketplace/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/marketplace/page.tsx b/frontend/app/hospitality/integrations/marketplace/page.tsx
new file mode 100644
index 0000000..604acc0
--- /dev/null
+++ b/frontend/app/hospitality/integrations/marketplace/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { MarketplaceIntegrationPage as default } from "@/modules/hospitality/features/connectorDispatches";
diff --git a/frontend/app/hospitality/integrations/website/error.tsx b/frontend/app/hospitality/integrations/website/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/integrations/website/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/website/loading.tsx b/frontend/app/hospitality/integrations/website/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/integrations/website/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/integrations/website/page.tsx b/frontend/app/hospitality/integrations/website/page.tsx
new file mode 100644
index 0000000..6e10efc
--- /dev/null
+++ b/frontend/app/hospitality/integrations/website/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { WebsiteIntegrationPage as default } from "@/modules/hospitality/features/connectorWebsite";
diff --git a/frontend/app/hospitality/inventory/error.tsx b/frontend/app/hospitality/inventory/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/inventory/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/inventory/loading.tsx b/frontend/app/hospitality/inventory/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/inventory/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/inventory/page.tsx b/frontend/app/hospitality/inventory/page.tsx
new file mode 100644
index 0000000..222793d
--- /dev/null
+++ b/frontend/app/hospitality/inventory/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { InventoryPage as default } from "@/modules/hospitality/features/inventoryOverview";
diff --git a/frontend/app/hospitality/invoices/error.tsx b/frontend/app/hospitality/invoices/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/invoices/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/invoices/loading.tsx b/frontend/app/hospitality/invoices/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/invoices/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/invoices/page.tsx b/frontend/app/hospitality/invoices/page.tsx
new file mode 100644
index 0000000..d025efd
--- /dev/null
+++ b/frontend/app/hospitality/invoices/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { InvoicesPage as default } from "@/modules/hospitality/features/invoices";
diff --git a/frontend/app/hospitality/kitchen/display/error.tsx b/frontend/app/hospitality/kitchen/display/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/display/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/display/loading.tsx b/frontend/app/hospitality/kitchen/display/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/display/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/display/page.tsx b/frontend/app/hospitality/kitchen/display/page.tsx
new file mode 100644
index 0000000..e551bd6
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/display/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { KitchenDisplayPage as default } from "@/modules/hospitality/features/kitchenTickets";
diff --git a/frontend/app/hospitality/kitchen/preparation/error.tsx b/frontend/app/hospitality/kitchen/preparation/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/preparation/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/preparation/loading.tsx b/frontend/app/hospitality/kitchen/preparation/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/preparation/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/preparation/page.tsx b/frontend/app/hospitality/kitchen/preparation/page.tsx
new file mode 100644
index 0000000..cbaeb5d
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/preparation/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { KitchenPrepPage as default } from "@/modules/hospitality/features/kitchenTicketsPrep";
diff --git a/frontend/app/hospitality/kitchen/queue/error.tsx b/frontend/app/hospitality/kitchen/queue/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/queue/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/queue/loading.tsx b/frontend/app/hospitality/kitchen/queue/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/queue/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/kitchen/queue/page.tsx b/frontend/app/hospitality/kitchen/queue/page.tsx
new file mode 100644
index 0000000..74f76c4
--- /dev/null
+++ b/frontend/app/hospitality/kitchen/queue/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { KitchenQueuePage as default } from "@/modules/hospitality/features/kitchenTicketItems";
diff --git a/frontend/app/hospitality/layout.tsx b/frontend/app/hospitality/layout.tsx
new file mode 100644
index 0000000..82ff53d
--- /dev/null
+++ b/frontend/app/hospitality/layout.tsx
@@ -0,0 +1,5 @@
+"use client";
+
+import { HospitalityRootLayout } from "@/modules/hospitality/components/createPortalLayout";
+
+export default HospitalityRootLayout;
diff --git a/frontend/app/hospitality/loading.tsx b/frontend/app/hospitality/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/availability/error.tsx b/frontend/app/hospitality/menu/availability/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/availability/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/availability/loading.tsx b/frontend/app/hospitality/menu/availability/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/availability/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/availability/page.tsx b/frontend/app/hospitality/menu/availability/page.tsx
new file mode 100644
index 0000000..f8aeb65
--- /dev/null
+++ b/frontend/app/hospitality/menu/availability/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AvailabilityPage as default } from "@/modules/hospitality/features/availabilityWindows";
diff --git a/frontend/app/hospitality/menu/categories/error.tsx b/frontend/app/hospitality/menu/categories/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/categories/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/categories/loading.tsx b/frontend/app/hospitality/menu/categories/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/categories/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/categories/page.tsx b/frontend/app/hospitality/menu/categories/page.tsx
new file mode 100644
index 0000000..57c59e7
--- /dev/null
+++ b/frontend/app/hospitality/menu/categories/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { MenuCategoriesPage as default } from "@/modules/hospitality/features/menuCategories";
diff --git a/frontend/app/hospitality/menu/error.tsx b/frontend/app/hospitality/menu/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/items/error.tsx b/frontend/app/hospitality/menu/items/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/items/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/items/loading.tsx b/frontend/app/hospitality/menu/items/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/items/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/items/page.tsx b/frontend/app/hospitality/menu/items/page.tsx
new file mode 100644
index 0000000..1863a23
--- /dev/null
+++ b/frontend/app/hospitality/menu/items/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { MenuItemsPage as default } from "@/modules/hospitality/features/menuItems";
diff --git a/frontend/app/hospitality/menu/loading.tsx b/frontend/app/hospitality/menu/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/media/error.tsx b/frontend/app/hospitality/menu/media/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/media/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/media/loading.tsx b/frontend/app/hospitality/menu/media/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/media/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/media/page.tsx b/frontend/app/hospitality/menu/media/page.tsx
new file mode 100644
index 0000000..ee38e83
--- /dev/null
+++ b/frontend/app/hospitality/menu/media/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { MediaLibraryPage as default } from "@/modules/hospitality/features/menuMediaRefs";
diff --git a/frontend/app/hospitality/menu/modifier-options/error.tsx b/frontend/app/hospitality/menu/modifier-options/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifier-options/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/modifier-options/loading.tsx b/frontend/app/hospitality/menu/modifier-options/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifier-options/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/modifier-options/page.tsx b/frontend/app/hospitality/menu/modifier-options/page.tsx
new file mode 100644
index 0000000..dd9ff8e
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifier-options/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ModifierOptionsPage as default } from "@/modules/hospitality/features/modifierOptions";
diff --git a/frontend/app/hospitality/menu/modifiers/error.tsx b/frontend/app/hospitality/menu/modifiers/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifiers/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/modifiers/loading.tsx b/frontend/app/hospitality/menu/modifiers/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifiers/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/menu/modifiers/page.tsx b/frontend/app/hospitality/menu/modifiers/page.tsx
new file mode 100644
index 0000000..76d962b
--- /dev/null
+++ b/frontend/app/hospitality/menu/modifiers/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ModifierGroupsPage as default } from "@/modules/hospitality/features/modifierGroups";
diff --git a/frontend/app/hospitality/menu/page.tsx b/frontend/app/hospitality/menu/page.tsx
new file mode 100644
index 0000000..14383c4
--- /dev/null
+++ b/frontend/app/hospitality/menu/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DigitalMenuPage as default } from "@/modules/hospitality/features/menus";
diff --git a/frontend/app/hospitality/not-found.tsx b/frontend/app/hospitality/not-found.tsx
new file mode 100644
index 0000000..5cc4a1b
--- /dev/null
+++ b/frontend/app/hospitality/not-found.tsx
@@ -0,0 +1,10 @@
+import Link from "next/link";
+import { Button, EmptyState } from "@/components/ds";
+
+export default function HospitalityNotFound() {
+ return (
+
+ } />
+
+ );
+}
diff --git a/frontend/app/hospitality/notifications/error.tsx b/frontend/app/hospitality/notifications/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/notifications/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/notifications/loading.tsx b/frontend/app/hospitality/notifications/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/notifications/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/notifications/page.tsx b/frontend/app/hospitality/notifications/page.tsx
new file mode 100644
index 0000000..17217ea
--- /dev/null
+++ b/frontend/app/hospitality/notifications/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { NotificationsPage as default } from "@/modules/hospitality/features/notifications";
diff --git a/frontend/app/hospitality/ordering/cart/error.tsx b/frontend/app/hospitality/ordering/cart/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/ordering/cart/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/ordering/cart/loading.tsx b/frontend/app/hospitality/ordering/cart/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/ordering/cart/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/ordering/cart/page.tsx b/frontend/app/hospitality/ordering/cart/page.tsx
new file mode 100644
index 0000000..d3a97b4
--- /dev/null
+++ b/frontend/app/hospitality/ordering/cart/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CartPage as default } from "@/modules/hospitality/features/carts";
diff --git a/frontend/app/hospitality/ordering/checkout/error.tsx b/frontend/app/hospitality/ordering/checkout/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/ordering/checkout/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/ordering/checkout/loading.tsx b/frontend/app/hospitality/ordering/checkout/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/ordering/checkout/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/ordering/checkout/page.tsx b/frontend/app/hospitality/ordering/checkout/page.tsx
new file mode 100644
index 0000000..98021bd
--- /dev/null
+++ b/frontend/app/hospitality/ordering/checkout/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CheckoutPage as default } from "@/modules/hospitality/features/cartLines";
diff --git a/frontend/app/hospitality/orders/online/error.tsx b/frontend/app/hospitality/orders/online/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/orders/online/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/online/loading.tsx b/frontend/app/hospitality/orders/online/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/orders/online/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/online/page.tsx b/frontend/app/hospitality/orders/online/page.tsx
new file mode 100644
index 0000000..49922d1
--- /dev/null
+++ b/frontend/app/hospitality/orders/online/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { OnlineOrdersPage as default } from "@/modules/hospitality/features/onlineOrders";
diff --git a/frontend/app/hospitality/orders/pickup/error.tsx b/frontend/app/hospitality/orders/pickup/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/orders/pickup/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/pickup/loading.tsx b/frontend/app/hospitality/orders/pickup/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/orders/pickup/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/pickup/page.tsx b/frontend/app/hospitality/orders/pickup/page.tsx
new file mode 100644
index 0000000..d30b16f
--- /dev/null
+++ b/frontend/app/hospitality/orders/pickup/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PickupOrdersPage as default } from "@/modules/hospitality/features/pickupOrders";
diff --git a/frontend/app/hospitality/orders/timeline/error.tsx b/frontend/app/hospitality/orders/timeline/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/orders/timeline/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/timeline/loading.tsx b/frontend/app/hospitality/orders/timeline/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/orders/timeline/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/orders/timeline/page.tsx b/frontend/app/hospitality/orders/timeline/page.tsx
new file mode 100644
index 0000000..f904626
--- /dev/null
+++ b/frontend/app/hospitality/orders/timeline/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { OrderTimelinePage as default } from "@/modules/hospitality/features/orderTimeline";
diff --git a/frontend/app/hospitality/page.tsx b/frontend/app/hospitality/page.tsx
new file mode 100644
index 0000000..c2bfc79
--- /dev/null
+++ b/frontend/app/hospitality/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ExecutiveDashboard as default } from "@/modules/hospitality/features/dashboard";
diff --git a/frontend/app/hospitality/permissions/error.tsx b/frontend/app/hospitality/permissions/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/permissions/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/permissions/loading.tsx b/frontend/app/hospitality/permissions/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/permissions/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/permissions/page.tsx b/frontend/app/hospitality/permissions/page.tsx
new file mode 100644
index 0000000..1cdf56c
--- /dev/null
+++ b/frontend/app/hospitality/permissions/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PermissionsPage as default } from "@/modules/hospitality/features/permissions";
diff --git a/frontend/app/hospitality/pos/coupons/error.tsx b/frontend/app/hospitality/pos/coupons/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/coupons/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/coupons/loading.tsx b/frontend/app/hospitality/pos/coupons/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/coupons/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/coupons/page.tsx b/frontend/app/hospitality/pos/coupons/page.tsx
new file mode 100644
index 0000000..d2deb33
--- /dev/null
+++ b/frontend/app/hospitality/pos/coupons/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CouponsPage as default } from "@/modules/hospitality/features/posDiscountsCoupons";
diff --git a/frontend/app/hospitality/pos/discounts/error.tsx b/frontend/app/hospitality/pos/discounts/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/discounts/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/discounts/loading.tsx b/frontend/app/hospitality/pos/discounts/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/discounts/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/discounts/page.tsx b/frontend/app/hospitality/pos/discounts/page.tsx
new file mode 100644
index 0000000..d151efe
--- /dev/null
+++ b/frontend/app/hospitality/pos/discounts/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DiscountsPage as default } from "@/modules/hospitality/features/posDiscounts";
diff --git a/frontend/app/hospitality/pos/lite/error.tsx b/frontend/app/hospitality/pos/lite/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/lite/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/lite/loading.tsx b/frontend/app/hospitality/pos/lite/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/lite/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/lite/page.tsx b/frontend/app/hospitality/pos/lite/page.tsx
new file mode 100644
index 0000000..9cfe750
--- /dev/null
+++ b/frontend/app/hospitality/pos/lite/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PosLitePage as default } from "@/modules/hospitality/features/posTickets";
diff --git a/frontend/app/hospitality/pos/payments/error.tsx b/frontend/app/hospitality/pos/payments/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/payments/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/payments/loading.tsx b/frontend/app/hospitality/pos/payments/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/payments/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/payments/page.tsx b/frontend/app/hospitality/pos/payments/page.tsx
new file mode 100644
index 0000000..bbd4de4
--- /dev/null
+++ b/frontend/app/hospitality/pos/payments/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PaymentsPage as default } from "@/modules/hospitality/features/posPaymentsList";
diff --git a/frontend/app/hospitality/pos/pro/error.tsx b/frontend/app/hospitality/pos/pro/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/pro/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/pro/loading.tsx b/frontend/app/hospitality/pos/pro/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/pro/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/pro/page.tsx b/frontend/app/hospitality/pos/pro/page.tsx
new file mode 100644
index 0000000..1738f1d
--- /dev/null
+++ b/frontend/app/hospitality/pos/pro/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PosProPage as default } from "@/modules/hospitality/features/posPayments";
diff --git a/frontend/app/hospitality/pos/promotions/error.tsx b/frontend/app/hospitality/pos/promotions/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/promotions/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/promotions/loading.tsx b/frontend/app/hospitality/pos/promotions/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/promotions/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/promotions/page.tsx b/frontend/app/hospitality/pos/promotions/page.tsx
new file mode 100644
index 0000000..a9e44aa
--- /dev/null
+++ b/frontend/app/hospitality/pos/promotions/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PromotionsPage as default } from "@/modules/hospitality/features/featureTogglesPromo";
diff --git a/frontend/app/hospitality/pos/register/error.tsx b/frontend/app/hospitality/pos/register/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/pos/register/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/register/loading.tsx b/frontend/app/hospitality/pos/register/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/pos/register/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/pos/register/page.tsx b/frontend/app/hospitality/pos/register/page.tsx
new file mode 100644
index 0000000..46bb3c3
--- /dev/null
+++ b/frontend/app/hospitality/pos/register/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CashRegisterPage as default } from "@/modules/hospitality/features/posRegisters";
diff --git a/frontend/app/hospitality/qr/menu/error.tsx b/frontend/app/hospitality/qr/menu/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/qr/menu/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/qr/menu/loading.tsx b/frontend/app/hospitality/qr/menu/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/qr/menu/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/qr/menu/page.tsx b/frontend/app/hospitality/qr/menu/page.tsx
new file mode 100644
index 0000000..67cb406
--- /dev/null
+++ b/frontend/app/hospitality/qr/menu/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { QrMenuPage as default } from "@/modules/hospitality/features/qrCodes";
diff --git a/frontend/app/hospitality/qr/ordering/error.tsx b/frontend/app/hospitality/qr/ordering/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/qr/ordering/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/qr/ordering/loading.tsx b/frontend/app/hospitality/qr/ordering/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/qr/ordering/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/qr/ordering/page.tsx b/frontend/app/hospitality/qr/ordering/page.tsx
new file mode 100644
index 0000000..e14953e
--- /dev/null
+++ b/frontend/app/hospitality/qr/ordering/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { QrOrderingPage as default } from "@/modules/hospitality/features/qrOrderingSessions";
diff --git a/frontend/app/hospitality/queue/error.tsx b/frontend/app/hospitality/queue/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/queue/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/queue/loading.tsx b/frontend/app/hospitality/queue/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/queue/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/queue/page.tsx b/frontend/app/hospitality/queue/page.tsx
new file mode 100644
index 0000000..5a73c1d
--- /dev/null
+++ b/frontend/app/hospitality/queue/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { QueuePage as default } from "@/modules/hospitality/features/waitlist";
diff --git a/frontend/app/hospitality/receipts/error.tsx b/frontend/app/hospitality/receipts/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/receipts/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/receipts/loading.tsx b/frontend/app/hospitality/receipts/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/receipts/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/receipts/page.tsx b/frontend/app/hospitality/receipts/page.tsx
new file mode 100644
index 0000000..487c0f2
--- /dev/null
+++ b/frontend/app/hospitality/receipts/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ReceiptsPage as default } from "@/modules/hospitality/features/receipts";
diff --git a/frontend/app/hospitality/reports/error.tsx b/frontend/app/hospitality/reports/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/reports/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/reports/loading.tsx b/frontend/app/hospitality/reports/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/reports/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/reports/page.tsx b/frontend/app/hospitality/reports/page.tsx
new file mode 100644
index 0000000..f5cc2cb
--- /dev/null
+++ b/frontend/app/hospitality/reports/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ReportsPage as default } from "@/modules/hospitality/features/analyticsReports";
diff --git a/frontend/app/hospitality/reservations/error.tsx b/frontend/app/hospitality/reservations/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/reservations/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/reservations/loading.tsx b/frontend/app/hospitality/reservations/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/reservations/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/reservations/page.tsx b/frontend/app/hospitality/reservations/page.tsx
new file mode 100644
index 0000000..ddfd70b
--- /dev/null
+++ b/frontend/app/hospitality/reservations/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ReservationsPage as default } from "@/modules/hospitality/features/reservations";
diff --git a/frontend/app/hospitality/roles/error.tsx b/frontend/app/hospitality/roles/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/roles/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/roles/loading.tsx b/frontend/app/hospitality/roles/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/roles/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/roles/page.tsx b/frontend/app/hospitality/roles/page.tsx
new file mode 100644
index 0000000..77b4dbf
--- /dev/null
+++ b/frontend/app/hospitality/roles/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { RolesPage as default } from "@/modules/hospitality/features/roles";
diff --git a/frontend/app/hospitality/settings/error.tsx b/frontend/app/hospitality/settings/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/settings/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/settings/loading.tsx b/frontend/app/hospitality/settings/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/settings/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/settings/page.tsx b/frontend/app/hospitality/settings/page.tsx
new file mode 100644
index 0000000..742ed7b
--- /dev/null
+++ b/frontend/app/hospitality/settings/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SettingsPage as default } from "@/modules/hospitality/features/settings";
diff --git a/frontend/app/hospitality/tables/error.tsx b/frontend/app/hospitality/tables/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/hospitality/tables/error.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { ErrorState } from "@/components/ds";
+
+export default function Error({ error, reset }: { error: Error; reset: () => void }) {
+ return ;
+}
diff --git a/frontend/app/hospitality/tables/loading.tsx b/frontend/app/hospitality/tables/loading.tsx
new file mode 100644
index 0000000..9385765
--- /dev/null
+++ b/frontend/app/hospitality/tables/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/hospitality/tables/page.tsx b/frontend/app/hospitality/tables/page.tsx
new file mode 100644
index 0000000..bd576d4
--- /dev/null
+++ b/frontend/app/hospitality/tables/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { TablesPage as default } from "@/modules/hospitality/features/tables";
diff --git a/frontend/docs/frontend-architecture.md b/frontend/docs/frontend-architecture.md
new file mode 100644
index 0000000..54c4420
--- /dev/null
+++ b/frontend/docs/frontend-architecture.md
@@ -0,0 +1,251 @@
+# TorbatYar Frontend Architecture
+
+**Version:** Phase 1 (shared scaffold)
+**Date:** 2026-07-26
+**Status:** Architecture created — business modules not migrated
+
+---
+
+## 1. Overview
+
+TorbatYar SuperApp frontend is a **Next.js 15 App Router monolith** hosting the platform shell and multiple business modules (Accounting, Beauty, Healthcare, Platform Admin). Phase 1 introduces a **formal shared architecture** under `frontend/shared/` and a **future module home** under `frontend/modules/` without moving existing code or changing routes/imports.
+
+```
+frontend/
+├── app/ # App Router (unchanged — 350 routes)
+├── components/ # Legacy components (unchanged)
+├── hooks/ # Legacy hooks (unchanged)
+├── lib/ # Legacy API clients & utilities (unchanged)
+├── modules/ # 🆕 Future business module packages (scaffold)
+├── shared/ # 🆕 Platform layer (scaffold)
+├── docs/ # Architecture & audit documentation
+└── scripts/ # validate-architecture.mjs, generators
+```
+
+---
+
+## 2. Architectural Layers
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│ app/ Routes & layouts (URL mapping) │
+└────────────────────────────┬────────────────────────────────┘
+ │
+┌────────────────────────────▼────────────────────────────────┐
+│ modules/ (future) Business logic & module UI │
+│ components/{module}/ (legacy — current location) │
+└────────────────────────────┬────────────────────────────────┘
+ │
+┌────────────────────────────▼────────────────────────────────┐
+│ shared/ Cross-module platform layer │
+│ components/ds/ (legacy DS — current location) │
+└────────────────────────────┬────────────────────────────────┘
+ │
+┌────────────────────────────▼────────────────────────────────┐
+│ lib/auth, lib/utils Platform utilities │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### Dependency rule
+
+**Imports flow downward only.** Shared code never imports business modules. Business modules never import sibling modules.
+
+---
+
+## 3. Directory Reference
+
+### 3.1 `modules/` — Business module packages
+
+| Path | Purpose |
+|------|---------|
+| [modules/](../modules/README.md) | Future home for self-contained business modules |
+
+**Phase 1:** README only. Accounting, Beauty, Healthcare migrated; **CRM** active under `modules/crm/`.
+
+### 3.2 `shared/` — Platform layer
+
+| Directory | Purpose | Documentation |
+|-----------|---------|---------------|
+| `shared/ui/` | UI atoms (Button, Input, Dialog) | [README](../shared/ui/README.md) |
+| `shared/forms/` | Form fields, RHF helpers | [README](../shared/forms/README.md) |
+| `shared/layouts/` | Shells, portal chrome | [README](../shared/layouts/README.md) |
+| `shared/tables/` | DataTable, pagination | [README](../shared/tables/README.md) |
+| `shared/charts/` | Chart wrappers | [README](../shared/charts/README.md) |
+| `shared/calendar/` | Jalali date pickers | [README](../shared/calendar/README.md) |
+| `shared/hooks/` | Cross-module hooks | [README](../shared/hooks/README.md) |
+| `shared/utils/` | Pure utilities | [README](../shared/utils/README.md) |
+| `shared/api/` | HTTP client infrastructure | [README](../shared/api/README.md) |
+| `shared/theme/` | White-label theming | [README](../shared/theme/README.md) |
+| `shared/providers/` | React providers | [README](../shared/providers/README.md) |
+| `shared/icons/` | Icon exports | [README](../shared/icons/README.md) |
+| `shared/types/` | Shared TypeScript types | [README](../shared/types/README.md) |
+| `shared/constants/` | App-wide constants | [README](../shared/constants/README.md) |
+| `shared/design-system/` | Tokens & patterns | [README](../shared/design-system/README.md) |
+
+**Phase 1:** Each subdirectory contains a README mapping to legacy source locations. No code migrated.
+
+### 3.3 Legacy locations (unchanged)
+
+| Concern | Current path | Future path |
+|---------|--------------|-------------|
+| Routes | `app/{module}/` | Unchanged (may re-export from modules/) |
+| Accounting UI | `components/accounting/` | `modules/accounting/components/` |
+| Beauty UI | `components/beauty/` | `modules/beauty/components/` |
+| Healthcare UI | `components/healthcare/` | `modules/healthcare/components/` |
+| Design system | `components/ds/` | `shared/ui/` + `shared/design-system/` |
+| Hooks | `hooks/` | `shared/hooks/` + `modules/{id}/hooks/` |
+| API clients | `lib/*-api.ts` | `shared/api/` + `modules/{id}/api/` |
+
+---
+
+## 4. Business Modules (legacy)
+
+| Module | Route prefix | Component root | BFF |
+|--------|--------------|----------------|-----|
+| Platform | `/`, `/dashboard`, `/login` | `components/ui/`, root | — |
+| Admin | `/admin` | `components/admin/` | — |
+| Accounting | `/accounting` | `components/accounting/` | `/api/accounting/*` |
+| Beauty | `/beauty` | `modules/beauty/` | `/api/beauty-business/*` |
+| Healthcare | `/healthcare` | `src/modules/healthcare/` | `/api/healthcare/*` |
+| Hospitality | `/hospitality` | `modules/hospitality/` | `/api/hospitality/*` |
+
+| CRM | `/crm` | `modules/crm/` | `/api/crm/*` |
+
+Delivery: healthcare pharmacy portal only.
+
+---
+
+## 5. Path Aliases
+
+Configured in `tsconfig.json`:
+
+| Alias | Maps to | Status |
+|-------|---------|--------|
+| `@/*` | `./*` | Active — all existing imports |
+| `@/shared/*` | `./shared/*` | Active — for future migration |
+| `@/modules/*` | `./modules/*` | Active — for future modules |
+
+**Phase 1 does not change existing imports.** Legacy `@/components/ds` remains canonical until Phase 2 migration.
+
+---
+
+## 6. Module Boundaries
+
+### Allowed
+
+```
+app/accounting/ → components/accounting/, components/ds/, hooks/, lib/accounting-api
+app/beauty/ → components/beauty/, components/ds/, hooks/, lib/beauty-business-api
+app/healthcare/ → components/healthcare/, components/ds/, hooks/, lib/healthcare-api
+components/ds/ → lib/utils only
+shared/ (future) → other shared/*, lib/auth, lib/utils
+```
+
+### Forbidden
+
+```
+accounting ✕ beauty | healthcare
+beauty ✕ accounting | healthcare
+healthcare ✕ accounting | beauty
+ds/shared ✕ any business module
+```
+
+Enforced by:
+- `.eslintrc.json` — `no-restricted-imports` with per-module overrides
+- `npm run validate:architecture` — CI-style import graph scan
+
+Full matrix: [module-boundaries.md](./module-boundaries.md)
+
+---
+
+## 7. Providers & State
+
+| Layer | Technology | Location |
+|-------|------------|----------|
+| Server state | TanStack Query | `components/providers/AppProviders.tsx` |
+| Auth session | `useMe` hook | `hooks/useMe.ts` |
+| Tenant | `useTenantId` | `hooks/useTenantId.ts` |
+| Theme | CSS vars + `ThemeProvider` | `lib/theme.ts`, `styles/globals.css` |
+| Color mode | Context | `components/providers/ColorModeProvider.tsx` |
+
+Future home: `shared/providers/`, `shared/hooks/`
+
+---
+
+## 8. API Architecture
+
+Browser → same-origin BFF → microservice:
+
+```
+/accounting pages → accountingApi → /api/accounting/* → :8002
+/beauty pages → beautyBusinessApi → /api/beauty-business/* → :8011
+/healthcare pages → healthcareApi → /api/healthcare/* → :8010
+/admin, /dashboard → api → NEXT_PUBLIC_BACKEND_URL → :8000
+```
+
+Future shared fetch factory: `shared/api/createApiClient.ts` (Phase 3)
+
+---
+
+## 9. Validation Commands
+
+| Command | Purpose |
+|---------|---------|
+| `npm run build` | Production build + type check |
+| `npm run typecheck` | TypeScript only (`tsc --noEmit`) |
+| `npm run lint` | ESLint + Next.js rules + boundary overrides |
+| `npm run validate:architecture` | Directory scaffold + import boundary scan |
+
+### Phase 1 validation results (2026-07-26)
+
+| Check | Result | Notes |
+|-------|--------|-------|
+| `validate:architecture` | ✅ Pass | Scaffold complete; zero cross-domain imports |
+| `lint` | ✅ Pass | 3 warnings in `inventory/issues/page.tsx` (pre-existing) |
+| `typecheck` | ❌ Fail | Pre-existing: `Button` `loading` prop in `purchase/returns/page.tsx` (Phase 0) |
+| `build` | ❌ Fail | Same TypeScript error as typecheck |
+
+---
+
+## 10. Migration Phases
+
+| Phase | Focus | Status |
+|-------|-------|--------|
+| 0 | Stabilize build, remove dead code | Pending |
+| **1** | **Shared scaffold + boundary enforcement** | **✅ Current** |
+| 2 | Extract portal infrastructure to `shared/layouts/` | Planned |
+| 3 | Split API client monoliths | Planned |
+| 4 | Extract accounting route logic | Planned |
+| 5 | Middleware, error boundaries | Planned |
+| 6 | New module template | Ongoing |
+
+Details: [frontend-migration-plan.md](./frontend-migration-plan.md)
+
+---
+
+## 11. Related Documentation
+
+| Document | Description |
+|----------|-------------|
+| [frontend-architecture-audit.md](./frontend-architecture-audit.md) | Full audit (pre-Phase 1) |
+| [frontend-migration-plan.md](./frontend-migration-plan.md) | Phased roadmap |
+| [module-boundaries.md](./module-boundaries.md) | Import ownership rules |
+| [shared-components-report.md](./shared-components-report.md) | DS inventory |
+| [dependency-report.md](./dependency-report.md) | npm & API clients |
+| [import-report.md](./import-report.md) | Import patterns |
+| [refactor-risk-analysis.md](./refactor-risk-analysis.md) | Refactor risks |
+
+---
+
+## 12. Phase 1 Exit Criteria
+
+| Criterion | Status |
+|-----------|--------|
+| `modules/` scaffold with README | ✅ |
+| `shared/*` subdirectories with README | ✅ |
+| Path aliases for `@/shared/*`, `@/modules/*` | ✅ |
+| ESLint cross-domain import rules | ✅ |
+| Architecture validation script | ✅ |
+| No business module migrations | ✅ |
+| No route changes | ✅ |
+| No import changes in existing code | ✅ |
diff --git a/frontend/docs/hospitality-component-map.md b/frontend/docs/hospitality-component-map.md
new file mode 100644
index 0000000..4a97014
--- /dev/null
+++ b/frontend/docs/hospitality-component-map.md
@@ -0,0 +1,65 @@
+# Hospitality Component Map
+
+**Module root:** `frontend/modules/hospitality/`
+
+## Shell & navigation
+
+| Component | Path | Role |
+|-----------|------|------|
+| `HospitalityPortalShell` | `components/HospitalityPortalShell.tsx` | Sidebar, mobile menu, theme toggle |
+| `HospitalityRootLayout` | `components/createPortalLayout.tsx` | Auth + tenant gate; hub exception |
+| `HospitalityBreadcrumbs` | `components/HospitalityBreadcrumbs.tsx` | Page breadcrumb trail |
+| `HospitalityBundleGate` | `components/HospitalityBundleGate.tsx` | Feature/bundle visibility |
+
+## Data pages
+
+| Component | Path | Role |
+|-----------|------|------|
+| `createHospitalityListPage` | `components/HospitalityListCrudPage.tsx` | Factory for list+CRUD pages |
+| `HospitalityTablePage` | `design-system/HospitalityTablePage.tsx` | Search + table + header |
+| `HospitalityStatusChip` | `design-system/HospitalityStatusChip.tsx` | Lifecycle status badge |
+
+## Pages (features)
+
+| Feature file | Route | Primary API |
+|--------------|-------|-------------|
+| `dashboard.tsx` | `/hospitality` | venues, reservations, posTickets, kitchenTickets |
+| `hub.tsx` | `/hospitality/hub` | — |
+| `venues.tsx` | `/hospitality/branches` | `venues` |
+| `branches.tsx` | `/hospitality/branches/list` | `branches` |
+| `diningAreas.tsx` | `/hospitality/dining-areas` | `diningAreas` |
+| `tables.tsx` | `/hospitality/tables` | `tables` |
+| `menus.tsx` | `/hospitality/menu` | `menus` |
+| `reservations.tsx` | `/hospitality/reservations` | `reservations` |
+| `kitchenTickets.tsx` | `/hospitality/kitchen/display` | `kitchenTickets` |
+| `posTickets.tsx` | `/hospitality/pos/lite` | `posTickets` |
+| `analyticsSnapshots.tsx` | `/hospitality/analytics` | `analyticsSnapshots` |
+| `tenantBundles.tsx` | `/hospitality/bundles` | `tenantBundles` |
+| `health.tsx` | `/hospitality/health` | `health()` |
+| `capabilities.tsx` | `/hospitality/capabilities` | `capabilities()` |
+
+*(54 feature modules total — see routing map.)*
+
+## Hooks
+
+| Hook | Path |
+|------|------|
+| `useHospitalityCapabilities` | `hooks/useHospitalityCapabilities.ts` |
+| `useHospitalityHealth` | `hooks/useHospitalityCapabilities.ts` |
+| `useHospitalityLookups` | `hooks/useHospitalityCapabilities.ts` |
+
+## Services
+
+| Service | Path |
+|---------|------|
+| `hospitalityApi` | `services/hospitality-api.ts` |
+| BFF proxy | `app/api/hospitality/[...path]/route.ts` |
+
+## Shared platform (external)
+
+| Import | Source |
+|--------|--------|
+| `@/components/ds` | Design system |
+| `@/hooks/useTenantId` | Tenant context |
+| `@/hooks/useMe` | Auth session |
+| `@/components/AuthGuard` | Route protection |
diff --git a/frontend/docs/hospitality-design-guidelines.md b/frontend/docs/hospitality-design-guidelines.md
new file mode 100644
index 0000000..43353e2
--- /dev/null
+++ b/frontend/docs/hospitality-design-guidelines.md
@@ -0,0 +1,66 @@
+# Hospitality Design Guidelines
+
+## Color & theme
+
+Use CSS variables from `globals.css`:
+
+```css
+--hospitality-accent: #ea580c; /* light */
+--hospitality-accent-soft: rgba(234, 88, 12, 0.12);
+--hospitality-warm: #f97316;
+```
+
+Dark mode equivalents use `#fb923c` accent.
+
+**Do not** hardcode hex in components — reference `var(--hospitality-accent)`.
+
+## Typography
+
+- Headings: inherit global `text-secondary`, semibold
+- Muted text: `text-[var(--muted)]`
+- Monospace for codes/tokens: `font-mono text-sm`
+
+## Spacing
+
+- Page padding: `p-4 lg:p-6`
+- Card gaps: `gap-4`
+- Form fields: `space-y-3`
+- Nav items: `py-2.5 px-3`, rounded-xl
+
+## Components
+
+| Pattern | Guideline |
+|---------|-----------|
+| Primary action | `Button` default variant, hospitality accent via active nav |
+| Destructive | `ConfirmDialog` with `danger` |
+| Status | `HospitalityStatusChip` or `Badge` tones |
+| Tables | Always wrap with search in `HospitalityTablePage` |
+| Empty states | Action button when create is available |
+
+## Navigation
+
+- Groups collapse on mobile drawer
+- Hide nav items when capability flag is `false`
+- Hub link at sidebar footer
+
+## Forms
+
+- react-hook-form + zod validation
+- Required fields marked in schema messages (Persian)
+- Select for enums; Input for text/number
+
+## Icons
+
+Lucide icons in nav config (`constants/portals.ts`).
+
+## Motion
+
+- Sidebar chevron: `transition-transform`
+- Nav hover: `transition-colors duration-200`
+- Avoid heavy animations on data tables
+
+## RTL
+
+- Search icon: `right-3`, input `pr-10`
+- Sidebar border: `border-l` (RTL shell)
+- Breadcrumb chevron rotated for RTL flow
diff --git a/frontend/docs/hospitality-responsive-report.md b/frontend/docs/hospitality-responsive-report.md
new file mode 100644
index 0000000..27d5923
--- /dev/null
+++ b/frontend/docs/hospitality-responsive-report.md
@@ -0,0 +1,55 @@
+# Hospitality Responsive Report
+
+**Date:** 2026-07-26
+**Product:** Torbat Food
+
+## Breakpoints strategy
+
+Mobile-first Tailwind utilities aligned with shared DS:
+
+| Breakpoint | Layout behavior |
+|------------|-----------------|
+| `< lg` | Hidden sidebar; hamburger opens full-height drawer |
+| `≥ lg` | Fixed 256px sidebar (`w-64`), content flex-1 |
+| `sm` | Dashboard stat grid 2 columns |
+| `xl` | Dashboard stat grid 4 columns |
+
+## Component responsiveness
+
+| Component | Mobile | Tablet | Desktop |
+|-----------|--------|--------|---------|
+| `HospitalityPortalShell` | Drawer nav | Drawer nav | Persistent sidebar |
+| `HospitalityTablePage` | Horizontal scroll via DataTable | Full width search | Search max-w-md |
+| Dashboard StatCards | Stack 1→2 cols | 2 cols | 4 cols |
+| Dialogs | Full-width modal (DS) | Centered | Centered |
+| Hub card | max-w-lg centered | Same | Same |
+
+## Touch targets
+
+- Nav links: `py-2.5` (~40px+ height)
+- Icon buttons: DS `size="icon"` minimum
+- Mobile menu button in header
+
+## Performance on mobile
+
+- Route-level code splitting via Next.js dynamic imports (default App Router)
+- TanStack Query `staleTime` on capabilities (60s) reduces refetch
+- Paginated lists (20 items) limit DOM size
+
+## RTL on small screens
+
+- Mobile drawer anchors from right (`absolute right-0`)
+- Search icon positioned for RTL input padding
+
+## Testing checklist
+
+- [ ] iPhone SE: open drawer, navigate to menu items
+- [ ] iPad: sidebar visible at lg
+- [ ] Desktop 1920px: dashboard 4-column stats
+- [ ] Dark mode toggle on mobile header/footer
+
+## Lighthouse notes
+
+- Use real backend in dev for accurate TTFB
+- Images in menu media use refs only (no binary in hospitality service)
+- Minimize layout shift: LoadingState skeletons on route transitions
diff --git a/frontend/docs/hospitality-routing-map.md b/frontend/docs/hospitality-routing-map.md
new file mode 100644
index 0000000..06d3f54
--- /dev/null
+++ b/frontend/docs/hospitality-routing-map.md
@@ -0,0 +1,84 @@
+# Hospitality Routing Map
+
+**Base:** `/hospitality`
+**Generator:** `frontend/scripts/scaffold-hospitality-module.mjs`
+
+## Route pattern
+
+```tsx
+// app/hospitality/{path}/page.tsx
+"use client";
+export { PageName as default } from "@/modules/hospitality/features/{feature}";
+```
+
+## All routes (54)
+
+| Path | Feature export | Bundle gate |
+|------|----------------|-------------|
+| `/hospitality` | ExecutiveDashboard | — |
+| `/hospitality/hub` | HospitalityHub | — |
+| `/hospitality/branches` | BranchesPage (venues) | — |
+| `/hospitality/branches/list` | BranchListPage | — |
+| `/hospitality/dining-areas` | DiningAreasPage | — |
+| `/hospitality/tables` | TablesPage | — |
+| `/hospitality/floor-map` | FloorMapPage | pos_pro |
+| `/hospitality/reservations` | ReservationsPage | reservation |
+| `/hospitality/queue` | QueuePage | table_service |
+| `/hospitality/customers` | CustomersPage | crm_connector |
+| `/hospitality/guests` | GuestsPage | qr_menu |
+| `/hospitality/menu` | DigitalMenuPage | digital_menu |
+| `/hospitality/menu/categories` | MenuCategoriesPage | digital_menu |
+| `/hospitality/menu/items` | MenuItemsPage | digital_menu |
+| `/hospitality/menu/modifiers` | ModifierGroupsPage | digital_menu |
+| `/hospitality/menu/modifier-options` | ModifierOptionsPage | digital_menu |
+| `/hospitality/menu/availability` | AvailabilityPage | digital_menu |
+| `/hospitality/menu/media` | MediaLibraryPage | digital_menu |
+| `/hospitality/qr/menu` | QrMenuPage | qr_menu |
+| `/hospitality/qr/ordering` | QrOrderingPage | qr_ordering |
+| `/hospitality/ordering/cart` | CartPage | qr_ordering |
+| `/hospitality/ordering/checkout` | CheckoutPage | qr_ordering |
+| `/hospitality/kitchen/display` | KitchenDisplayPage | kitchen |
+| `/hospitality/kitchen/queue` | KitchenQueuePage | kitchen |
+| `/hospitality/kitchen/preparation` | KitchenPrepPage | kitchen |
+| `/hospitality/pos/lite` | PosLitePage | pos_lite |
+| `/hospitality/pos/pro` | PosProPage | pos_pro |
+| `/hospitality/pos/register` | CashRegisterPage | pos_lite |
+| `/hospitality/pos/payments` | PaymentsPage | pos_pro |
+| `/hospitality/pos/discounts` | DiscountsPage | pos_pro |
+| `/hospitality/pos/coupons` | CouponsPage | pos_pro |
+| `/hospitality/pos/promotions` | PromotionsPage | pos_pro |
+| `/hospitality/integrations/delivery` | DeliveryIntegrationPage | delivery_connector |
+| `/hospitality/orders/pickup` | PickupOrdersPage | qr_ordering |
+| `/hospitality/orders/online` | OnlineOrdersPage | qr_ordering |
+| `/hospitality/orders/timeline` | OrderTimelinePage | kitchen |
+| `/hospitality/invoices` | InvoicesPage | pos_lite |
+| `/hospitality/receipts` | ReceiptsPage | pos_lite |
+| `/hospitality/reports` | ReportsPage | analytics |
+| `/hospitality/analytics` | AnalyticsPage | analytics |
+| `/hospitality/inventory` | InventoryPage | analytics |
+| `/hospitality/integrations/crm` | CrmIntegrationPage | crm_connector |
+| `/hospitality/integrations/loyalty` | LoyaltyIntegrationPage | loyalty_connector |
+| `/hospitality/integrations/communication` | CommunicationIntegrationPage | communication_connector |
+| `/hospitality/integrations/website` | WebsiteIntegrationPage | website_connector |
+| `/hospitality/integrations/marketplace` | MarketplaceIntegrationPage | — |
+| `/hospitality/settings` | SettingsPage | — |
+| `/hospitality/permissions` | PermissionsPage | — |
+| `/hospitality/roles` | RolesPage | — |
+| `/hospitality/audit` | AuditLogsPage | — |
+| `/hospitality/notifications` | NotificationsPage | — |
+| `/hospitality/health` | HealthPage | — |
+| `/hospitality/capabilities` | CapabilitiesPage | — |
+| `/hospitality/bundles` | BundlesPage | — |
+
+## Layout files
+
+| File | Purpose |
+|------|---------|
+| `app/hospitality/layout.tsx` | Root portal layout |
+| `app/hospitality/not-found.tsx` | 404 |
+| Each route `loading.tsx` | Skeleton |
+| Each route `error.tsx` | Error boundary |
+
+## BFF
+
+`GET|POST|PATCH|PUT|DELETE /api/hospitality/{path}` → `HOSPITALITY_SERVICE_URL` (default `http://localhost:8009`)
diff --git a/frontend/docs/hospitality-ui-specification.md b/frontend/docs/hospitality-ui-specification.md
new file mode 100644
index 0000000..334735d
--- /dev/null
+++ b/frontend/docs/hospitality-ui-specification.md
@@ -0,0 +1,81 @@
+# Hospitality UI Specification — Torbat Food
+
+**Version:** 1.0
+**Date:** 2026-07-26
+**Product:** Torbat Food (Hospitality Platform)
+
+## 1. Product vision
+
+Enterprise hospitality platform supporting cafe, restaurant, fast food, bakery, cloud kitchen, food court, catering, and related formats. UI adapts to enabled bundles; disabled modules are hidden from navigation.
+
+## 2. Design principles
+
+| Principle | Implementation |
+|-----------|----------------|
+| RTL first | App shell, tables, forms use RTL layout |
+| Mobile first | Collapsible sidebar, touch-friendly targets (min 44px) |
+| Low cognitive load | Grouped nav, consistent page headers, breadcrumbs |
+| Warm & welcoming | Orange hospitality accent (`--hospitality-accent`) |
+| Enterprise grade | Real API data, permission/bundle gates, audit export |
+| Performance | TanStack Query caching, paginated lists, code-split routes |
+
+## 3. Layout system
+
+- **Root layout:** `HospitalityRootLayout` — AuthGuard + tenant gate
+- **Hub (`/hospitality/hub`):** Portal picker without sidebar
+- **Management:** `HospitalityPortalShell` — sidebar nav, mobile drawer, dark mode toggle
+
+## 4. Page template (list/CRUD)
+
+Every data page uses `createHospitalityListPage`:
+
+1. Breadcrumb
+2. PageHeader (title, description, actions)
+3. Search toolbar + pagination + CSV export
+4. DataTable with status chips
+5. Dialog create/edit, ConfirmDialog delete
+6. Loading / empty / error / bundle-disabled states
+
+## 5. Dashboard (Executive)
+
+Widgets wired to live APIs:
+
+- Venues count
+- Reservations count
+- Open POS tickets
+- Active kitchen tickets
+- Quick actions (reservations, kitchen, POS, analytics)
+
+## 6. Bundle-gated modules
+
+| Bundle | UI sections |
+|--------|-------------|
+| `digital_menu` | Menu, categories, items, modifiers, media |
+| `qr_menu` / `qr_ordering` | QR menu, ordering, cart, checkout |
+| `table_service` / `reservation` | Reservations, queue, guests |
+| `pos_lite` / `pos_pro` | POS, register, payments, discounts |
+| `kitchen` | Display, queue, preparation |
+| `*_connector` | Integration registration pages |
+| `analytics` | Reports, snapshots, inventory overview |
+
+## 7. Shared components
+
+From `@/components/ds`: Button, Input, Dialog, Drawer, DataTable, PageHeader, StatCard, Badge, LoadingState, ErrorState, EmptyState, FormField, Select, ConfirmDialog.
+
+Module-specific: `HospitalityTablePage`, `HospitalityStatusChip`, `HospitalityBundleGate`.
+
+## 8. Accessibility
+
+- `aria-label` on search, icon buttons
+- Keyboard: hub → management navigation via standard links
+- Focus visible on interactive elements (DS defaults)
+
+## 9. API contract
+
+All pages consume `hospitalityApi` → BFF `/api/hospitality` → service `:8009`.
+
+Headers: `Authorization`, `X-Tenant-ID`.
+
+## 10. Supported venue formats
+
+Backend `VenueFormat` enum surfaced in venue create (future): cafe, coffee_shop, restaurant, fast_food, bakery, pastry, ice_cream, juice_bar, cloud_kitchen, food_court, catering, take_away, other.
diff --git a/frontend/docs/hospitality-validation-report.md b/frontend/docs/hospitality-validation-report.md
new file mode 100644
index 0000000..82439ce
--- /dev/null
+++ b/frontend/docs/hospitality-validation-report.md
@@ -0,0 +1,67 @@
+# Hospitality Validation Report
+
+**Date:** 2026-07-26
+**Scope:** Torbat Food frontend (`frontend/modules/hospitality`, `app/hospitality`)
+
+## Automated checks
+
+| Check | Command | Result |
+|-------|---------|--------|
+| Route scaffold | `node scripts/validate-hospitality-routes.mjs` | ✅ 54 routes, all feature files present |
+| ESLint (hospitality) | `npm run lint` | ✅ No hospitality warnings |
+| TypeScript (hospitality) | `npm run typecheck` | ✅ No hospitality errors |
+| API client generated | `scripts/gen-hospitality-api.mjs` | ✅ All Phase 12.0–12.8 resources |
+| BFF proxy | `app/api/hospitality/[...path]/route.ts` | ✅ Present |
+
+## Architecture compliance
+
+| Rule | Status |
+|------|--------|
+| Business logic in `modules/hospitality/` | ✅ |
+| Thin routes in `app/hospitality/` | ✅ |
+| No backend modifications | ✅ |
+| No cross-module imports | ✅ |
+| Shared DS only (`@/components/ds`) | ✅ |
+| Real API (no mocks) | ✅ |
+| Tenant header via API client | ✅ |
+| Bundle feature gating | ✅ `HospitalityBundleGate` + nav filter |
+
+## Page quality gates
+
+| Gate | Coverage |
+|------|----------|
+| Loading state | ✅ `loading.tsx` per route + query loaders |
+| Error state | ✅ `error.tsx` + `HospitalityPageError` |
+| Empty state | ✅ DataTable + EmptyState |
+| Permission state | ✅ 403 handling in page error |
+| Bundle disabled | ✅ EmptyState in bundle gate |
+| Search | ✅ HospitalityTablePage |
+| Pagination | ✅ List page toolbar |
+| Export | ✅ CSV export helper |
+| Breadcrumb | ✅ HospitalityBreadcrumbs |
+| Create dialog | ✅ Where API supports create |
+| Delete confirm | ✅ Where API supports remove |
+
+## API connection matrix
+
+All list pages call `hospitalityApi.{resource}.list(tenantId)` against live backend paths under `/api/v1/*`.
+
+Action endpoints wired in API client:
+
+- `reservations.updateStatus`, `waitlist.updateStatus`, `kitchenTickets.updateStatus`
+- `posShifts.close`, `posTickets.void`, `qrOrderingSessions.submit`
+- `tenantBundles.activate/deactivate`, `featureToggles.upsert`, `analyticsSnapshots.refresh`
+
+## Known limitations (honest)
+
+1. **Generic create forms** — Many resources require `venue_id` / foreign keys; create dialogs use minimal fields; extend per-resource as needed.
+2. **No update API** on Phase 12.1+ catalog entities — edit actions only where backend PATCH exists (venues, branches, menus, configurations).
+3. **Customers / marketplace** — Mapped to connector registrations/dispatches (no native CRM customer aggregate in hospitality DB).
+4. **Platform typecheck** — Pre-existing CRM resolver errors unrelated to hospitality.
+5. **Runtime E2E** — Requires hospitality service running on :8009 with valid tenant JWT.
+
+## Recommendations
+
+1. Add resource-specific create wizards (venue picker, menu item with category).
+2. Add `validate-hospitality` to CI alongside `validate:architecture`.
+3. Extend ESLint boundary rules for `modules/hospitality`.
diff --git a/frontend/modules/README.md b/frontend/modules/README.md
new file mode 100644
index 0000000..435ef77
--- /dev/null
+++ b/frontend/modules/README.md
@@ -0,0 +1,53 @@
+# `frontend/modules/`
+
+**Status:** Scaffold — business modules not migrated yet
+**Phase:** Migration Phase 1 (shared architecture creation)
+
+## Purpose
+
+Future home for **self-contained business module packages**. Each module will own its routes (via `app/` re-exports or colocated route adapters), components, hooks, API client, and types — without importing sibling modules.
+
+## Current state
+
+| Module | Status | Location |
+|--------|--------|----------|
+| **beauty** | ✅ Migrated | `modules/beauty/` — see [beauty-migration-report.md](../docs/beauty-migration-report.md) |
+| **hospitality** | ✅ Migrated | `modules/hospitality/` — Torbat Food |
+| accounting | Legacy | `components/accounting/`, `lib/accounting-api.ts` |
+| healthcare | Legacy | `components/healthcare/`, `lib/healthcare-api.ts` |
+| admin | Legacy | `components/admin/` |
+
+Business modules not yet migrated remain in legacy paths until their migration phase:
+
+| Module ID | Route prefix | Legacy component root | Legacy API client |
+|-----------|--------------|----------------------|-------------------|
+| accounting | `/accounting` | `components/accounting/` | `lib/accounting-api.ts` |
+| beauty | `/beauty` | `components/beauty/` | `lib/beauty-business-api.ts` |
+| healthcare | `/healthcare` | `components/healthcare/` | `lib/healthcare-api.ts` |
+| admin | `/admin` | `components/admin/` | `lib/api.ts` |
+
+CRM and other future modules will be scaffolded here when added.
+
+## Planned structure (per module)
+
+```
+modules/
+└── {module-id}/
+ ├── README.md # Module ownership and boundaries
+ ├── components/ # Module-only UI (portals, pages, DS)
+ ├── hooks/ # Module-only hooks
+ ├── api/ # Module API client (split from lib/)
+ ├── types/ # Module TypeScript types
+ └── constants/ # Nav configs, feature flags
+```
+
+## Import rules
+
+- Modules **MAY** import from `shared/` and platform utilities (`lib/auth.ts`, `lib/utils.ts`).
+- Modules **MUST NOT** import from other `modules/*` or legacy `components/{other-module}/`.
+- `app/{module}/` routes stay unchanged during Phase 1; they continue importing legacy paths.
+
+## Related docs
+
+- [../docs/frontend-architecture.md](../docs/frontend-architecture.md)
+- [../docs/module-boundaries.md](../docs/module-boundaries.md)
diff --git a/frontend/modules/hospitality/README.md b/frontend/modules/hospitality/README.md
new file mode 100644
index 0000000..90a6b29
--- /dev/null
+++ b/frontend/modules/hospitality/README.md
@@ -0,0 +1,43 @@
+# Hospitality Module (Torbat Food)
+
+**Commercial product:** Torbat Food
+**Route prefix:** `/hospitality`
+**Backend:** `hospitality-service` port 8009
+**BFF:** `/api/hospitality/*`
+
+## Structure
+
+```
+modules/hospitality/
+├── components/ # Portal shell, CRUD factory, bundle gate
+├── constants/ # Navigation, permissions
+├── design-system/ # Tokens, table page, status chips
+├── features/ # Page implementations (54 routes)
+├── hooks/ # Capabilities, lookups
+├── pages/ # Shared loaders/errors
+├── services/ # hospitality-api.ts
+└── types/ # DTO-aligned types
+```
+
+## Architecture
+
+- Thin routes in `app/hospitality/**/page.tsx` re-export from `features/`
+- Business logic lives exclusively under `modules/hospitality/`
+- Shared UI from `@/components/ds` (design system)
+- TanStack Query keys: `["hospitality", tenantId, ...]`
+
+## Capabilities
+
+Navigation and pages respect `GET /capabilities` feature flags and bundle licensing via `HospitalityBundleGate`.
+
+## Scripts
+
+```bash
+node scripts/gen-hospitality-api.mjs # Regenerate API client
+node scripts/scaffold-hospitality-module.mjs # Regenerate routes + feature stubs
+```
+
+## Related
+
+- [hospitality-ui-specification.md](../docs/hospitality-ui-specification.md)
+- [hospitality-routing-map.md](../docs/hospitality-routing-map.md)
diff --git a/frontend/modules/hospitality/components/HospitalityBreadcrumbs.tsx b/frontend/modules/hospitality/components/HospitalityBreadcrumbs.tsx
new file mode 100644
index 0000000..a827b92
--- /dev/null
+++ b/frontend/modules/hospitality/components/HospitalityBreadcrumbs.tsx
@@ -0,0 +1,16 @@
+"use client";
+
+import Link from "next/link";
+import { ChevronLeft } from "lucide-react";
+
+export function HospitalityBreadcrumbs({ current }: { current: string }) {
+ return (
+
+ );
+}
diff --git a/frontend/modules/hospitality/components/HospitalityBundleGate.tsx b/frontend/modules/hospitality/components/HospitalityBundleGate.tsx
new file mode 100644
index 0000000..3cd9dd6
--- /dev/null
+++ b/frontend/modules/hospitality/components/HospitalityBundleGate.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
+import { EmptyState } from "@/components/ds";
+import { HospitalityPageLoader } from "@/modules/hospitality/pages/shared";
+
+const FEATURE_TO_BUNDLE: Record = {
+ digital_menu: "digital_menu",
+ digital_menu_catalog: "digital_menu",
+ qr_menu: "qr_menu",
+ qr_ordering: "qr_ordering",
+ pos_lite: "pos_lite",
+ pos_pro: "pos_pro",
+ kitchen: "kitchen",
+ kitchen_engine: "kitchen",
+ reservation: "reservation",
+ table_service: "table_service",
+ delivery_integration: "delivery_connector",
+ delivery_connector: "delivery_connector",
+ accounting_integration: "accounting_connector",
+ crm_integration: "crm_connector",
+ crm_connector: "crm_connector",
+ loyalty_integration: "loyalty_connector",
+ loyalty_connector: "loyalty_connector",
+ communication_integration: "communication_connector",
+ communication_connector: "communication_connector",
+ website_integration: "website_connector",
+ website_connector: "website_connector",
+ analytics: "analytics",
+};
+
+export function HospitalityBundleGate({
+ feature,
+ capabilities,
+ children,
+}: {
+ feature: string;
+ capabilities?: { features: Record };
+ children: React.ReactNode;
+}) {
+ const capsQ = useHospitalityCapabilities();
+ const caps = capabilities ?? capsQ.data;
+ if (capsQ.isLoading && !capabilities) return ;
+
+ const enabled = caps?.features?.[feature] ?? caps?.features?.[FEATURE_TO_BUNDLE[feature] ?? feature];
+ if (enabled === false) {
+ return (
+
+ );
+ }
+ return <>{children}>;
+}
diff --git a/frontend/modules/hospitality/components/HospitalityListCrudPage.tsx b/frontend/modules/hospitality/components/HospitalityListCrudPage.tsx
new file mode 100644
index 0000000..54cdda0
--- /dev/null
+++ b/frontend/modules/hospitality/components/HospitalityListCrudPage.tsx
@@ -0,0 +1,312 @@
+"use client";
+
+import { useState } from "react";
+import { useForm, type FieldValues, type DefaultValues } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { Plus, Pencil, Trash2, Download } from "lucide-react";
+import { z } from "zod";
+import {
+ Button,
+ Dialog,
+ Input,
+ EmptyState,
+ FormField,
+ ConfirmDialog,
+ Select,
+} from "@/components/ds";
+import { HospitalityTablePage } from "@/modules/hospitality/design-system";
+import { HospitalityPageLoader, HospitalityPageError, exportToCsv } from "@/modules/hospitality/pages/shared";
+import { useTenantId } from "@/hooks/useTenantId";
+import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
+import { HospitalityStatusChip } from "@/modules/hospitality/design-system";
+import { HospitalityBundleGate } from "@/modules/hospitality/components/HospitalityBundleGate";
+
+export type HospitalityFieldConfig = {
+ name: string;
+ label: string;
+ type?: "text" | "number" | "select";
+ options?: { value: string; label: string }[];
+ required?: boolean;
+};
+
+export type HospitalityColumnConfig = {
+ key: string;
+ header: string;
+ type?: "status" | "datetime" | "text";
+ render?: (row: Record) => React.ReactNode;
+};
+
+type ApiResource = {
+ list: (tenantId: string, params?: { page?: number; page_size?: number }) => Promise[]>;
+ create?: (tenantId: string, body: Record) => Promise;
+ update?: (tenantId: string, id: string, body: Record) => Promise;
+ remove?: (tenantId: string, id: string) => Promise;
+};
+
+export type HospitalityListConfig = {
+ title: string;
+ description?: string;
+ breadcrumb?: string;
+ resourceKey: string;
+ bundleFeature?: string | null;
+ permission?: string;
+ api: ApiResource;
+ columns: HospitalityColumnConfig[];
+ createFields?: HospitalityFieldConfig[];
+ editFields?: HospitalityFieldConfig[];
+ filterDeleted?: boolean;
+};
+
+function buildSchema(fields: HospitalityFieldConfig[]) {
+ const shape: Record = {};
+ for (const f of fields) {
+ shape[f.name] = f.required
+ ? z.string().min(1, `${f.label} الزامی است`)
+ : z.string().optional();
+ }
+ return z.object(shape);
+}
+
+function renderCell(col: HospitalityColumnConfig, row: Record) {
+ if (col.render) return col.render(row);
+ const val = row[col.key];
+ if (col.type === "status" && val) return ;
+ if (col.type === "datetime" && val) {
+ try {
+ return new Date(String(val)).toLocaleString("fa-IR");
+ } catch {
+ return String(val);
+ }
+ }
+ return val != null ? String(val) : "—";
+}
+
+export function createHospitalityListPage(config: HospitalityListConfig) {
+ return function HospitalityListPage() {
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const caps = useHospitalityCapabilities();
+ const [createOpen, setCreateOpen] = useState(false);
+ const [editRow, setEditRow] = useState | null>(null);
+ const [deleteId, setDeleteId] = useState(null);
+ const [page, setPage] = useState(1);
+ const pageSize = 20;
+
+ const createFields = config.createFields ?? [];
+ const editFields = config.editFields ?? createFields;
+ const createSchema = buildSchema(createFields);
+ const editSchema = buildSchema(editFields);
+
+ const listQ = useQuery({
+ queryKey: ["hospitality", tenantId, config.resourceKey, page],
+ queryFn: () => config.api.list(tenantId!, { page, page_size: pageSize }),
+ enabled: !!tenantId,
+ });
+
+ const createForm = useForm({
+ resolver: zodResolver(createSchema),
+ defaultValues: Object.fromEntries(createFields.map((f) => [f.name, ""])) as DefaultValues,
+ });
+ const editForm = useForm({ resolver: zodResolver(editSchema) });
+
+ const invalidate = () =>
+ qc.invalidateQueries({ queryKey: ["hospitality", tenantId, config.resourceKey] });
+
+ const createM = useMutation({
+ mutationFn: (d: FieldValues) => config.api.create!(tenantId!, d),
+ onSuccess: async () => {
+ toast.success("رکورد ایجاد شد");
+ setCreateOpen(false);
+ createForm.reset();
+ await invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ const updateM = useMutation({
+ mutationFn: (d: FieldValues) => config.api.update!(tenantId!, String(editRow!.id), d),
+ onSuccess: async () => {
+ toast.success("بهروزرسانی شد");
+ setEditRow(null);
+ await invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ const deleteM = useMutation({
+ mutationFn: (id: string) => config.api.remove!(tenantId!, id),
+ onSuccess: async () => {
+ toast.success("حذف شد");
+ setDeleteId(null);
+ await invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || listQ.isLoading || caps.isLoading) return ;
+ if (listQ.error) return listQ.refetch()} />;
+
+ let rows = listQ.data ?? [];
+ if (config.filterDeleted) rows = rows.filter((r) => !r.is_deleted);
+
+ const tableColumns = [
+ ...config.columns.map((c) => ({
+ key: c.key,
+ header: c.header,
+ render: (row: Record) => renderCell(c, row),
+ })),
+ {
+ key: "actions",
+ header: "عملیات",
+ searchable: false as const,
+ render: (row: Record) => (
+
+ {config.api.update ? (
+
+ ) : null}
+ {config.api.remove ? (
+
+ ) : null}
+
+ ),
+ },
+ ];
+
+ const body = (
+ setCreateOpen(true)}>
+
+ جدید
+
+ ) : undefined
+ }
+ toolbar={
+
+
+
+ صفحه {page}
+
+
+ }
+ empty={
+ setCreateOpen(true)}>ایجاد اولین رکورد
+ ) : undefined
+ }
+ />
+ }
+ />
+ );
+
+ const pageContent = config.bundleFeature ? (
+
+ {body}
+
+ ) : (
+ body
+ );
+
+ return (
+ <>
+ {pageContent}
+ {config.api.create && createFields.length > 0 ? (
+
+ ) : null}
+ {editRow && config.api.update ? (
+
+ ) : null}
+ setDeleteId(null)}
+ onConfirm={() => deleteId && deleteM.mutate(deleteId)}
+ title="حذف رکورد"
+ description="آیا از حذف این رکورد مطمئن هستید؟"
+ confirmLabel="حذف"
+ danger
+ />
+ >
+ );
+ };
+}
diff --git a/frontend/modules/hospitality/components/HospitalityPortalShell.tsx b/frontend/modules/hospitality/components/HospitalityPortalShell.tsx
new file mode 100644
index 0000000..ee13a3a
--- /dev/null
+++ b/frontend/modules/hospitality/components/HospitalityPortalShell.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { ChevronDown, Moon, Sun, Menu, X } from "lucide-react";
+import { cn } from "@/lib/utils";
+import { useColorMode } from "@/components/providers/ColorModeProvider";
+import { Button } from "@/components/ds";
+import {
+ HOSPITALITY_PORTAL,
+ navForHospitality,
+ filterNavByCapabilities,
+} from "@/modules/hospitality/constants/portals";
+import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
+
+function isActive(pathname: string, href: string) {
+ const base = href.split("?")[0];
+ if (base === "/hospitality") return pathname === "/hospitality";
+ return pathname === base || pathname.startsWith(`${base}/`);
+}
+
+export function HospitalityPortalShell({
+ children,
+ title,
+}: {
+ children: React.ReactNode;
+ title?: string;
+}) {
+ const pathname = usePathname();
+ const { mode, toggle } = useColorMode();
+ const caps = useHospitalityCapabilities();
+ const [openGroups, setOpenGroups] = useState>({});
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ const nav = useMemo(
+ () => filterNavByCapabilities(navForHospitality(), caps.data?.features),
+ [caps.data?.features]
+ );
+
+ const autoOpen = useMemo(() => {
+ const map: Record = {};
+ for (const g of nav) {
+ if (g.href && isActive(pathname, g.href)) map[g.id] = true;
+ if (g.items?.some((i) => isActive(pathname, i.href))) map[g.id] = true;
+ }
+ return map;
+ }, [pathname, nav]);
+
+ const expanded = { ...autoOpen, ...openGroups };
+
+ const sidebar = (
+ <>
+
+
تربت فود
+
{title ?? HOSPITALITY_PORTAL.label}
+
+
+
+
+ تغییر پورتال
+
+
+
+ >
+ );
+
+ return (
+
+
+ {mobileOpen ? (
+
+
+ ) : null}
+
+
+ );
+}
diff --git a/frontend/modules/hospitality/components/createPortalLayout.tsx b/frontend/modules/hospitality/components/createPortalLayout.tsx
new file mode 100644
index 0000000..ceed6ac
--- /dev/null
+++ b/frontend/modules/hospitality/components/createPortalLayout.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import { AuthGuard } from "@/components/AuthGuard";
+import { useMe } from "@/hooks/useMe";
+import { useRouter, usePathname } from "next/navigation";
+import { useEffect } from "react";
+import { LoadingState, ErrorState } from "@/components/ds";
+import { HospitalityPortalShell } from "@/modules/hospitality/components/HospitalityPortalShell";
+
+export type HospitalityPortalId = "management";
+
+function PortalGate({ children }: { children: React.ReactNode }) {
+ const { me, loading, error, reload } = useMe();
+ const router = useRouter();
+
+ useEffect(() => {
+ if (loading) return;
+ if (me?.onboarding_required) router.replace("/onboarding");
+ }, [me, loading, router]);
+
+ if (loading) return ;
+ if (error) return ;
+ if (!me?.current_tenant_id) {
+ return ;
+ }
+
+ return {children};
+}
+
+export function createPortalLayout(_portalId: HospitalityPortalId) {
+ return function PortalLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+ };
+}
+
+/** Root layout wrapper — hub renders without shell sidebar. */
+export function HospitalityRootLayout({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname();
+ const isHub = pathname === "/hospitality/hub";
+
+ if (isHub) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/modules/hospitality/constants/portals.ts b/frontend/modules/hospitality/constants/portals.ts
new file mode 100644
index 0000000..fac37a1
--- /dev/null
+++ b/frontend/modules/hospitality/constants/portals.ts
@@ -0,0 +1,202 @@
+import type { LucideIcon } from "lucide-react";
+import {
+ LayoutDashboard,
+ Building2,
+ UtensilsCrossed,
+ QrCode,
+ ChefHat,
+ CreditCard,
+ Truck,
+ BarChart3,
+ Settings,
+ Shield,
+ Users,
+ Map,
+ Calendar,
+ Clock,
+ ShoppingCart,
+ Package,
+ Plug,
+ Bell,
+ Activity,
+ Layers,
+} from "lucide-react";
+
+export type HospitalityPortalId = "management";
+
+export type PortalNavItem = { href: string; label: string; feature?: string | null };
+export type PortalNavGroup = {
+ id: string;
+ label: string;
+ icon: LucideIcon;
+ href?: string;
+ feature?: string | null;
+ items?: PortalNavItem[];
+};
+
+export const HOSPITALITY_PORTAL = {
+ id: "management" as const,
+ label: "مدیریت مهماننوازی",
+ description: "داشبورد، منو، POS، آشپزخانه و یکپارچهسازیها",
+ basePath: "/hospitality",
+ icon: UtensilsCrossed,
+};
+
+export const MANAGEMENT_NAV: PortalNavGroup[] = [
+ { id: "dash", label: "داشبورد", icon: LayoutDashboard, href: "/hospitality" },
+ {
+ id: "venue",
+ label: "مکان و سالن",
+ icon: Building2,
+ items: [
+ { href: "/hospitality/branches", label: "مکانها" },
+ { href: "/hospitality/branches/list", label: "شعب" },
+ { href: "/hospitality/dining-areas", label: "سالنها" },
+ { href: "/hospitality/tables", label: "میزها" },
+ { href: "/hospitality/floor-map", label: "نقشه سالن", feature: "pos_pro" },
+ ],
+ },
+ {
+ id: "service",
+ label: "خدمات میز",
+ icon: Calendar,
+ feature: "table_service",
+ items: [
+ { href: "/hospitality/reservations", label: "رزروها", feature: "reservation" },
+ { href: "/hospitality/queue", label: "صف انتظار", feature: "table_service" },
+ { href: "/hospitality/customers", label: "مشتریان", feature: "crm_connector" },
+ { href: "/hospitality/guests", label: "پروفایل مهمان", feature: "qr_menu" },
+ ],
+ },
+ {
+ id: "menu",
+ label: "منوی دیجیتال",
+ icon: UtensilsCrossed,
+ feature: "digital_menu",
+ items: [
+ { href: "/hospitality/menu", label: "منو" },
+ { href: "/hospitality/menu/categories", label: "دستهبندی" },
+ { href: "/hospitality/menu/items", label: "آیتمها" },
+ { href: "/hospitality/menu/modifiers", label: "افزودنیها" },
+ { href: "/hospitality/menu/modifier-options", label: "گزینهها" },
+ { href: "/hospitality/menu/availability", label: "دسترسی" },
+ { href: "/hospitality/menu/media", label: "رسانه" },
+ ],
+ },
+ {
+ id: "qr",
+ label: "QR و سفارش",
+ icon: QrCode,
+ feature: "qr_menu",
+ items: [
+ { href: "/hospitality/qr/menu", label: "منوی QR", feature: "qr_menu" },
+ { href: "/hospitality/qr/ordering", label: "سفارش QR", feature: "qr_ordering" },
+ { href: "/hospitality/ordering/cart", label: "سبد", feature: "qr_ordering" },
+ { href: "/hospitality/ordering/checkout", label: "تسویه", feature: "qr_ordering" },
+ ],
+ },
+ {
+ id: "kitchen",
+ label: "آشپزخانه",
+ icon: ChefHat,
+ feature: "kitchen",
+ items: [
+ { href: "/hospitality/kitchen/display", label: "نمایشگر" },
+ { href: "/hospitality/kitchen/queue", label: "صف" },
+ { href: "/hospitality/kitchen/preparation", label: "آمادهسازی" },
+ ],
+ },
+ {
+ id: "pos",
+ label: "صندوق",
+ icon: CreditCard,
+ feature: "pos_lite",
+ items: [
+ { href: "/hospitality/pos/lite", label: "POS Lite", feature: "pos_lite" },
+ { href: "/hospitality/pos/pro", label: "POS Pro", feature: "pos_pro" },
+ { href: "/hospitality/pos/register", label: "صندوق", feature: "pos_lite" },
+ { href: "/hospitality/pos/payments", label: "پرداخت", feature: "pos_pro" },
+ { href: "/hospitality/pos/discounts", label: "تخفیف", feature: "pos_pro" },
+ { href: "/hospitality/pos/coupons", label: "کوپن", feature: "pos_pro" },
+ { href: "/hospitality/pos/promotions", label: "پروموشن", feature: "pos_pro" },
+ ],
+ },
+ {
+ id: "orders",
+ label: "سفارشها",
+ icon: ShoppingCart,
+ items: [
+ { href: "/hospitality/orders/pickup", label: "بیرونبر", feature: "qr_ordering" },
+ { href: "/hospitality/orders/online", label: "آنلاین", feature: "qr_ordering" },
+ { href: "/hospitality/orders/timeline", label: "خط زمانی", feature: "kitchen" },
+ { href: "/hospitality/invoices", label: "فاکتور", feature: "pos_lite" },
+ { href: "/hospitality/receipts", label: "رسید", feature: "pos_lite" },
+ ],
+ },
+ {
+ id: "integrations",
+ label: "یکپارچهسازی",
+ icon: Plug,
+ items: [
+ { href: "/hospitality/integrations/delivery", label: "پیک", feature: "delivery_connector" },
+ { href: "/hospitality/integrations/crm", label: "CRM", feature: "crm_connector" },
+ { href: "/hospitality/integrations/loyalty", label: "وفاداری", feature: "loyalty_connector" },
+ { href: "/hospitality/integrations/communication", label: "ارتباطات", feature: "communication_connector" },
+ { href: "/hospitality/integrations/website", label: "وبسایت", feature: "website_connector" },
+ { href: "/hospitality/integrations/marketplace", label: "مارکتپلیس" },
+ ],
+ },
+ {
+ id: "insights",
+ label: "گزارش و تحلیل",
+ icon: BarChart3,
+ feature: "analytics",
+ items: [
+ { href: "/hospitality/reports", label: "گزارشها", feature: "analytics" },
+ { href: "/hospitality/analytics", label: "آنالیتیکس", feature: "analytics" },
+ { href: "/hospitality/inventory", label: "موجودی", feature: "analytics" },
+ ],
+ },
+ {
+ id: "admin",
+ label: "مدیریت سیستم",
+ icon: Settings,
+ items: [
+ { href: "/hospitality/settings", label: "تنظیمات" },
+ { href: "/hospitality/bundles", label: "بستهها" },
+ { href: "/hospitality/roles", label: "نقشها" },
+ { href: "/hospitality/permissions", label: "مجوزها" },
+ { href: "/hospitality/audit", label: "ممیزی" },
+ { href: "/hospitality/notifications", label: "اعلانها" },
+ { href: "/hospitality/health", label: "سلامت" },
+ { href: "/hospitality/capabilities", label: "قابلیتها" },
+ ],
+ },
+];
+
+export function navForHospitality(): PortalNavGroup[] {
+ return MANAGEMENT_NAV;
+}
+
+function featureEnabled(features: Record | undefined, feature?: string | null) {
+ if (!feature) return true;
+ if (!features) return true;
+ return features[feature] !== false;
+}
+
+export function filterNavByCapabilities(
+ nav: PortalNavGroup[],
+ features?: Record
+): PortalNavGroup[] {
+ return nav
+ .map((group) => {
+ if (group.feature && !featureEnabled(features, group.feature)) return null;
+ if (group.items) {
+ const items = group.items.filter((i) => featureEnabled(features, i.feature));
+ if (items.length === 0 && !group.href) return null;
+ return { ...group, items };
+ }
+ return group;
+ })
+ .filter(Boolean) as PortalNavGroup[];
+}
diff --git a/frontend/modules/hospitality/design-system/HospitalityStatusChip.tsx b/frontend/modules/hospitality/design-system/HospitalityStatusChip.tsx
new file mode 100644
index 0000000..1b7a8f8
--- /dev/null
+++ b/frontend/modules/hospitality/design-system/HospitalityStatusChip.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { Badge } from "@/components/ds";
+import { lifecycleStatusLabels, lifecycleStatusTone } from "./tokens";
+
+export function HospitalityStatusChip({ status }: { status: string }) {
+ return (
+
+ {lifecycleStatusLabels[status] ?? status}
+
+ );
+}
diff --git a/frontend/modules/hospitality/design-system/HospitalityTablePage.tsx b/frontend/modules/hospitality/design-system/HospitalityTablePage.tsx
new file mode 100644
index 0000000..82a101b
--- /dev/null
+++ b/frontend/modules/hospitality/design-system/HospitalityTablePage.tsx
@@ -0,0 +1,78 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { PageHeader, DataTable, EmptyState, Input } from "@/components/ds";
+import { Search } from "lucide-react";
+import { HospitalityBreadcrumbs } from "@/modules/hospitality/components/HospitalityBreadcrumbs";
+
+export type HospitalityTableColumn = {
+ key: string;
+ header: string;
+ className?: string;
+ render?: (row: Record) => React.ReactNode;
+ searchable?: boolean;
+};
+
+export function HospitalityTablePage({
+ title,
+ description,
+ breadcrumb,
+ columns,
+ rows,
+ empty,
+ actions,
+ searchPlaceholder = "جستجو…",
+ filterFn,
+ toolbar,
+}: {
+ title: string;
+ description?: string;
+ breadcrumb?: string;
+ columns: HospitalityTableColumn[];
+ rows: Record[];
+ empty?: React.ReactNode;
+ actions?: React.ReactNode;
+ searchPlaceholder?: string;
+ filterFn?: (row: Record, query: string) => boolean;
+ toolbar?: React.ReactNode;
+}) {
+ const [query, setQuery] = useState("");
+
+ const filtered = useMemo(() => {
+ const q = query.trim().toLowerCase();
+ if (!q) return rows;
+ if (filterFn) return rows.filter((r) => filterFn(r, q));
+ return rows.filter((r) =>
+ columns.some((c) => {
+ if (c.searchable === false) return false;
+ const val = r[c.key];
+ return val != null && String(val).toLowerCase().includes(q);
+ })
+ );
+ }, [rows, query, columns, filterFn]);
+
+ return (
+
+ {breadcrumb ?
: null}
+
+
+
+
+ setQuery(e.target.value)}
+ placeholder={searchPlaceholder}
+ className="pr-10"
+ aria-label="جستجو"
+ />
+
+ {toolbar}
+
+
}
+ />
+
+ );
+}
diff --git a/frontend/modules/hospitality/design-system/index.ts b/frontend/modules/hospitality/design-system/index.ts
new file mode 100644
index 0000000..252d296
--- /dev/null
+++ b/frontend/modules/hospitality/design-system/index.ts
@@ -0,0 +1,3 @@
+export * from "./tokens";
+export * from "./HospitalityStatusChip";
+export * from "./HospitalityTablePage";
diff --git a/frontend/modules/hospitality/design-system/tokens.ts b/frontend/modules/hospitality/design-system/tokens.ts
new file mode 100644
index 0000000..96806d1
--- /dev/null
+++ b/frontend/modules/hospitality/design-system/tokens.ts
@@ -0,0 +1,44 @@
+/** Hospitality module visual tokens — warm, welcoming F&B palette. */
+export const hospitalityTokens = {
+ accent: "var(--hospitality-accent)",
+ accentSoft: "var(--hospitality-accent-soft)",
+ warm: "var(--hospitality-warm)",
+ success: "var(--hospitality-success)",
+ warning: "var(--hospitality-warning)",
+ danger: "var(--hospitality-danger)",
+} as const;
+
+export const venueFormatLabels: Record = {
+ cafe: "کافه",
+ coffee_shop: "کافیشاپ",
+ restaurant: "رستوران",
+ fast_food: "فستفود",
+ bakery: "نانوایی",
+ pastry: "شیرینی",
+ ice_cream: "بستنی",
+ juice_bar: "آبمیوه",
+ cloud_kitchen: "آشپزخانه ابری",
+ food_court: "فودکورت",
+ catering: "کترینگ",
+ take_away: "بیرونبر",
+ other: "سایر",
+};
+
+export const lifecycleStatusLabels: Record = {
+ draft: "پیشنویس",
+ active: "فعال",
+ inactive: "غیرفعال",
+ suspended: "معلق",
+ archived: "آرشیو",
+};
+
+export const lifecycleStatusTone: Record<
+ string,
+ "default" | "primary" | "success" | "warning" | "danger" | "info"
+> = {
+ draft: "default",
+ active: "success",
+ inactive: "default",
+ suspended: "warning",
+ archived: "danger",
+};
diff --git a/frontend/modules/hospitality/features/analyticsReports.tsx b/frontend/modules/hospitality/features/analyticsReports.tsx
new file mode 100644
index 0000000..af3d347
--- /dev/null
+++ b/frontend/modules/hospitality/features/analyticsReports.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const ReportsPage = createHospitalityListPage({
+ title: "گزارشها",
+ breadcrumb: "گزارشها",
+ resourceKey: "analyticsReports",
+ bundleFeature: "analytics",
+ permission: "hospitality.view",
+ api: hospitalityApi.analyticsReports,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default ReportsPage;
diff --git a/frontend/modules/hospitality/features/analyticsSnapshots.tsx b/frontend/modules/hospitality/features/analyticsSnapshots.tsx
new file mode 100644
index 0000000..5c24c83
--- /dev/null
+++ b/frontend/modules/hospitality/features/analyticsSnapshots.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const AnalyticsPage = createHospitalityListPage({
+ title: "آنالیتیکس",
+ breadcrumb: "آنالیتیکس",
+ resourceKey: "analyticsSnapshots",
+ bundleFeature: "analytics",
+ permission: "hospitality.view",
+ api: hospitalityApi.analyticsSnapshots,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default AnalyticsPage;
diff --git a/frontend/modules/hospitality/features/availabilityWindows.tsx b/frontend/modules/hospitality/features/availabilityWindows.tsx
new file mode 100644
index 0000000..cdc8176
--- /dev/null
+++ b/frontend/modules/hospitality/features/availabilityWindows.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const AvailabilityPage = createHospitalityListPage({
+ title: "قوانین دسترسی",
+ breadcrumb: "قوانین دسترسی",
+ resourceKey: "availabilityWindows",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.availabilityWindows,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default AvailabilityPage;
diff --git a/frontend/modules/hospitality/features/branches.tsx b/frontend/modules/hospitality/features/branches.tsx
new file mode 100644
index 0000000..8a92bc2
--- /dev/null
+++ b/frontend/modules/hospitality/features/branches.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const BranchListPage = createHospitalityListPage({
+ title: "شعب",
+ breadcrumb: "شعب",
+ resourceKey: "branches",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.branches,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default BranchListPage;
diff --git a/frontend/modules/hospitality/features/capabilities.tsx b/frontend/modules/hospitality/features/capabilities.tsx
new file mode 100644
index 0000000..206c4c4
--- /dev/null
+++ b/frontend/modules/hospitality/features/capabilities.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { useHospitalityCapabilities } from "@/modules/hospitality/hooks/useHospitalityCapabilities";
+import { HospitalityPageLoader, HospitalityPageError } from "@/modules/hospitality/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+
+export const CapabilitiesPage = function HospitalityCapabilitiesPage() {
+ const q = useHospitalityCapabilities();
+ if (q.isLoading) return ;
+ if (q.error) return q.refetch()} />;
+ const features = q.data?.features ?? {};
+ return (
+
+
+
+ {Object.entries(features).map(([k, v]) => (
+
+ {k}
+ {v ? "فعال" : "غیرفعال"}
+
+ ))}
+
+
+ );
+};
+export default CapabilitiesPage;
diff --git a/frontend/modules/hospitality/features/cartLines.tsx b/frontend/modules/hospitality/features/cartLines.tsx
new file mode 100644
index 0000000..67b692c
--- /dev/null
+++ b/frontend/modules/hospitality/features/cartLines.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CheckoutPage = createHospitalityListPage({
+ title: "تسویه",
+ breadcrumb: "تسویه",
+ resourceKey: "cartLines",
+ bundleFeature: "qr_ordering",
+ permission: "hospitality.view",
+ api: hospitalityApi.cartLines,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CheckoutPage;
diff --git a/frontend/modules/hospitality/features/carts.tsx b/frontend/modules/hospitality/features/carts.tsx
new file mode 100644
index 0000000..f70292e
--- /dev/null
+++ b/frontend/modules/hospitality/features/carts.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CartPage = createHospitalityListPage({
+ title: "سبد خرید",
+ breadcrumb: "سبد خرید",
+ resourceKey: "carts",
+ bundleFeature: "qr_ordering",
+ permission: "hospitality.view",
+ api: hospitalityApi.carts,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CartPage;
diff --git a/frontend/modules/hospitality/features/connectorCommunication.tsx b/frontend/modules/hospitality/features/connectorCommunication.tsx
new file mode 100644
index 0000000..e9d8f0d
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorCommunication.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CommunicationIntegrationPage = createHospitalityListPage({
+ title: "ارتباطات",
+ breadcrumb: "ارتباطات",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "communication_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CommunicationIntegrationPage;
diff --git a/frontend/modules/hospitality/features/connectorCrm.tsx b/frontend/modules/hospitality/features/connectorCrm.tsx
new file mode 100644
index 0000000..3e002cc
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorCrm.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CrmIntegrationPage = createHospitalityListPage({
+ title: "CRM",
+ breadcrumb: "CRM",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "crm_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CrmIntegrationPage;
diff --git a/frontend/modules/hospitality/features/connectorDelivery.tsx b/frontend/modules/hospitality/features/connectorDelivery.tsx
new file mode 100644
index 0000000..e2ca7b0
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorDelivery.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const DeliveryIntegrationPage = createHospitalityListPage({
+ title: "پیک",
+ breadcrumb: "پیک",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "delivery_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default DeliveryIntegrationPage;
diff --git a/frontend/modules/hospitality/features/connectorDispatches.tsx b/frontend/modules/hospitality/features/connectorDispatches.tsx
new file mode 100644
index 0000000..d16596a
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorDispatches.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const MarketplaceIntegrationPage = createHospitalityListPage({
+ title: "مارکتپلیس",
+ breadcrumb: "مارکتپلیس",
+ resourceKey: "connectorDispatches",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorDispatches,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default MarketplaceIntegrationPage;
diff --git a/frontend/modules/hospitality/features/connectorLoyalty.tsx b/frontend/modules/hospitality/features/connectorLoyalty.tsx
new file mode 100644
index 0000000..5cc3e28
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorLoyalty.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const LoyaltyIntegrationPage = createHospitalityListPage({
+ title: "وفاداری",
+ breadcrumb: "وفاداری",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "loyalty_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default LoyaltyIntegrationPage;
diff --git a/frontend/modules/hospitality/features/connectorRegistrationsCrm.tsx b/frontend/modules/hospitality/features/connectorRegistrationsCrm.tsx
new file mode 100644
index 0000000..5091010
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorRegistrationsCrm.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CustomersPage = createHospitalityListPage({
+ title: "مشتریان",
+ breadcrumb: "مشتریان",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "crm_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CustomersPage;
diff --git a/frontend/modules/hospitality/features/connectorWebsite.tsx b/frontend/modules/hospitality/features/connectorWebsite.tsx
new file mode 100644
index 0000000..a5ac348
--- /dev/null
+++ b/frontend/modules/hospitality/features/connectorWebsite.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const WebsiteIntegrationPage = createHospitalityListPage({
+ title: "وبسایت",
+ breadcrumb: "وبسایت",
+ resourceKey: "connectorRegistrations",
+ bundleFeature: "website_connector",
+ permission: "hospitality.view",
+ api: hospitalityApi.connectorRegistrations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default WebsiteIntegrationPage;
diff --git a/frontend/modules/hospitality/features/dashboard.tsx b/frontend/modules/hospitality/features/dashboard.tsx
new file mode 100644
index 0000000..ab99caa
--- /dev/null
+++ b/frontend/modules/hospitality/features/dashboard.tsx
@@ -0,0 +1,66 @@
+"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 ;
+ if (countsQ.error) return countsQ.refetch()} />;
+
+ const c = countsQ.data!;
+
+ return (
+
+
+
Torbat Food v{caps.data?.version}}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ExecutiveDashboard;
diff --git a/frontend/modules/hospitality/features/diningAreas.tsx b/frontend/modules/hospitality/features/diningAreas.tsx
new file mode 100644
index 0000000..a1177bf
--- /dev/null
+++ b/frontend/modules/hospitality/features/diningAreas.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const DiningAreasPage = createHospitalityListPage({
+ title: "سالنها",
+ breadcrumb: "سالنها",
+ resourceKey: "diningAreas",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.diningAreas,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default DiningAreasPage;
diff --git a/frontend/modules/hospitality/features/events.tsx b/frontend/modules/hospitality/features/events.tsx
new file mode 100644
index 0000000..713508d
--- /dev/null
+++ b/frontend/modules/hospitality/features/events.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const AuditLogsPage = createHospitalityListPage({
+ title: "لاگ ممیزی",
+ breadcrumb: "لاگ ممیزی",
+ resourceKey: "events",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.events,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default AuditLogsPage;
diff --git a/frontend/modules/hospitality/features/featureTogglesPromo.tsx b/frontend/modules/hospitality/features/featureTogglesPromo.tsx
new file mode 100644
index 0000000..4bbdbf1
--- /dev/null
+++ b/frontend/modules/hospitality/features/featureTogglesPromo.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PromotionsPage = createHospitalityListPage({
+ title: "پروموشنها",
+ breadcrumb: "پروموشنها",
+ resourceKey: "featureToggles",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.featureToggles,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PromotionsPage;
diff --git a/frontend/modules/hospitality/features/health.tsx b/frontend/modules/hospitality/features/health.tsx
new file mode 100644
index 0000000..3d411fc
--- /dev/null
+++ b/frontend/modules/hospitality/features/health.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+import { HospitalityPageLoader, HospitalityPageError } from "@/modules/hospitality/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+
+export const HealthPage = function HospitalityHealthPage() {
+ const q = useQuery({ queryKey: ["hospitality", "health"], queryFn: () => hospitalityApi.health() });
+ if (q.isLoading) return ;
+ if (q.error) return q.refetch()} />;
+ return (
+
+
+
+ {q.data?.status}
+ {q.data?.service} — v{q.data?.version}
+
+
+ );
+};
+export default HealthPage;
diff --git a/frontend/modules/hospitality/features/hub.tsx b/frontend/modules/hospitality/features/hub.tsx
new file mode 100644
index 0000000..e9aa8d9
--- /dev/null
+++ b/frontend/modules/hospitality/features/hub.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import Link from "next/link";
+import { useMe } from "@/hooks/useMe";
+import { HOSPITALITY_PORTAL } from "@/modules/hospitality/constants/portals";
+import { Card, CardContent, Button, LoadingState, ErrorState } from "@/components/ds";
+
+export const HospitalityHub = function HospitalityHubPage() {
+ const { me, loading, error, reload } = useMe();
+ if (loading) return ;
+ if (error) return ;
+ if (!me?.current_tenant_id) {
+ return ;
+ }
+
+ return (
+
+
تربت فود
+
پلتفرم مهماننوازی سازمانی
+
+
+ {HOSPITALITY_PORTAL.label}
+ {HOSPITALITY_PORTAL.description}
+
+
+
+
+
+
+ );
+};
+
+export default HospitalityHub;
diff --git a/frontend/modules/hospitality/features/inventoryOverview.tsx b/frontend/modules/hospitality/features/inventoryOverview.tsx
new file mode 100644
index 0000000..2f22ba5
--- /dev/null
+++ b/frontend/modules/hospitality/features/inventoryOverview.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const InventoryPage = createHospitalityListPage({
+ title: "موجودی",
+ breadcrumb: "موجودی",
+ resourceKey: "analyticsSnapshots",
+ bundleFeature: "analytics",
+ permission: "hospitality.view",
+ api: hospitalityApi.analyticsSnapshots,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default InventoryPage;
diff --git a/frontend/modules/hospitality/features/invoices.tsx b/frontend/modules/hospitality/features/invoices.tsx
new file mode 100644
index 0000000..362ea32
--- /dev/null
+++ b/frontend/modules/hospitality/features/invoices.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const InvoicesPage = createHospitalityListPage({
+ title: "فاکتورها",
+ breadcrumb: "فاکتورها",
+ resourceKey: "posTickets",
+ bundleFeature: "pos_lite",
+ permission: "hospitality.view",
+ api: hospitalityApi.posTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default InvoicesPage;
diff --git a/frontend/modules/hospitality/features/kitchenTicketItems.tsx b/frontend/modules/hospitality/features/kitchenTicketItems.tsx
new file mode 100644
index 0000000..c133784
--- /dev/null
+++ b/frontend/modules/hospitality/features/kitchenTicketItems.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const KitchenQueuePage = createHospitalityListPage({
+ title: "صف آشپزخانه",
+ breadcrumb: "صف آشپزخانه",
+ resourceKey: "kitchenTicketItems",
+ bundleFeature: "kitchen",
+ permission: "hospitality.view",
+ api: hospitalityApi.kitchenTicketItems,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default KitchenQueuePage;
diff --git a/frontend/modules/hospitality/features/kitchenTickets.tsx b/frontend/modules/hospitality/features/kitchenTickets.tsx
new file mode 100644
index 0000000..e384731
--- /dev/null
+++ b/frontend/modules/hospitality/features/kitchenTickets.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const KitchenDisplayPage = createHospitalityListPage({
+ title: "نمایشگر آشپزخانه",
+ breadcrumb: "نمایشگر آشپزخانه",
+ resourceKey: "kitchenTickets",
+ bundleFeature: "kitchen",
+ permission: "hospitality.view",
+ api: hospitalityApi.kitchenTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default KitchenDisplayPage;
diff --git a/frontend/modules/hospitality/features/kitchenTicketsPrep.tsx b/frontend/modules/hospitality/features/kitchenTicketsPrep.tsx
new file mode 100644
index 0000000..203aa4a
--- /dev/null
+++ b/frontend/modules/hospitality/features/kitchenTicketsPrep.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const KitchenPrepPage = createHospitalityListPage({
+ title: "وضعیت آمادهسازی",
+ breadcrumb: "وضعیت آمادهسازی",
+ resourceKey: "kitchenTickets",
+ bundleFeature: "kitchen",
+ permission: "hospitality.view",
+ api: hospitalityApi.kitchenTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default KitchenPrepPage;
diff --git a/frontend/modules/hospitality/features/menuCategories.tsx b/frontend/modules/hospitality/features/menuCategories.tsx
new file mode 100644
index 0000000..63279e8
--- /dev/null
+++ b/frontend/modules/hospitality/features/menuCategories.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const MenuCategoriesPage = createHospitalityListPage({
+ title: "دستهبندی منو",
+ breadcrumb: "دستهبندی منو",
+ resourceKey: "menuCategories",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.menuCategories,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default MenuCategoriesPage;
diff --git a/frontend/modules/hospitality/features/menuItems.tsx b/frontend/modules/hospitality/features/menuItems.tsx
new file mode 100644
index 0000000..39cf279
--- /dev/null
+++ b/frontend/modules/hospitality/features/menuItems.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const MenuItemsPage = createHospitalityListPage({
+ title: "آیتمهای منو",
+ breadcrumb: "آیتمهای منو",
+ resourceKey: "menuItems",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.menuItems,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default MenuItemsPage;
diff --git a/frontend/modules/hospitality/features/menuMediaRefs.tsx b/frontend/modules/hospitality/features/menuMediaRefs.tsx
new file mode 100644
index 0000000..8c81e92
--- /dev/null
+++ b/frontend/modules/hospitality/features/menuMediaRefs.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const MediaLibraryPage = createHospitalityListPage({
+ title: "کتابخانه رسانه",
+ breadcrumb: "کتابخانه رسانه",
+ resourceKey: "menuMediaRefs",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.menuMediaRefs,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default MediaLibraryPage;
diff --git a/frontend/modules/hospitality/features/menus.tsx b/frontend/modules/hospitality/features/menus.tsx
new file mode 100644
index 0000000..a973fdf
--- /dev/null
+++ b/frontend/modules/hospitality/features/menus.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const DigitalMenuPage = createHospitalityListPage({
+ title: "منوی دیجیتال",
+ breadcrumb: "منوی دیجیتال",
+ resourceKey: "menus",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.menus,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default DigitalMenuPage;
diff --git a/frontend/modules/hospitality/features/modifierGroups.tsx b/frontend/modules/hospitality/features/modifierGroups.tsx
new file mode 100644
index 0000000..5bcf791
--- /dev/null
+++ b/frontend/modules/hospitality/features/modifierGroups.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const ModifierGroupsPage = createHospitalityListPage({
+ title: "گروه افزودنی",
+ breadcrumb: "گروه افزودنی",
+ resourceKey: "modifierGroups",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.modifierGroups,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default ModifierGroupsPage;
diff --git a/frontend/modules/hospitality/features/modifierOptions.tsx b/frontend/modules/hospitality/features/modifierOptions.tsx
new file mode 100644
index 0000000..b09abc3
--- /dev/null
+++ b/frontend/modules/hospitality/features/modifierOptions.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const ModifierOptionsPage = createHospitalityListPage({
+ title: "گزینه افزودنی",
+ breadcrumb: "گزینه افزودنی",
+ resourceKey: "modifierOptions",
+ bundleFeature: "digital_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.modifierOptions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default ModifierOptionsPage;
diff --git a/frontend/modules/hospitality/features/notifications.tsx b/frontend/modules/hospitality/features/notifications.tsx
new file mode 100644
index 0000000..5c54abf
--- /dev/null
+++ b/frontend/modules/hospitality/features/notifications.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const NotificationsPage = createHospitalityListPage({
+ title: "اعلانها",
+ breadcrumb: "اعلانها",
+ resourceKey: "events",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.events,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default NotificationsPage;
diff --git a/frontend/modules/hospitality/features/onlineOrders.tsx b/frontend/modules/hospitality/features/onlineOrders.tsx
new file mode 100644
index 0000000..51877bf
--- /dev/null
+++ b/frontend/modules/hospitality/features/onlineOrders.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const OnlineOrdersPage = createHospitalityListPage({
+ title: "سفارش آنلاین",
+ breadcrumb: "سفارش آنلاین",
+ resourceKey: "qrOrderingSessions",
+ bundleFeature: "qr_ordering",
+ permission: "hospitality.view",
+ api: hospitalityApi.qrOrderingSessions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default OnlineOrdersPage;
diff --git a/frontend/modules/hospitality/features/orderTimeline.tsx b/frontend/modules/hospitality/features/orderTimeline.tsx
new file mode 100644
index 0000000..8cebcb6
--- /dev/null
+++ b/frontend/modules/hospitality/features/orderTimeline.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const OrderTimelinePage = createHospitalityListPage({
+ title: "خط زمانی سفارش",
+ breadcrumb: "خط زمانی سفارش",
+ resourceKey: "kitchenTickets",
+ bundleFeature: "kitchen",
+ permission: "hospitality.view",
+ api: hospitalityApi.kitchenTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default OrderTimelinePage;
diff --git a/frontend/modules/hospitality/features/permissions.tsx b/frontend/modules/hospitality/features/permissions.tsx
new file mode 100644
index 0000000..0e909be
--- /dev/null
+++ b/frontend/modules/hospitality/features/permissions.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PermissionsPage = createHospitalityListPage({
+ title: "مجوزها",
+ breadcrumb: "مجوزها",
+ resourceKey: "permissions",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.permissions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PermissionsPage;
diff --git a/frontend/modules/hospitality/features/pickupOrders.tsx b/frontend/modules/hospitality/features/pickupOrders.tsx
new file mode 100644
index 0000000..22262df
--- /dev/null
+++ b/frontend/modules/hospitality/features/pickupOrders.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PickupOrdersPage = createHospitalityListPage({
+ title: "سفارش بیرونبر",
+ breadcrumb: "سفارش بیرونبر",
+ resourceKey: "qrOrderingSessions",
+ bundleFeature: "qr_ordering",
+ permission: "hospitality.view",
+ api: hospitalityApi.qrOrderingSessions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PickupOrdersPage;
diff --git a/frontend/modules/hospitality/features/posDiscounts.tsx b/frontend/modules/hospitality/features/posDiscounts.tsx
new file mode 100644
index 0000000..5271247
--- /dev/null
+++ b/frontend/modules/hospitality/features/posDiscounts.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const DiscountsPage = createHospitalityListPage({
+ title: "تخفیفها",
+ breadcrumb: "تخفیفها",
+ resourceKey: "posDiscounts",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.posDiscounts,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default DiscountsPage;
diff --git a/frontend/modules/hospitality/features/posDiscountsCoupons.tsx b/frontend/modules/hospitality/features/posDiscountsCoupons.tsx
new file mode 100644
index 0000000..d57d114
--- /dev/null
+++ b/frontend/modules/hospitality/features/posDiscountsCoupons.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CouponsPage = createHospitalityListPage({
+ title: "کوپنها",
+ breadcrumb: "کوپنها",
+ resourceKey: "posDiscounts",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.posDiscounts,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CouponsPage;
diff --git a/frontend/modules/hospitality/features/posFloorPlans.tsx b/frontend/modules/hospitality/features/posFloorPlans.tsx
new file mode 100644
index 0000000..dcfc7aa
--- /dev/null
+++ b/frontend/modules/hospitality/features/posFloorPlans.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const FloorMapPage = createHospitalityListPage({
+ title: "نقشه سالن",
+ breadcrumb: "نقشه سالن",
+ resourceKey: "posFloorPlans",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.posFloorPlans,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default FloorMapPage;
diff --git a/frontend/modules/hospitality/features/posPayments.tsx b/frontend/modules/hospitality/features/posPayments.tsx
new file mode 100644
index 0000000..b8bbc59
--- /dev/null
+++ b/frontend/modules/hospitality/features/posPayments.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PosProPage = createHospitalityListPage({
+ title: "POS Pro",
+ breadcrumb: "POS Pro",
+ resourceKey: "posPayments",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.posPayments,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PosProPage;
diff --git a/frontend/modules/hospitality/features/posPaymentsList.tsx b/frontend/modules/hospitality/features/posPaymentsList.tsx
new file mode 100644
index 0000000..5bf65fd
--- /dev/null
+++ b/frontend/modules/hospitality/features/posPaymentsList.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PaymentsPage = createHospitalityListPage({
+ title: "پرداختها",
+ breadcrumb: "پرداختها",
+ resourceKey: "posPayments",
+ bundleFeature: "pos_pro",
+ permission: "hospitality.view",
+ api: hospitalityApi.posPayments,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PaymentsPage;
diff --git a/frontend/modules/hospitality/features/posRegisters.tsx b/frontend/modules/hospitality/features/posRegisters.tsx
new file mode 100644
index 0000000..cd88cb5
--- /dev/null
+++ b/frontend/modules/hospitality/features/posRegisters.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const CashRegisterPage = createHospitalityListPage({
+ title: "صندوق",
+ breadcrumb: "صندوق",
+ resourceKey: "posRegisters",
+ bundleFeature: "pos_lite",
+ permission: "hospitality.view",
+ api: hospitalityApi.posRegisters,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default CashRegisterPage;
diff --git a/frontend/modules/hospitality/features/posTickets.tsx b/frontend/modules/hospitality/features/posTickets.tsx
new file mode 100644
index 0000000..d23e002
--- /dev/null
+++ b/frontend/modules/hospitality/features/posTickets.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const PosLitePage = createHospitalityListPage({
+ title: "POS Lite",
+ breadcrumb: "POS Lite",
+ resourceKey: "posTickets",
+ bundleFeature: "pos_lite",
+ permission: "hospitality.view",
+ api: hospitalityApi.posTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default PosLitePage;
diff --git a/frontend/modules/hospitality/features/qrCodes.tsx b/frontend/modules/hospitality/features/qrCodes.tsx
new file mode 100644
index 0000000..af915dd
--- /dev/null
+++ b/frontend/modules/hospitality/features/qrCodes.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const QrMenuPage = createHospitalityListPage({
+ title: "منوی QR",
+ breadcrumb: "منوی QR",
+ resourceKey: "qrCodes",
+ bundleFeature: "qr_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.qrCodes,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default QrMenuPage;
diff --git a/frontend/modules/hospitality/features/qrMenuSessions.tsx b/frontend/modules/hospitality/features/qrMenuSessions.tsx
new file mode 100644
index 0000000..d26cfcb
--- /dev/null
+++ b/frontend/modules/hospitality/features/qrMenuSessions.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const GuestsPage = createHospitalityListPage({
+ title: "پروفایل مهمان",
+ breadcrumb: "پروفایل مهمان",
+ resourceKey: "qrMenuSessions",
+ bundleFeature: "qr_menu",
+ permission: "hospitality.view",
+ api: hospitalityApi.qrMenuSessions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default GuestsPage;
diff --git a/frontend/modules/hospitality/features/qrOrderingSessions.tsx b/frontend/modules/hospitality/features/qrOrderingSessions.tsx
new file mode 100644
index 0000000..5d6033e
--- /dev/null
+++ b/frontend/modules/hospitality/features/qrOrderingSessions.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const QrOrderingPage = createHospitalityListPage({
+ title: "سفارش QR",
+ breadcrumb: "سفارش QR",
+ resourceKey: "qrOrderingSessions",
+ bundleFeature: "qr_ordering",
+ permission: "hospitality.view",
+ api: hospitalityApi.qrOrderingSessions,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default QrOrderingPage;
diff --git a/frontend/modules/hospitality/features/receipts.tsx b/frontend/modules/hospitality/features/receipts.tsx
new file mode 100644
index 0000000..245d158
--- /dev/null
+++ b/frontend/modules/hospitality/features/receipts.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const ReceiptsPage = createHospitalityListPage({
+ title: "رسیدها",
+ breadcrumb: "رسیدها",
+ resourceKey: "posTickets",
+ bundleFeature: "pos_lite",
+ permission: "hospitality.view",
+ api: hospitalityApi.posTickets,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default ReceiptsPage;
diff --git a/frontend/modules/hospitality/features/reservations.tsx b/frontend/modules/hospitality/features/reservations.tsx
new file mode 100644
index 0000000..bdcd9e3
--- /dev/null
+++ b/frontend/modules/hospitality/features/reservations.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const ReservationsPage = createHospitalityListPage({
+ title: "رزروها",
+ breadcrumb: "رزروها",
+ resourceKey: "reservations",
+ bundleFeature: "reservation",
+ permission: "hospitality.view",
+ api: hospitalityApi.reservations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default ReservationsPage;
diff --git a/frontend/modules/hospitality/features/roles.tsx b/frontend/modules/hospitality/features/roles.tsx
new file mode 100644
index 0000000..19a17c4
--- /dev/null
+++ b/frontend/modules/hospitality/features/roles.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const RolesPage = createHospitalityListPage({
+ title: "نقشها",
+ breadcrumb: "نقشها",
+ resourceKey: "roles",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.roles,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default RolesPage;
diff --git a/frontend/modules/hospitality/features/settings.tsx b/frontend/modules/hospitality/features/settings.tsx
new file mode 100644
index 0000000..92bbc45
--- /dev/null
+++ b/frontend/modules/hospitality/features/settings.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const SettingsPage = createHospitalityListPage({
+ title: "تنظیمات",
+ breadcrumb: "تنظیمات",
+ resourceKey: "settings",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.settings,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default SettingsPage;
diff --git a/frontend/modules/hospitality/features/tables.tsx b/frontend/modules/hospitality/features/tables.tsx
new file mode 100644
index 0000000..0a2d31e
--- /dev/null
+++ b/frontend/modules/hospitality/features/tables.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const TablesPage = createHospitalityListPage({
+ title: "میزها",
+ breadcrumb: "میزها",
+ resourceKey: "tables",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.tables,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default TablesPage;
diff --git a/frontend/modules/hospitality/features/tenantBundles.tsx b/frontend/modules/hospitality/features/tenantBundles.tsx
new file mode 100644
index 0000000..d038d51
--- /dev/null
+++ b/frontend/modules/hospitality/features/tenantBundles.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const BundlesPage = createHospitalityListPage({
+ title: "بستههای فعال",
+ breadcrumb: "بستههای فعال",
+ resourceKey: "tenantBundles",
+ bundleFeature: null,
+ permission: "hospitality.view",
+ api: hospitalityApi.tenantBundles,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default BundlesPage;
diff --git a/frontend/modules/hospitality/features/venues.tsx b/frontend/modules/hospitality/features/venues.tsx
new file mode 100644
index 0000000..461b4bc
--- /dev/null
+++ b/frontend/modules/hospitality/features/venues.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+import { venueFormatLabels } from "@/modules/hospitality/design-system/tokens";
+
+export const BranchesPage = createHospitalityListPage({
+ title: "مکانها (Venues)",
+ description: "مدیریت مکانهای مهماننوازی — کافه، رستوران، فستفود و …",
+ breadcrumb: "مکانها",
+ resourceKey: "venues",
+ bundleFeature: null,
+ permission: "hospitality.venues.view",
+ api: hospitalityApi.venues,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ {
+ key: "venue_format",
+ header: "نوع",
+ render: (r) => venueFormatLabels[String(r.venue_format)] ?? String(r.venue_format),
+ },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "currency_code", header: "ارز" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ {
+ name: "venue_format",
+ label: "نوع کسبوکار",
+ type: "select",
+ required: true,
+ options: Object.entries(venueFormatLabels).map(([value, label]) => ({ value, label })),
+ },
+ ],
+ editFields: [
+ { name: "name", label: "نام", required: true },
+ {
+ name: "status",
+ label: "وضعیت",
+ type: "select",
+ options: [
+ { value: "draft", label: "پیشنویس" },
+ { value: "active", label: "فعال" },
+ { value: "inactive", label: "غیرفعال" },
+ ],
+ },
+ ],
+ filterDeleted: true,
+});
+
+export default BranchesPage;
diff --git a/frontend/modules/hospitality/features/waitlist.tsx b/frontend/modules/hospitality/features/waitlist.tsx
new file mode 100644
index 0000000..0df955a
--- /dev/null
+++ b/frontend/modules/hospitality/features/waitlist.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createHospitalityListPage } from "@/modules/hospitality/components/HospitalityListCrudPage";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export const QueuePage = createHospitalityListPage({
+ title: "صف انتظار",
+ breadcrumb: "صف انتظار",
+ resourceKey: "waitlist",
+ bundleFeature: "table_service",
+ permission: "hospitality.view",
+ api: hospitalityApi.waitlist,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default QueuePage;
diff --git a/frontend/modules/hospitality/hooks/useHospitalityCapabilities.ts b/frontend/modules/hospitality/hooks/useHospitalityCapabilities.ts
new file mode 100644
index 0000000..b6b791e
--- /dev/null
+++ b/frontend/modules/hospitality/hooks/useHospitalityCapabilities.ts
@@ -0,0 +1,35 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { hospitalityApi } from "@/modules/hospitality/services/hospitality-api";
+
+export function useHospitalityCapabilities() {
+ return useQuery({
+ queryKey: ["hospitality", "capabilities"],
+ queryFn: () => hospitalityApi.capabilities(),
+ staleTime: 60_000,
+ });
+}
+
+export function useHospitalityHealth() {
+ return useQuery({
+ queryKey: ["hospitality", "health"],
+ queryFn: () => hospitalityApi.health(),
+ });
+}
+
+export function useHospitalityLookups(tenantId: string | null) {
+ return useQuery({
+ queryKey: ["hospitality", tenantId, "lookups"],
+ queryFn: async () => {
+ const [venues, branches, menus] = await Promise.all([
+ hospitalityApi.venues.list(tenantId!, { page: 1, page_size: 500 }),
+ hospitalityApi.branches.list(tenantId!, { page: 1, page_size: 500 }),
+ hospitalityApi.menus.list(tenantId!, { page: 1, page_size: 500 }),
+ ]);
+ return { venues, branches, menus };
+ },
+ enabled: !!tenantId,
+ staleTime: 30_000,
+ });
+}
diff --git a/frontend/modules/hospitality/index.ts b/frontend/modules/hospitality/index.ts
new file mode 100644
index 0000000..815b2ef
--- /dev/null
+++ b/frontend/modules/hospitality/index.ts
@@ -0,0 +1,2 @@
+export * from "./types";
+export { hospitalityApi, HospitalityApiError } from "./services/hospitality-api";
diff --git a/frontend/modules/hospitality/pages/shared.tsx b/frontend/modules/hospitality/pages/shared.tsx
new file mode 100644
index 0000000..0b495a1
--- /dev/null
+++ b/frontend/modules/hospitality/pages/shared.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { LoadingState, ErrorState, EmptyState } from "@/components/ds";
+import { HospitalityApiError } from "@/modules/hospitality/services/hospitality-api";
+
+export function HospitalityPageLoader({ label = "در حال بارگذاری…" }: { label?: string }) {
+ return ;
+}
+
+export function HospitalityPageError({
+ error,
+ onRetry,
+}: {
+ error: unknown;
+ onRetry?: () => void;
+}) {
+ if (error instanceof HospitalityApiError && error.status === 403) {
+ return (
+
+ );
+ }
+ const message = error instanceof Error ? error.message : "خطا در دریافت دادهها";
+ return ;
+}
+
+export function exportToCsv(title: string, rows: Record[], columns: string[]) {
+ if (rows.length === 0) return;
+ const header = columns.join(",");
+ const body = rows
+ .map((r) => columns.map((c) => JSON.stringify(r[c] ?? "")).join(","))
+ .join("\n");
+ const blob = new Blob(["\uFEFF" + header + "\n" + body], { type: "text/csv;charset=utf-8" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = `${title}-export.csv`;
+ a.click();
+ URL.revokeObjectURL(url);
+}
diff --git a/frontend/modules/hospitality/services/hospitality-api.ts b/frontend/modules/hospitality/services/hospitality-api.ts
new file mode 100644
index 0000000..48ce552
--- /dev/null
+++ b/frontend/modules/hospitality/services/hospitality-api.ts
@@ -0,0 +1,657 @@
+/**
+ * Hospitality Service API client — Torbat Food (real backend only).
+ * Browser → /api/hospitality BFF; SSR → direct upstream :8009
+ */
+
+import { getValidAccessToken, refreshSession } from "@/lib/auth";
+import type { CapabilitiesResponse, HealthResponse, ListParams } from "@/modules/hospitality/types";
+
+const HOSPITALITY_BASE =
+ typeof window !== "undefined"
+ ? "/api/hospitality"
+ : process.env.HOSPITALITY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_HOSPITALITY_API_URL ||
+ "http://localhost:8009";
+
+export class HospitalityApiError extends Error {
+ constructor(
+ public status: number,
+ public code: string,
+ message: string
+ ) {
+ super(message);
+ this.name = "HospitalityApiError";
+ }
+}
+
+type RequestOptions = RequestInit & { tenantId: string; auth?: boolean };
+
+async function request(path: string, options: RequestOptions): Promise {
+ const { tenantId, auth = true, headers: customHeaders, ...init } = options;
+ const headers = new Headers(customHeaders);
+ if (!headers.has("Content-Type") && init.body) {
+ headers.set("Content-Type", "application/json");
+ }
+ headers.set("X-Tenant-ID", tenantId);
+ if (auth) {
+ const token = await getValidAccessToken();
+ if (token) headers.set("Authorization", `Bearer ${token}`);
+ }
+
+ const url = path.startsWith("http")
+ ? path
+ : `${HOSPITALITY_BASE.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
+
+ let res: Response;
+ try {
+ res = await fetch(url, { ...init, headers });
+ } catch (err) {
+ const detail = err instanceof Error ? err.message : "network_error";
+ throw new Error(`اتصال به سرویس تربت فود برقرار نشد (${url}) — ${detail}`);
+ }
+
+ if (res.status === 401 && auth) {
+ const refreshed = await refreshSession({ force: true });
+ if (refreshed) {
+ headers.set("Authorization", `Bearer ${refreshed}`);
+ res = await fetch(url, { ...init, headers });
+ }
+ }
+
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ throw new HospitalityApiError(
+ res.status,
+ body?.error?.code || body?.detail || "unknown_error",
+ body?.error?.message || body?.detail || res.statusText
+ );
+ }
+ if (res.status === 204) return undefined as T;
+ return res.json() as Promise;
+}
+
+function qs(params?: Record) {
+ if (!params) return "";
+ const sp = new URLSearchParams();
+ Object.entries(params).forEach(([k, v]) => {
+ if (v !== undefined && v !== null && v !== "") sp.set(k, String(v));
+ });
+ const s = sp.toString();
+ return s ? `?${s}` : "";
+}
+
+function action(tenantId: string, apiPath: string, body?: Record) {
+ return request>(apiPath, {
+ tenantId,
+ method: "POST",
+ body: body ? JSON.stringify(body) : undefined,
+ });
+}
+
+export const hospitalityApi = {
+ health: (): Promise =>
+ fetch(`${HOSPITALITY_BASE}/health`).then((r) => r.json()),
+
+ capabilities: (): Promise =>
+ fetch(`${HOSPITALITY_BASE}/capabilities`).then((r) => r.json()),
+
+ venues: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/venues${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/venues/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/venues`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`/api/v1/venues/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ remove: (tenantId: string, id: string) =>
+ request>(`/api/v1/venues/${id}/delete`, { tenantId, method: "POST" }),
+ },
+ branches: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/branches${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/branches/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/branches`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`/api/v1/branches/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ remove: (tenantId: string, id: string) =>
+ request>(`/api/v1/branches/${id}/delete`, { tenantId, method: "POST" }),
+ },
+ diningAreas: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/dining-areas${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/dining-areas/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/dining-areas`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ tables: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/tables${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/tables/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/tables`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ menus: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menus${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/menus/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/menus`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`/api/v1/menus/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ remove: (tenantId: string, id: string) =>
+ request>(`/api/v1/menus/${id}/delete`, { tenantId, method: "POST" }),
+ },
+ menuCategories: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-categories${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/menu-categories/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/menu-categories`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ menuItems: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-items${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/menu-items/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/menu-items`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ allergens: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/allergens${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/allergens/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/allergens`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ modifierGroups: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/modifier-groups${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/modifier-groups/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/modifier-groups`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ modifierOptions: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/modifier-options${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/modifier-options/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/modifier-options`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ availabilityWindows: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/availability-windows${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/availability-windows/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/availability-windows`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ menuMediaRefs: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-media-refs${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/menu-media-refs/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/menu-media-refs`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ qrCodes: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/qr-codes${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/qr-codes/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/qr-codes`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ qrMenuSessions: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/qr-menu-sessions${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/qr-menu-sessions/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/qr-menu-sessions`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ carts: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/carts${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/carts/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/carts`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ cartLines: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/cart-lines${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/cart-lines/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/cart-lines`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posRegisters: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-registers${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-registers/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-registers`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posTicketLines: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-ticket-lines${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-ticket-lines/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-ticket-lines`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posPayments: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-payments${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-payments/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-payments`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posDiscounts: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-discounts${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-discounts/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-discounts`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posTaxLines: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-tax-lines${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-tax-lines/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-tax-lines`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posFloorPlans: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-floor-plans${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-floor-plans/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-floor-plans`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ posStations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-stations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-stations/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/pos-stations`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ kitchenStations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/kitchen-stations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/kitchen-stations/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/kitchen-stations`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ connectorRegistrations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/connector-registrations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/connector-registrations/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/connector-registrations`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ connectorDispatches: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/connector-dispatches${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/connector-dispatches/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/connector-dispatches`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ analyticsReports: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/analytics-reports${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/analytics-reports/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/analytics-reports`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ bundleDefinitions: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/bundle-definitions${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/bundle-definitions/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/bundle-definitions`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ roles: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/roles${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/roles/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/roles`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ permissions: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/permissions${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/permissions/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/permissions`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ configurations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/configurations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/configurations/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/configurations`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`/api/v1/configurations/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ },
+ events: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/events${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/events/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/events`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+
+ tenantBundles: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/tenant-bundles${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/tenant-bundles/${id}`, { tenantId }),
+ activate: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/tenant-bundles/activate", body),
+ deactivate: (tenantId: string, id: string) =>
+ action(tenantId, `/api/v1/tenant-bundles/${id}/deactivate`),
+ },
+ featureToggles: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/feature-toggles${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/feature-toggles/${id}`, { tenantId }),
+ upsert: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/feature-toggles/upsert", body),
+ },
+ menuItemModifiers: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-item-modifiers${qs(params)}`, { tenantId }),
+ link: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/menu-item-modifiers/link", body),
+ },
+ menuItemAllergens: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-item-allergens${qs(params)}`, { tenantId }),
+ link: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/menu-item-allergens/link", body),
+ },
+ menuLocalizations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/menu-localizations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/menu-localizations/${id}`, { tenantId }),
+ upsert: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/menu-localizations/upsert", body),
+ },
+ settings: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/settings${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/settings/${id}`, { tenantId }),
+ upsert: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/settings/upsert", body),
+ },
+ reservations: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/reservations${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/reservations/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/reservations", body),
+ updateStatus: (tenantId: string, id: string, body: Record) =>
+ action(tenantId, `/api/v1/reservations/${id}/status`, body),
+ },
+ tableAssignments: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/table-assignments${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/table-assignments/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/table-assignments", body),
+ release: (tenantId: string, id: string) =>
+ action(tenantId, `/api/v1/table-assignments/${id}/release`),
+ },
+ serviceRequests: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/service-requests${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/service-requests/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/service-requests", body),
+ updateStatus: (tenantId: string, id: string, body: Record) =>
+ action(tenantId, `/api/v1/service-requests/${id}/status`, body),
+ },
+ waitlist: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/waitlist${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/waitlist/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/waitlist", body),
+ updateStatus: (tenantId: string, id: string, body: Record) =>
+ action(tenantId, `/api/v1/waitlist/${id}/status`, body),
+ },
+ posShifts: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-shifts${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-shifts/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/pos-shifts", body),
+ close: (tenantId: string, id: string) =>
+ action(tenantId, `/api/v1/pos-shifts/${id}/close`),
+ },
+ posTickets: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/pos-tickets${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/pos-tickets/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/pos-tickets", body),
+ void: (tenantId: string, id: string) =>
+ action(tenantId, `/api/v1/pos-tickets/${id}/void`),
+ },
+ qrOrderingSessions: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/qr-ordering-sessions${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/qr-ordering-sessions/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/qr-ordering-sessions", body),
+ submit: (tenantId: string, id: string) =>
+ action(tenantId, `/api/v1/qr-ordering-sessions/${id}/submit`),
+ },
+ kitchenTickets: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/kitchen-tickets${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/kitchen-tickets/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/kitchen-tickets", body),
+ updateStatus: (tenantId: string, id: string, body: Record) =>
+ action(tenantId, `/api/v1/kitchen-tickets/${id}/status`, body),
+ },
+ kitchenTicketItems: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/kitchen-ticket-items${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/kitchen-ticket-items/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/kitchen-ticket-items", body),
+ updateStatus: (tenantId: string, id: string, body: Record) =>
+ action(tenantId, `/api/v1/kitchen-ticket-items/${id}/status`, body),
+ },
+ analyticsSnapshots: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/analytics-snapshots${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/analytics-snapshots/${id}`, { tenantId }),
+ refresh: (tenantId: string, body: Record) =>
+ action(tenantId, "/api/v1/analytics-snapshots/refresh", body),
+ },
+};
diff --git a/frontend/modules/hospitality/types/index.ts b/frontend/modules/hospitality/types/index.ts
new file mode 100644
index 0000000..f856757
--- /dev/null
+++ b/frontend/modules/hospitality/types/index.ts
@@ -0,0 +1,156 @@
+/** Hospitality Platform (Torbat Food) — shared TypeScript types aligned with backend DTOs. */
+
+export type LifecycleStatus = "draft" | "active" | "inactive" | "suspended" | "archived";
+export type VenueFormat =
+ | "cafe"
+ | "coffee_shop"
+ | "restaurant"
+ | "fast_food"
+ | "bakery"
+ | "pastry"
+ | "ice_cream"
+ | "juice_bar"
+ | "cloud_kitchen"
+ | "food_court"
+ | "catering"
+ | "take_away"
+ | "other";
+
+export type BundleKey =
+ | "digital_menu"
+ | "qr_menu"
+ | "qr_ordering"
+ | "pos_lite"
+ | "pos_pro"
+ | "kitchen"
+ | "reservation"
+ | "table_service"
+ | "delivery_connector"
+ | "accounting_connector"
+ | "crm_connector"
+ | "loyalty_connector"
+ | "communication_connector"
+ | "website_connector"
+ | "analytics"
+ | "ai_assistant";
+
+export type BundleStatus = "available" | "active" | "suspended" | "retired";
+export type TableStatus = "available" | "occupied" | "reserved" | "cleaning" | "out_of_service";
+export type MenuStatus = "draft" | "published" | "archived";
+
+export type OrmBase = {
+ id: string;
+ tenant_id: string;
+ is_deleted: boolean;
+ created_by: string | null;
+ updated_by: string | null;
+ created_at: string;
+ updated_at: string;
+};
+
+export type ListParams = { page?: number; page_size?: number };
+
+export type HealthResponse = { status: string; service: string; version: string };
+
+export type CapabilitiesResponse = {
+ service: string;
+ version: string;
+ phase: string;
+ product: string;
+ platform: string;
+ features: Record;
+ venue_formats: VenueFormat[];
+ bundles: BundleKey[];
+ independence: {
+ standalone: boolean;
+ superapp: boolean;
+ integrations: string[];
+ integration_mode: string;
+ };
+};
+
+export type Venue = OrmBase & {
+ code: string;
+ name: string;
+ description: string | null;
+ status: LifecycleStatus;
+ venue_format: VenueFormat;
+ legal_name: string | null;
+ timezone: string;
+ language: string;
+ currency_code: string;
+ settings: Record | null;
+ version: number;
+};
+
+export type Branch = OrmBase & {
+ venue_id: string;
+ code: string;
+ name: string;
+ description: string | null;
+ status: LifecycleStatus;
+ address: Record | null;
+ timezone: string | null;
+ working_hours: Record | null;
+ metadata_json: Record | null;
+};
+
+export type DiningArea = OrmBase & {
+ venue_id: string;
+ branch_id: string | null;
+ code: string;
+ name: string;
+ description: string | null;
+ status: LifecycleStatus;
+ sort_order: number;
+ metadata_json: Record | null;
+};
+
+export type DiningTable = OrmBase & {
+ venue_id: string;
+ dining_area_id: string | null;
+ code: string;
+ name: string;
+ capacity: number;
+ status: TableStatus;
+ qr_token: string | null;
+ metadata_json: Record | null;
+};
+
+export type Menu = OrmBase & {
+ venue_id: string;
+ code: string;
+ name: string;
+ description: string | null;
+ status: MenuStatus;
+ is_default: boolean;
+ currency_code: string;
+ version: number;
+};
+
+export type MenuCategory = OrmBase & {
+ venue_id: string;
+ menu_id: string;
+ code: string;
+ name: string;
+ description: string | null;
+ sort_order: number;
+};
+
+export type MenuItem = OrmBase & {
+ menu_id: string;
+ category_id: string | null;
+ code: string;
+ name: string;
+ description: string | null;
+ status: LifecycleStatus;
+ base_price: number;
+ is_vegetarian: boolean;
+ is_vegan: boolean;
+ is_gluten_free: boolean;
+ preparation_minutes: number | null;
+ calories: number | null;
+ spicy_level: number | null;
+ tags: string[] | null;
+ primary_media_ref: string | null;
+};
diff --git a/frontend/package.json b/frontend/package.json
index b2fd883..5684f08 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,19 @@
"dev": "next dev --hostname 0.0.0.0 --port 3000",
"build": "next build",
"start": "next start --hostname 0.0.0.0 --port 3000",
- "lint": "next lint"
+ "lint": "next lint",
+ "typecheck": "tsc --noEmit",
+ "validate:architecture": "node scripts/validate-architecture.mjs",
+ "validate:beauty-imports": "node scripts/validate-beauty-imports.mjs",
+ "validate:beauty-routes": "node scripts/validate-beauty-routes.mjs",
+ "validate:beauty-bundle": "node scripts/validate-beauty-bundle.mjs",
+ "validate:healthcare-imports": "node scripts/validate-healthcare-imports.mjs",
+ "validate:healthcare-routes": "node scripts/validate-healthcare-routes.mjs",
+ "validate:healthcare-bundle": "node scripts/validate-healthcare-bundle.mjs",
+ "validate:healthcare-circular": "node scripts/validate-healthcare-circular.mjs",
+ "validate:healthcare": "npm run validate:healthcare-imports && npm run validate:healthcare-routes && npm run validate:healthcare-circular && npm run validate:architecture",
+ "validate:hospitality-routes": "node scripts/validate-hospitality-routes.mjs",
+ "validate:hospitality": "npm run validate:hospitality-routes && npm run validate:architecture"
},
"dependencies": {
"@hookform/resolvers": "^5.4.0",
@@ -33,6 +45,8 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"autoprefixer": "^10.4.0",
+ "eslint": "^8.57.1",
+ "eslint-config-next": "^15.5.18",
"postcss": "^8.4.0",
"tailwindcss": "^3.4.0",
"typescript": "^5.4.0"
diff --git a/frontend/scripts/gen-hospitality-api.mjs b/frontend/scripts/gen-hospitality-api.mjs
new file mode 100644
index 0000000..bc5f5bb
--- /dev/null
+++ b/frontend/scripts/gen-hospitality-api.mjs
@@ -0,0 +1,326 @@
+#!/usr/bin/env node
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
+
+const RESOURCES = [
+ ["venues", true, true],
+ ["branches", true, true],
+ ["dining-areas", false, false],
+ ["tables", false, false],
+ ["menus", true, true],
+ ["menu-categories", false, false],
+ ["menu-items", false, false],
+ ["allergens", false, false],
+ ["modifier-groups", false, false],
+ ["modifier-options", false, false],
+ ["availability-windows", false, false],
+ ["menu-media-refs", false, false],
+ ["qr-codes", false, false],
+ ["qr-menu-sessions", false, false],
+ ["carts", false, false],
+ ["cart-lines", false, false],
+ ["pos-registers", false, false],
+ ["pos-ticket-lines", false, false],
+ ["pos-payments", false, false],
+ ["pos-discounts", false, false],
+ ["pos-tax-lines", false, false],
+ ["pos-floor-plans", false, false],
+ ["pos-stations", false, false],
+ ["kitchen-stations", false, false],
+ ["connector-registrations", false, false],
+ ["connector-dispatches", false, false],
+ ["analytics-reports", false, false],
+ ["bundle-definitions", false, false],
+ ["roles", false, false],
+ ["permissions", false, false],
+ ["configurations", true, false],
+ ["events", false, false],
+];
+
+function kebabToProp(s) {
+ return s.split("-").map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1))).join("");
+}
+
+let apiBlocks = "";
+for (const [p, hasUpdate, hasDelete] of RESOURCES) {
+ const prop = kebabToProp(p);
+ let block = ` ${prop}: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(\`/api/v1/${p}\${qs(params)}\`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(\`/api/v1/${p}/\${id}\`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(\`/api/v1/${p}\`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),`;
+ if (hasUpdate) {
+ block += `
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(\`/api/v1/${p}/\${id}\`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),`;
+ }
+ if (hasDelete) {
+ block += `
+ remove: (tenantId: string, id: string) =>
+ request>(\`/api/v1/${p}/\${id}/delete\`, { tenantId, method: "POST" }),`;
+ }
+ block += `
+ },`;
+ apiBlocks += block + "\n";
+}
+
+const header = `/**
+ * Hospitality Service API client — Torbat Food (real backend only).
+ * Browser → /api/hospitality BFF; SSR → direct upstream :8009
+ */
+
+import { getValidAccessToken, refreshSession } from "@/lib/auth";
+import type { CapabilitiesResponse, HealthResponse, ListParams } from "@/modules/hospitality/types";
+
+const HOSPITALITY_BASE =
+ typeof window !== "undefined"
+ ? "/api/hospitality"
+ : process.env.HOSPITALITY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_HOSPITALITY_API_URL ||
+ "http://localhost:8009";
+
+export class HospitalityApiError extends Error {
+ constructor(
+ public status: number,
+ public code: string,
+ message: string
+ ) {
+ super(message);
+ this.name = "HospitalityApiError";
+ }
+}
+
+type RequestOptions = RequestInit & { tenantId: string; auth?: boolean };
+
+async function request(path: string, options: RequestOptions): Promise {
+ const { tenantId, auth = true, headers: customHeaders, ...init } = options;
+ const headers = new Headers(customHeaders);
+ if (!headers.has("Content-Type") && init.body) {
+ headers.set("Content-Type", "application/json");
+ }
+ headers.set("X-Tenant-ID", tenantId);
+ if (auth) {
+ const token = await getValidAccessToken();
+ if (token) headers.set("Authorization", \`Bearer \${token}\`);
+ }
+
+ const url = path.startsWith("http")
+ ? path
+ : \`\${HOSPITALITY_BASE.replace(/\\/$/, "")}\${path.startsWith("/") ? path : \`/\${path}\`}\`;
+
+ let res: Response;
+ try {
+ res = await fetch(url, { ...init, headers });
+ } catch (err) {
+ const detail = err instanceof Error ? err.message : "network_error";
+ throw new Error(\`اتصال به سرویس تربت فود برقرار نشد (\${url}) — \${detail}\`);
+ }
+
+ if (res.status === 401 && auth) {
+ const refreshed = await refreshSession({ force: true });
+ if (refreshed) {
+ headers.set("Authorization", \`Bearer \${refreshed}\`);
+ res = await fetch(url, { ...init, headers });
+ }
+ }
+
+ if (!res.ok) {
+ const body = await res.json().catch(() => ({}));
+ throw new HospitalityApiError(
+ res.status,
+ body?.error?.code || body?.detail || "unknown_error",
+ body?.error?.message || body?.detail || res.statusText
+ );
+ }
+ if (res.status === 204) return undefined as T;
+ return res.json() as Promise;
+}
+
+function qs(params?: Record) {
+ if (!params) return "";
+ const sp = new URLSearchParams();
+ Object.entries(params).forEach(([k, v]) => {
+ if (v !== undefined && v !== null && v !== "") sp.set(k, String(v));
+ });
+ const s = sp.toString();
+ return s ? \`?\${s}\` : "";
+}
+
+function action(tenantId: string, apiPath: string, body?: Record) {
+ return request>(apiPath, {
+ tenantId,
+ method: "POST",
+ body: body ? JSON.stringify(body) : undefined,
+ });
+}
+
+export const hospitalityApi = {
+ health: (): Promise =>
+ fetch(\`\${HOSPITALITY_BASE}/health\`).then((r) => r.json()),
+
+ capabilities: (): Promise =>
+ fetch(\`\${HOSPITALITY_BASE}/capabilities\`).then((r) => r.json()),
+
+${apiBlocks}
+ tenantBundles: {
+ list: (tenantId: string, params?: ListParams) =>
+ request