diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
index b5cc80a..7f90862 100644
--- a/frontend/.eslintrc.json
+++ b/frontend/.eslintrc.json
@@ -95,6 +95,32 @@
]
}
},
+ {
+ "files": ["app/delivery/**/*", "modules/delivery/**/*"],
+ "rules": {
+ "no-restricted-imports": [
+ "error",
+ {
+ "patterns": [
+ {
+ "group": [
+ "@/components/accounting/**",
+ "@/components/beauty/**",
+ "@/components/healthcare/**",
+ "@/modules/beauty/**",
+ "@/modules/accounting/**",
+ "@/modules/hospitality/**",
+ "@/modules/crm/**",
+ "@/src/modules/healthcare/**",
+ "@/src/modules/accounting/**"
+ ],
+ "message": "Delivery must not import other business modules."
+ }
+ ]
+ }
+ ]
+ }
+ },
{
"files": [
"components/ds/**/*",
@@ -120,6 +146,7 @@
"@/modules/beauty/**",
"@/modules/healthcare/**",
"@/modules/hospitality/**",
+ "@/modules/delivery/**",
"@/src/modules/healthcare/**",
"@/src/modules/accounting/**",
"@/modules/accounting/**",
diff --git a/frontend/app/api/delivery/[...path]/route.ts b/frontend/app/api/delivery/[...path]/route.ts
new file mode 100644
index 0000000..b1027f7
--- /dev/null
+++ b/frontend/app/api/delivery/[...path]/route.ts
@@ -0,0 +1,53 @@
+import { NextRequest, NextResponse } from "next/server";
+
+const UPSTREAM =
+ process.env.DELIVERY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_DELIVERY_API_URL ||
+ "http://localhost:8007";
+
+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: "delivery_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/delivery/ai/recommendations/error.tsx b/frontend/app/delivery/ai/recommendations/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/ai/recommendations/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/delivery/ai/recommendations/loading.tsx b/frontend/app/delivery/ai/recommendations/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/ai/recommendations/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/ai/recommendations/page.tsx b/frontend/app/delivery/ai/recommendations/page.tsx
new file mode 100644
index 0000000..1c2965f
--- /dev/null
+++ b/frontend/app/delivery/ai/recommendations/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AiRecommendationsPage as default } from "@/modules/delivery/features/aiRecommendations";
diff --git a/frontend/app/delivery/analytics/error.tsx b/frontend/app/delivery/analytics/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/analytics/heatmaps/error.tsx b/frontend/app/delivery/analytics/heatmaps/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/analytics/heatmaps/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/delivery/analytics/heatmaps/loading.tsx b/frontend/app/delivery/analytics/heatmaps/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/analytics/heatmaps/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/analytics/heatmaps/page.tsx b/frontend/app/delivery/analytics/heatmaps/page.tsx
new file mode 100644
index 0000000..a9fc2cc
--- /dev/null
+++ b/frontend/app/delivery/analytics/heatmaps/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { HeatMapsPage as default } from "@/modules/delivery/features/heatmaps";
diff --git a/frontend/app/delivery/analytics/kpis/error.tsx b/frontend/app/delivery/analytics/kpis/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/analytics/kpis/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/delivery/analytics/kpis/loading.tsx b/frontend/app/delivery/analytics/kpis/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/analytics/kpis/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/analytics/kpis/page.tsx b/frontend/app/delivery/analytics/kpis/page.tsx
new file mode 100644
index 0000000..0145405
--- /dev/null
+++ b/frontend/app/delivery/analytics/kpis/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PerformanceKpisPage as default } from "@/modules/delivery/features/kpis";
diff --git a/frontend/app/delivery/analytics/loading.tsx b/frontend/app/delivery/analytics/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/analytics/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/analytics/page.tsx b/frontend/app/delivery/analytics/page.tsx
new file mode 100644
index 0000000..a8dbe48
--- /dev/null
+++ b/frontend/app/delivery/analytics/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AnalyticsPage as default } from "@/modules/delivery/features/analytics";
diff --git a/frontend/app/delivery/api-keys/error.tsx b/frontend/app/delivery/api-keys/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/api-keys/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/delivery/api-keys/loading.tsx b/frontend/app/delivery/api-keys/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/api-keys/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/api-keys/page.tsx b/frontend/app/delivery/api-keys/page.tsx
new file mode 100644
index 0000000..22d9eda
--- /dev/null
+++ b/frontend/app/delivery/api-keys/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ApiKeysPage as default } from "@/modules/delivery/features/apiKeys";
diff --git a/frontend/app/delivery/audit/error.tsx b/frontend/app/delivery/audit/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/audit/loading.tsx b/frontend/app/delivery/audit/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/audit/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/audit/page.tsx b/frontend/app/delivery/audit/page.tsx
new file mode 100644
index 0000000..2e6e0c0
--- /dev/null
+++ b/frontend/app/delivery/audit/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AuditLogsPage as default } from "@/modules/delivery/features/audit";
diff --git a/frontend/app/delivery/automation/error.tsx b/frontend/app/delivery/automation/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/automation/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/delivery/automation/loading.tsx b/frontend/app/delivery/automation/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/automation/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/automation/page.tsx b/frontend/app/delivery/automation/page.tsx
new file mode 100644
index 0000000..48fb50d
--- /dev/null
+++ b/frontend/app/delivery/automation/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AutomationPage as default } from "@/modules/delivery/features/automation";
diff --git a/frontend/app/delivery/branches/error.tsx b/frontend/app/delivery/branches/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/branches/loading.tsx b/frontend/app/delivery/branches/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/branches/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/branches/page.tsx b/frontend/app/delivery/branches/page.tsx
new file mode 100644
index 0000000..64b8bba
--- /dev/null
+++ b/frontend/app/delivery/branches/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { BranchesPage as default } from "@/modules/delivery/features/branches";
diff --git a/frontend/app/delivery/capabilities/error.tsx b/frontend/app/delivery/capabilities/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/capabilities/loading.tsx b/frontend/app/delivery/capabilities/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/capabilities/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/capabilities/page.tsx b/frontend/app/delivery/capabilities/page.tsx
new file mode 100644
index 0000000..d3f7cb0
--- /dev/null
+++ b/frontend/app/delivery/capabilities/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CapabilitiesPage as default } from "@/modules/delivery/features/capabilities";
diff --git a/frontend/app/delivery/cod/error.tsx b/frontend/app/delivery/cod/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/cod/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/delivery/cod/loading.tsx b/frontend/app/delivery/cod/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/cod/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/cod/page.tsx b/frontend/app/delivery/cod/page.tsx
new file mode 100644
index 0000000..9b50ac0
--- /dev/null
+++ b/frontend/app/delivery/cod/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CodManagementPage as default } from "@/modules/delivery/features/cod";
diff --git a/frontend/app/delivery/commissions/error.tsx b/frontend/app/delivery/commissions/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/commissions/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/delivery/commissions/loading.tsx b/frontend/app/delivery/commissions/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/commissions/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/commissions/page.tsx b/frontend/app/delivery/commissions/page.tsx
new file mode 100644
index 0000000..bf2220a
--- /dev/null
+++ b/frontend/app/delivery/commissions/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CommissionsPage as default } from "@/modules/delivery/features/commissions";
diff --git a/frontend/app/delivery/configurations/error.tsx b/frontend/app/delivery/configurations/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/configurations/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/delivery/configurations/loading.tsx b/frontend/app/delivery/configurations/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/configurations/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/configurations/page.tsx b/frontend/app/delivery/configurations/page.tsx
new file mode 100644
index 0000000..9410dec
--- /dev/null
+++ b/frontend/app/delivery/configurations/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ConfigurationsPage as default } from "@/modules/delivery/features/configurations";
diff --git a/frontend/app/delivery/deliveries/error.tsx b/frontend/app/delivery/deliveries/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/deliveries/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/delivery/deliveries/loading.tsx b/frontend/app/delivery/deliveries/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/deliveries/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/deliveries/page.tsx b/frontend/app/delivery/deliveries/page.tsx
new file mode 100644
index 0000000..d39897e
--- /dev/null
+++ b/frontend/app/delivery/deliveries/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DeliveriesPage as default } from "@/modules/delivery/features/deliveries";
diff --git a/frontend/app/delivery/dispatch/assignment/error.tsx b/frontend/app/delivery/dispatch/assignment/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/dispatch/assignment/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/delivery/dispatch/assignment/loading.tsx b/frontend/app/delivery/dispatch/assignment/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/dispatch/assignment/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/dispatch/assignment/page.tsx b/frontend/app/delivery/dispatch/assignment/page.tsx
new file mode 100644
index 0000000..3fcaa56
--- /dev/null
+++ b/frontend/app/delivery/dispatch/assignment/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { AssignmentCenterPage as default } from "@/modules/delivery/features/assignment";
diff --git a/frontend/app/delivery/dispatch/queue/error.tsx b/frontend/app/delivery/dispatch/queue/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/dispatch/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/delivery/dispatch/queue/loading.tsx b/frontend/app/delivery/dispatch/queue/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/dispatch/queue/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/dispatch/queue/page.tsx b/frontend/app/delivery/dispatch/queue/page.tsx
new file mode 100644
index 0000000..22b9005
--- /dev/null
+++ b/frontend/app/delivery/dispatch/queue/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DispatchQueuePage as default } from "@/modules/delivery/features/dispatchQueue";
diff --git a/frontend/app/delivery/dispatcher/error.tsx b/frontend/app/delivery/dispatcher/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/dispatcher/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/delivery/dispatcher/loading.tsx b/frontend/app/delivery/dispatcher/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/dispatcher/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/dispatcher/page.tsx b/frontend/app/delivery/dispatcher/page.tsx
new file mode 100644
index 0000000..9a3641b
--- /dev/null
+++ b/frontend/app/delivery/dispatcher/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DispatcherDashboard as default } from "@/modules/delivery/features/dispatcher";
diff --git a/frontend/app/delivery/drivers/[id]/error.tsx b/frontend/app/delivery/drivers/[id]/error.tsx
new file mode 100644
index 0000000..cf81b54
--- /dev/null
+++ b/frontend/app/delivery/drivers/[id]/error.tsx
@@ -0,0 +1,3 @@
+"use client";
+import { ErrorState } from "@/components/ds";
+export default function Error({ error, reset }: { error: Error; reset: () => void }) { return ; }
diff --git a/frontend/app/delivery/drivers/[id]/loading.tsx b/frontend/app/delivery/drivers/[id]/loading.tsx
new file mode 100644
index 0000000..0c1a2f2
--- /dev/null
+++ b/frontend/app/delivery/drivers/[id]/loading.tsx
@@ -0,0 +1,2 @@
+import { LoadingState } from "@/components/ds";
+export default function Loading() { return ; }
diff --git a/frontend/app/delivery/drivers/[id]/page.tsx b/frontend/app/delivery/drivers/[id]/page.tsx
new file mode 100644
index 0000000..3e85cdc
--- /dev/null
+++ b/frontend/app/delivery/drivers/[id]/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DriverDetailPage as default } from "@/modules/delivery/features/driverDetail";
diff --git a/frontend/app/delivery/drivers/availability/error.tsx b/frontend/app/delivery/drivers/availability/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/drivers/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/delivery/drivers/availability/loading.tsx b/frontend/app/delivery/drivers/availability/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/drivers/availability/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/drivers/availability/page.tsx b/frontend/app/delivery/drivers/availability/page.tsx
new file mode 100644
index 0000000..8cd62b7
--- /dev/null
+++ b/frontend/app/delivery/drivers/availability/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DriverAvailabilityPage as default } from "@/modules/delivery/features/driverAvailability";
diff --git a/frontend/app/delivery/drivers/error.tsx b/frontend/app/delivery/drivers/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/drivers/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/delivery/drivers/loading.tsx b/frontend/app/delivery/drivers/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/drivers/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/drivers/page.tsx b/frontend/app/delivery/drivers/page.tsx
new file mode 100644
index 0000000..b6eef34
--- /dev/null
+++ b/frontend/app/delivery/drivers/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DriversPage as default } from "@/modules/delivery/features/drivers";
diff --git a/frontend/app/delivery/drivers/performance/error.tsx b/frontend/app/delivery/drivers/performance/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/drivers/performance/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/delivery/drivers/performance/loading.tsx b/frontend/app/delivery/drivers/performance/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/drivers/performance/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/drivers/performance/page.tsx b/frontend/app/delivery/drivers/performance/page.tsx
new file mode 100644
index 0000000..c6e3094
--- /dev/null
+++ b/frontend/app/delivery/drivers/performance/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DriverPerformancePage as default } from "@/modules/delivery/features/driverPerformance";
diff --git a/frontend/app/delivery/error.tsx b/frontend/app/delivery/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/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/delivery/failed/error.tsx b/frontend/app/delivery/failed/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/failed/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/delivery/failed/loading.tsx b/frontend/app/delivery/failed/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/failed/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/failed/page.tsx b/frontend/app/delivery/failed/page.tsx
new file mode 100644
index 0000000..568d4e3
--- /dev/null
+++ b/frontend/app/delivery/failed/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { FailedDeliveriesPage as default } from "@/modules/delivery/features/failed";
diff --git a/frontend/app/delivery/fleet-dashboard/error.tsx b/frontend/app/delivery/fleet-dashboard/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/fleet-dashboard/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/delivery/fleet-dashboard/loading.tsx b/frontend/app/delivery/fleet-dashboard/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/fleet-dashboard/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/fleet-dashboard/page.tsx b/frontend/app/delivery/fleet-dashboard/page.tsx
new file mode 100644
index 0000000..04040f1
--- /dev/null
+++ b/frontend/app/delivery/fleet-dashboard/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { FleetDashboard as default } from "@/modules/delivery/features/fleetDashboard";
diff --git a/frontend/app/delivery/fleet/maintenance/error.tsx b/frontend/app/delivery/fleet/maintenance/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/fleet/maintenance/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/delivery/fleet/maintenance/loading.tsx b/frontend/app/delivery/fleet/maintenance/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/fleet/maintenance/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/fleet/maintenance/page.tsx b/frontend/app/delivery/fleet/maintenance/page.tsx
new file mode 100644
index 0000000..223e97b
--- /dev/null
+++ b/frontend/app/delivery/fleet/maintenance/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { FleetMaintenancePage as default } from "@/modules/delivery/features/maintenance";
diff --git a/frontend/app/delivery/health/error.tsx b/frontend/app/delivery/health/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/health/loading.tsx b/frontend/app/delivery/health/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/health/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/health/page.tsx b/frontend/app/delivery/health/page.tsx
new file mode 100644
index 0000000..0ce919a
--- /dev/null
+++ b/frontend/app/delivery/health/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { HealthPage as default } from "@/modules/delivery/features/health";
diff --git a/frontend/app/delivery/hub/error.tsx b/frontend/app/delivery/hub/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/hub/loading.tsx b/frontend/app/delivery/hub/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/hub/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/hub/page.tsx b/frontend/app/delivery/hub/page.tsx
new file mode 100644
index 0000000..179532e
--- /dev/null
+++ b/frontend/app/delivery/hub/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DeliveryHub as default } from "@/modules/delivery/features/hub";
diff --git a/frontend/app/delivery/incidents/error.tsx b/frontend/app/delivery/incidents/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/incidents/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/delivery/incidents/loading.tsx b/frontend/app/delivery/incidents/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/incidents/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/incidents/page.tsx b/frontend/app/delivery/incidents/page.tsx
new file mode 100644
index 0000000..08e1297
--- /dev/null
+++ b/frontend/app/delivery/incidents/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { IncidentsPage as default } from "@/modules/delivery/features/incidents";
diff --git a/frontend/app/delivery/integrations/providers/error.tsx b/frontend/app/delivery/integrations/providers/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/integrations/providers/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/delivery/integrations/providers/loading.tsx b/frontend/app/delivery/integrations/providers/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/integrations/providers/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/integrations/providers/page.tsx b/frontend/app/delivery/integrations/providers/page.tsx
new file mode 100644
index 0000000..549cbc4
--- /dev/null
+++ b/frontend/app/delivery/integrations/providers/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ExternalProvidersPage as default } from "@/modules/delivery/features/externalProviders";
diff --git a/frontend/app/delivery/integrations/routing/error.tsx b/frontend/app/delivery/integrations/routing/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/integrations/routing/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/delivery/integrations/routing/loading.tsx b/frontend/app/delivery/integrations/routing/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/integrations/routing/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/integrations/routing/page.tsx b/frontend/app/delivery/integrations/routing/page.tsx
new file mode 100644
index 0000000..9f75e15
--- /dev/null
+++ b/frontend/app/delivery/integrations/routing/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { RoutingEnginesPage as default } from "@/modules/delivery/features/routingEngines";
diff --git a/frontend/app/delivery/layout.tsx b/frontend/app/delivery/layout.tsx
new file mode 100644
index 0000000..874de78
--- /dev/null
+++ b/frontend/app/delivery/layout.tsx
@@ -0,0 +1,5 @@
+"use client";
+
+import { DeliveryRootLayout } from "@/modules/delivery/components/createPortalLayout";
+
+export default DeliveryRootLayout;
diff --git a/frontend/app/delivery/loading.tsx b/frontend/app/delivery/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/geofence/error.tsx b/frontend/app/delivery/maps/geofence/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/maps/geofence/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/delivery/maps/geofence/loading.tsx b/frontend/app/delivery/maps/geofence/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/maps/geofence/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/geofence/page.tsx b/frontend/app/delivery/maps/geofence/page.tsx
new file mode 100644
index 0000000..3fb62e9
--- /dev/null
+++ b/frontend/app/delivery/maps/geofence/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { GeofencePage as default } from "@/modules/delivery/features/geofence";
diff --git a/frontend/app/delivery/maps/routes/optimization/error.tsx b/frontend/app/delivery/maps/routes/optimization/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/optimization/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/delivery/maps/routes/optimization/loading.tsx b/frontend/app/delivery/maps/routes/optimization/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/optimization/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/routes/optimization/page.tsx b/frontend/app/delivery/maps/routes/optimization/page.tsx
new file mode 100644
index 0000000..0d0f83c
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/optimization/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { RouteOptimizationPage as default } from "@/modules/delivery/features/routeOptimization";
diff --git a/frontend/app/delivery/maps/routes/planner/error.tsx b/frontend/app/delivery/maps/routes/planner/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/planner/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/delivery/maps/routes/planner/loading.tsx b/frontend/app/delivery/maps/routes/planner/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/planner/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/routes/planner/page.tsx b/frontend/app/delivery/maps/routes/planner/page.tsx
new file mode 100644
index 0000000..16fab43
--- /dev/null
+++ b/frontend/app/delivery/maps/routes/planner/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { RoutePlannerPage as default } from "@/modules/delivery/features/routePlanner";
diff --git a/frontend/app/delivery/maps/view/error.tsx b/frontend/app/delivery/maps/view/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/maps/view/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/delivery/maps/view/loading.tsx b/frontend/app/delivery/maps/view/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/maps/view/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/view/page.tsx b/frontend/app/delivery/maps/view/page.tsx
new file mode 100644
index 0000000..94bde01
--- /dev/null
+++ b/frontend/app/delivery/maps/view/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { MapViewPage as default } from "@/modules/delivery/features/mapView";
diff --git a/frontend/app/delivery/maps/zones/error.tsx b/frontend/app/delivery/maps/zones/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/maps/zones/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/delivery/maps/zones/loading.tsx b/frontend/app/delivery/maps/zones/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/maps/zones/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/maps/zones/page.tsx b/frontend/app/delivery/maps/zones/page.tsx
new file mode 100644
index 0000000..df011d9
--- /dev/null
+++ b/frontend/app/delivery/maps/zones/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DeliveryZonesPage as default } from "@/modules/delivery/features/deliveryZones";
diff --git a/frontend/app/delivery/not-found.tsx b/frontend/app/delivery/not-found.tsx
new file mode 100644
index 0000000..df2a67a
--- /dev/null
+++ b/frontend/app/delivery/not-found.tsx
@@ -0,0 +1,10 @@
+import Link from "next/link";
+import { Button, EmptyState } from "@/components/ds";
+
+export default function DeliveryNotFound() {
+ return (
+
+ } />
+
+ );
+}
diff --git a/frontend/app/delivery/notifications/error.tsx b/frontend/app/delivery/notifications/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/notifications/loading.tsx b/frontend/app/delivery/notifications/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/notifications/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/notifications/page.tsx b/frontend/app/delivery/notifications/page.tsx
new file mode 100644
index 0000000..2a95646
--- /dev/null
+++ b/frontend/app/delivery/notifications/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { NotificationsPage as default } from "@/modules/delivery/features/notifications";
diff --git a/frontend/app/delivery/operations/error.tsx b/frontend/app/delivery/operations/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/operations/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/delivery/operations/loading.tsx b/frontend/app/delivery/operations/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/operations/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/operations/page.tsx b/frontend/app/delivery/operations/page.tsx
new file mode 100644
index 0000000..485cc7e
--- /dev/null
+++ b/frontend/app/delivery/operations/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { LiveOperationsDashboard as default } from "@/modules/delivery/features/operations";
diff --git a/frontend/app/delivery/orders/error.tsx b/frontend/app/delivery/orders/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/orders/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/delivery/orders/loading.tsx b/frontend/app/delivery/orders/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/orders/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/orders/page.tsx b/frontend/app/delivery/orders/page.tsx
new file mode 100644
index 0000000..e633021
--- /dev/null
+++ b/frontend/app/delivery/orders/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { OrdersPage as default } from "@/modules/delivery/features/orders";
diff --git a/frontend/app/delivery/organizations/error.tsx b/frontend/app/delivery/organizations/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/organizations/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/delivery/organizations/loading.tsx b/frontend/app/delivery/organizations/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/organizations/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/organizations/page.tsx b/frontend/app/delivery/organizations/page.tsx
new file mode 100644
index 0000000..7f99ad6
--- /dev/null
+++ b/frontend/app/delivery/organizations/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { OrganizationsPage as default } from "@/modules/delivery/features/organizations";
diff --git a/frontend/app/delivery/page.tsx b/frontend/app/delivery/page.tsx
new file mode 100644
index 0000000..6b875d5
--- /dev/null
+++ b/frontend/app/delivery/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ExecutiveDashboard as default } from "@/modules/delivery/features/dashboard";
diff --git a/frontend/app/delivery/payments/error.tsx b/frontend/app/delivery/payments/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/payments/loading.tsx b/frontend/app/delivery/payments/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/payments/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/payments/page.tsx b/frontend/app/delivery/payments/page.tsx
new file mode 100644
index 0000000..27e36ca
--- /dev/null
+++ b/frontend/app/delivery/payments/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PaymentsPage as default } from "@/modules/delivery/features/payments";
diff --git a/frontend/app/delivery/permissions/error.tsx b/frontend/app/delivery/permissions/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/permissions/loading.tsx b/frontend/app/delivery/permissions/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/permissions/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/permissions/page.tsx b/frontend/app/delivery/permissions/page.tsx
new file mode 100644
index 0000000..e7d57cc
--- /dev/null
+++ b/frontend/app/delivery/permissions/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PermissionsPage as default } from "@/modules/delivery/features/permissions";
diff --git a/frontend/app/delivery/pickups/error.tsx b/frontend/app/delivery/pickups/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/pickups/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/delivery/pickups/loading.tsx b/frontend/app/delivery/pickups/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/pickups/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/pickups/page.tsx b/frontend/app/delivery/pickups/page.tsx
new file mode 100644
index 0000000..9e80379
--- /dev/null
+++ b/frontend/app/delivery/pickups/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { PickupsPage as default } from "@/modules/delivery/features/pickups";
diff --git a/frontend/app/delivery/reports/error.tsx b/frontend/app/delivery/reports/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/reports/loading.tsx b/frontend/app/delivery/reports/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/reports/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/reports/page.tsx b/frontend/app/delivery/reports/page.tsx
new file mode 100644
index 0000000..8556817
--- /dev/null
+++ b/frontend/app/delivery/reports/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ReportsPage as default } from "@/modules/delivery/features/reports";
diff --git a/frontend/app/delivery/returns/error.tsx b/frontend/app/delivery/returns/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/returns/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/delivery/returns/loading.tsx b/frontend/app/delivery/returns/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/returns/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/returns/page.tsx b/frontend/app/delivery/returns/page.tsx
new file mode 100644
index 0000000..d510939
--- /dev/null
+++ b/frontend/app/delivery/returns/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ReturnsPage as default } from "@/modules/delivery/features/returns";
diff --git a/frontend/app/delivery/roles/error.tsx b/frontend/app/delivery/roles/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/roles/loading.tsx b/frontend/app/delivery/roles/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/roles/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/roles/page.tsx b/frontend/app/delivery/roles/page.tsx
new file mode 100644
index 0000000..792c4a9
--- /dev/null
+++ b/frontend/app/delivery/roles/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { RolesPage as default } from "@/modules/delivery/features/roles";
diff --git a/frontend/app/delivery/schedules/error.tsx b/frontend/app/delivery/schedules/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/schedules/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/delivery/schedules/loading.tsx b/frontend/app/delivery/schedules/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/schedules/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/schedules/page.tsx b/frontend/app/delivery/schedules/page.tsx
new file mode 100644
index 0000000..61fb779
--- /dev/null
+++ b/frontend/app/delivery/schedules/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SchedulesPage as default } from "@/modules/delivery/features/schedules";
diff --git a/frontend/app/delivery/settings/error.tsx b/frontend/app/delivery/settings/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/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/delivery/settings/loading.tsx b/frontend/app/delivery/settings/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/settings/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/settings/page.tsx b/frontend/app/delivery/settings/page.tsx
new file mode 100644
index 0000000..4d72e29
--- /dev/null
+++ b/frontend/app/delivery/settings/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SettingsPage as default } from "@/modules/delivery/features/settings";
diff --git a/frontend/app/delivery/settlements/error.tsx b/frontend/app/delivery/settlements/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/settlements/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/delivery/settlements/loading.tsx b/frontend/app/delivery/settlements/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/settlements/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/settlements/page.tsx b/frontend/app/delivery/settlements/page.tsx
new file mode 100644
index 0000000..98f7ab2
--- /dev/null
+++ b/frontend/app/delivery/settlements/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SettlementsPage as default } from "@/modules/delivery/features/settlements";
diff --git a/frontend/app/delivery/shifts/error.tsx b/frontend/app/delivery/shifts/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/shifts/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/delivery/shifts/loading.tsx b/frontend/app/delivery/shifts/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/shifts/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/shifts/page.tsx b/frontend/app/delivery/shifts/page.tsx
new file mode 100644
index 0000000..736dfee
--- /dev/null
+++ b/frontend/app/delivery/shifts/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { ShiftsPage as default } from "@/modules/delivery/features/shifts";
diff --git a/frontend/app/delivery/support/error.tsx b/frontend/app/delivery/support/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/support/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/delivery/support/loading.tsx b/frontend/app/delivery/support/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/support/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/support/page.tsx b/frontend/app/delivery/support/page.tsx
new file mode 100644
index 0000000..c2b6f84
--- /dev/null
+++ b/frontend/app/delivery/support/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { SupportTicketsPage as default } from "@/modules/delivery/features/support";
diff --git a/frontend/app/delivery/tasks/error.tsx b/frontend/app/delivery/tasks/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/tasks/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/delivery/tasks/loading.tsx b/frontend/app/delivery/tasks/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/tasks/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/tasks/page.tsx b/frontend/app/delivery/tasks/page.tsx
new file mode 100644
index 0000000..c6fca83
--- /dev/null
+++ b/frontend/app/delivery/tasks/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { TasksPage as default } from "@/modules/delivery/features/tasks";
diff --git a/frontend/app/delivery/tracking/customer/error.tsx b/frontend/app/delivery/tracking/customer/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/tracking/customer/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/delivery/tracking/customer/loading.tsx b/frontend/app/delivery/tracking/customer/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/tracking/customer/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/tracking/customer/page.tsx b/frontend/app/delivery/tracking/customer/page.tsx
new file mode 100644
index 0000000..cf62ba3
--- /dev/null
+++ b/frontend/app/delivery/tracking/customer/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { CustomerTrackingPage as default } from "@/modules/delivery/features/customerTracking";
diff --git a/frontend/app/delivery/tracking/live/error.tsx b/frontend/app/delivery/tracking/live/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/tracking/live/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/delivery/tracking/live/loading.tsx b/frontend/app/delivery/tracking/live/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/tracking/live/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/tracking/live/page.tsx b/frontend/app/delivery/tracking/live/page.tsx
new file mode 100644
index 0000000..b871941
--- /dev/null
+++ b/frontend/app/delivery/tracking/live/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { LiveTrackingPage as default } from "@/modules/delivery/features/liveTracking";
diff --git a/frontend/app/delivery/vehicle-types/error.tsx b/frontend/app/delivery/vehicle-types/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/vehicle-types/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/delivery/vehicle-types/loading.tsx b/frontend/app/delivery/vehicle-types/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/vehicle-types/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/vehicle-types/page.tsx b/frontend/app/delivery/vehicle-types/page.tsx
new file mode 100644
index 0000000..6fc79c6
--- /dev/null
+++ b/frontend/app/delivery/vehicle-types/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { VehicleTypesPage as default } from "@/modules/delivery/features/vehicleTypes";
diff --git a/frontend/app/delivery/vehicles/error.tsx b/frontend/app/delivery/vehicles/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/vehicles/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/delivery/vehicles/loading.tsx b/frontend/app/delivery/vehicles/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/vehicles/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/vehicles/page.tsx b/frontend/app/delivery/vehicles/page.tsx
new file mode 100644
index 0000000..817c3b5
--- /dev/null
+++ b/frontend/app/delivery/vehicles/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { VehiclesPage as default } from "@/modules/delivery/features/vehicles";
diff --git a/frontend/app/delivery/vendor-portal/error.tsx b/frontend/app/delivery/vendor-portal/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/vendor-portal/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/delivery/vendor-portal/loading.tsx b/frontend/app/delivery/vendor-portal/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/vendor-portal/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/vendor-portal/page.tsx b/frontend/app/delivery/vendor-portal/page.tsx
new file mode 100644
index 0000000..6a35e40
--- /dev/null
+++ b/frontend/app/delivery/vendor-portal/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { VendorPortalPage as default } from "@/modules/delivery/features/vendorPortal";
diff --git a/frontend/app/delivery/wallet/error.tsx b/frontend/app/delivery/wallet/error.tsx
new file mode 100644
index 0000000..b8dfa5e
--- /dev/null
+++ b/frontend/app/delivery/wallet/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/delivery/wallet/loading.tsx b/frontend/app/delivery/wallet/loading.tsx
new file mode 100644
index 0000000..61eb1c6
--- /dev/null
+++ b/frontend/app/delivery/wallet/loading.tsx
@@ -0,0 +1,5 @@
+import { LoadingState } from "@/components/ds";
+
+export default function Loading() {
+ return ;
+}
diff --git a/frontend/app/delivery/wallet/page.tsx b/frontend/app/delivery/wallet/page.tsx
new file mode 100644
index 0000000..76b7e4c
--- /dev/null
+++ b/frontend/app/delivery/wallet/page.tsx
@@ -0,0 +1,3 @@
+"use client";
+
+export { DriverWalletPage as default } from "@/modules/delivery/features/wallet";
diff --git a/frontend/docs/delivery-component-map.md b/frontend/docs/delivery-component-map.md
new file mode 100644
index 0000000..4710a92
--- /dev/null
+++ b/frontend/docs/delivery-component-map.md
@@ -0,0 +1,61 @@
+# Delivery Component Map
+
+## Shared (reused, not duplicated)
+
+| Component | Source | Usage in Delivery |
+|-----------|--------|-------------------|
+| Button, Input, Dialog, Drawer | `@/components/ds` | All forms and actions |
+| PageHeader, StatCard, DataTable | `@/components/ds` | List pages, dashboards |
+| LoadingState, ErrorState, EmptyState | `@/components/ds` | All page states |
+| PermissionDeniedState | `@/src/shared/ui` | 403 / missing permission |
+| AuthGuard | `@/components/AuthGuard` | Portal gate |
+| Tabs, Pagination, Breadcrumbs | `@/components/ds` | Tables, driver detail |
+
+## Delivery module components
+
+| Component | Path | Purpose |
+|-----------|------|---------|
+| DeliveryPortalShell | `components/DeliveryPortalShell.tsx` | Sidebar nav, mobile drawer, dark mode |
+| createPortalLayout | `components/createPortalLayout.tsx` | Auth + tenant gate |
+| DeliveryListCrudPage | `components/DeliveryListCrudPage.tsx` | CRUD factory for foundation entities |
+| DeliveryPhaseGate | `components/DeliveryPhaseGate.tsx` | Capability-gated future screens |
+| DeliveryMapPlaceholder | `components/DeliveryMapPlaceholder.tsx` | Map-first skeleton when routing enabled |
+| DeliveryBreadcrumbs | `components/DeliveryBreadcrumbs.tsx` | RTL breadcrumbs |
+
+## Design system
+
+| Component | Path |
+|-----------|------|
+| deliveryTokens | `design-system/tokens.ts` |
+| DeliveryStatusChip | `design-system/DeliveryStatusChip.tsx` |
+| DeliveryTablePage | `design-system/DeliveryTablePage.tsx` |
+| DeliveryStatGrid | `design-system/DeliveryStatGrid.tsx` |
+
+## Feature pages (by domain)
+
+| Domain | Feature files |
+|--------|---------------|
+| Dashboards | `dashboard.tsx`, `dispatcher.tsx`, `operations.tsx`, `fleetDashboard.tsx` |
+| Drivers | `drivers.tsx`, `driverDetail.tsx`, `driverAvailability.tsx`, `driverPerformance.tsx` |
+| Foundation | `organizations.tsx`, `branches.tsx`, `configurations.tsx`, `settings.tsx` |
+| Integrations | `externalProviders.tsx`, `routingEngines.tsx` |
+| Maps | `mapView.tsx`, `routePlanner.tsx`, `routeOptimization.tsx`, `geofence.tsx`, `deliveryZones.tsx` |
+| Operations (phase) | `orders.tsx`, `dispatchQueue.tsx`, `assignment.tsx`, `pickups.tsx`, … |
+| Finance (phase) | `cod.tsx`, `settlements.tsx`, `payments.tsx`, `wallet.tsx`, `commissions.tsx` |
+| Admin | `permissions.tsx`, `audit.tsx`, `health.tsx`, `capabilities.tsx` |
+
+## Hooks
+
+| Hook | File |
+|------|------|
+| useDeliveryCapabilities | `hooks/useDeliveryCapabilities.ts` |
+| useDeliveryPermissions | `hooks/useDeliveryPermissions.ts` |
+| useDeliveryLookups | `hooks/useDeliveryCapabilities.ts` (organizations + hubs) |
+| useDeliveryMetrics | `hooks/useDeliveryCapabilities.ts` |
+
+## API
+
+| Client | Path |
+|--------|------|
+| deliveryApi | `services/delivery-api.ts` |
+| BFF proxy | `app/api/delivery/[...path]/route.ts` |
diff --git a/frontend/docs/delivery-design-system.md b/frontend/docs/delivery-design-system.md
new file mode 100644
index 0000000..686a9e3
--- /dev/null
+++ b/frontend/docs/delivery-design-system.md
@@ -0,0 +1,59 @@
+# Delivery Design System
+
+Delivery uses the **shared platform design system** (`@/components/ds`) with module-specific tokens only — no duplicated Button/Input/Dialog implementations.
+
+## CSS tokens
+
+Defined in `frontend/styles/globals.css`:
+
+```css
+--delivery-accent: #0891b2; /* light — logistics cyan */
+--delivery-accent-soft: rgba(8, 145, 178, 0.12);
+--delivery-map: #0e7490;
+--delivery-success / warning / danger
+```
+
+Dark mode: `--delivery-accent: #22d3ee`
+
+**Rule:** Reference `var(--delivery-accent)` in module components; never hardcode hex.
+
+## Module design-system (`modules/delivery/design-system/`)
+
+| Export | Purpose |
+|--------|---------|
+| `deliveryTokens` | Accent variable references |
+| `DeliveryStatusChip` | Driver + lifecycle status badges |
+| `DeliveryTablePage` | Search, sort, pagination wrapper |
+| `DeliveryStatGrid` | Dashboard stat cards row |
+| `lifecycleStatusLabels` | Persian labels for statuses |
+| `driverStatusLabels` | Driver-specific statuses |
+
+## Layout patterns
+
+- **Portal shell:** Fixed RTL sidebar (desktop), drawer (mobile), sticky mobile header
+- **Page header:** Title + description + action slot
+- **CRUD list:** Table + create dialog + edit drawer + delete confirm
+- **Phase gate:** Centered card with phase badge — used instead of placeholder/mock data
+- **Map placeholder:** 16:9 aspect map canvas with marker/route badges
+
+## Typography & spacing
+
+Inherited from platform DS and Tailwind utilities. Consistent `space-y-6` page rhythm, `rounded-2xl` cards, `p-4 lg:p-6` main padding.
+
+## Icons
+
+Lucide React — consistent with other TorbatYar modules.
+
+## Accessibility
+
+- `aria-label` on mobile menu and close buttons
+- Keyboard: Escape closes dialogs/drawers (DS default)
+- RTL-first breadcrumb and table alignment
+
+## Dark mode
+
+Supported via `ColorModeProvider` toggle in portal shell; all surfaces use CSS variables.
+
+## Future: shared/maps
+
+When `frontend/shared/maps/` is populated, Delivery map pages should import shared map components rather than extending `DeliveryMapPlaceholder`.
diff --git a/frontend/docs/delivery-frontend-architecture.md b/frontend/docs/delivery-frontend-architecture.md
new file mode 100644
index 0000000..04239ac
--- /dev/null
+++ b/frontend/docs/delivery-frontend-architecture.md
@@ -0,0 +1,73 @@
+# Delivery Frontend Architecture
+
+**Module:** Torbat Driver (Delivery Platform)
+**Date:** 2026-07-26
+**Backend phase:** 10.1 (Driver Management)
+
+## Overview
+
+The Delivery frontend is the enterprise reference implementation for logistics and courier operations inside TorbatYar SuperApp. Business code lives under `frontend/modules/delivery/`; App Router routes under `app/delivery/` are thin re-exports only.
+
+## Layer diagram
+
+```
+app/delivery/ → Routes (page, layout, loading, error, not-found)
+ ↓
+modules/delivery/ → Business UI, hooks, API client, constants
+ ↓
+components/ds/ → Shared design system primitives
+ ↓
+/api/delivery/* BFF → delivery-service :8007
+```
+
+## Directory structure
+
+```
+modules/delivery/
+├── components/ Portal shell, CRUD factory, phase gates, map placeholder
+├── constants/ portals.ts, permissions.ts, phaseRegistry.ts
+├── design-system/ tokens, status chips, table page
+├── features/ One file per screen (56+ routes)
+├── hooks/ capabilities, permissions, lookups
+├── pages/shared.tsx PageLoader, PageError, CSV export
+├── services/delivery-api.ts
+└── types/index.ts
+```
+
+## Backend integration (Phase 10.0–10.1)
+
+| Domain | API prefix | CRUD status |
+|--------|------------|-------------|
+| Organizations | `/api/v1/organizations` | ✅ Full |
+| Hubs (Branches) | `/api/v1/hubs` | ✅ Full |
+| External providers | `/api/v1/external-providers` | ✅ Full |
+| Routing engines | `/api/v1/routing-engines` | ✅ Full |
+| Configurations | `/api/v1/configurations` | ✅ Full |
+| Settings | `/api/v1/settings` | ✅ Upsert + list |
+| Audit | `/api/v1/audit` | ✅ Query by entity |
+| Drivers | `/api/v1/drivers` | ✅ Full + lifecycle |
+| Permissions catalog | `/api/v1/permissions/catalog` | ✅ Read |
+| Health / Capabilities / Metrics | `/health`, `/capabilities`, `/metrics` | ✅ Read |
+
+## Future phases (capability-gated UI)
+
+Screens for fleet, dispatch, orders, tracking, maps (live), settlement, AI, etc. render **phase gate pages** that read `/capabilities` — no mock data, no fake CRUD. When backend enables a feature flag, the UI unlocks automatically.
+
+## Real-time
+
+WebSocket/SSE for driver locations, dispatch queue, and tracking is planned for Phase 10.7. The map placeholder documents the integration point.
+
+## Commands
+
+```bash
+node scripts/scaffold-delivery-module.mjs # Regenerate routes + feature stubs
+npm run validate:delivery # Route + architecture validation
+```
+
+## Related docs
+
+- [delivery-component-map.md](./delivery-component-map.md)
+- [delivery-routing-map.md](./delivery-routing-map.md)
+- [delivery-design-system.md](./delivery-design-system.md)
+- [delivery-validation-report.md](./delivery-validation-report.md)
+- [delivery-ui-checklist.md](./delivery-ui-checklist.md)
diff --git a/frontend/docs/delivery-routing-map.md b/frontend/docs/delivery-routing-map.md
new file mode 100644
index 0000000..e7cc79b
--- /dev/null
+++ b/frontend/docs/delivery-routing-map.md
@@ -0,0 +1,119 @@
+# Delivery Routing Map
+
+All routes under `/delivery`. App files are thin re-exports; implementation in `modules/delivery/features/`.
+
+## Portal
+
+| Route | Feature | Backend |
+|-------|---------|---------|
+| `/delivery/hub` | hub | — |
+| `/delivery` | dashboard | Real counts (orgs, hubs, drivers) |
+
+## Dashboards
+
+| Route | Feature | Status |
+|-------|---------|--------|
+| `/delivery/dispatcher` | dispatcher | Phase gate (10.5) |
+| `/delivery/operations` | operations | Phase gate (10.7) |
+| `/delivery/fleet-dashboard` | fleetDashboard | Phase gate (10.2) |
+
+## Drivers
+
+| Route | Feature | Status |
+|-------|---------|--------|
+| `/delivery/drivers` | drivers | ✅ Real API |
+| `/delivery/drivers/[id]` | driverDetail | ✅ Real API + lifecycle |
+| `/delivery/drivers/availability` | driverAvailability | Phase gate (10.3) |
+| `/delivery/drivers/performance` | driverPerformance | Phase gate (10.10) |
+
+## Orders & dispatch
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/orders` | orders | 10.5 |
+| `/delivery/dispatch/queue` | dispatchQueue | 10.5 |
+| `/delivery/dispatch/assignment` | assignment | 10.5 |
+| `/delivery/pickups` | pickups | 10.5 |
+| `/delivery/deliveries` | deliveries | 10.5 |
+| `/delivery/returns` | returns | 10.5 |
+| `/delivery/failed` | failed | 10.5 |
+
+## Tracking
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/tracking/live` | liveTracking | 10.7 |
+| `/delivery/tracking/customer` | customerTracking | 10.7 |
+
+## Maps
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/maps/view` | mapView | 10.6 (map skeleton when enabled) |
+| `/delivery/maps/routes/planner` | routePlanner | 10.6 |
+| `/delivery/maps/routes/optimization` | routeOptimization | 10.6 |
+| `/delivery/maps/geofence` | geofence | 10.6 |
+| `/delivery/maps/zones` | deliveryZones | 10.3 |
+
+## Fleet
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/vehicles` | vehicles | 10.2 |
+| `/delivery/vehicle-types` | vehicleTypes | 10.2 |
+| `/delivery/fleet/maintenance` | maintenance | 10.2 |
+| `/delivery/schedules` | schedules | 10.3 |
+| `/delivery/shifts` | shifts | 10.3 |
+| `/delivery/tasks` | tasks | 10.5 |
+
+## Foundation (live)
+
+| Route | Feature | API |
+|-------|---------|-----|
+| `/delivery/organizations` | organizations | organizations |
+| `/delivery/branches` | branches | hubs |
+| `/delivery/configurations` | configurations | configurations |
+| `/delivery/settings` | settings | settings |
+| `/delivery/integrations/providers` | externalProviders | external-providers |
+| `/delivery/integrations/routing` | routingEngines | routing-engines |
+
+## Finance (phase)
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/cod` | cod | 10.8 |
+| `/delivery/settlements` | settlements | 10.8 |
+| `/delivery/payments` | payments | 10.8 |
+| `/delivery/wallet` | wallet | 10.8 |
+| `/delivery/commissions` | commissions | 10.8 |
+
+## Insights & AI (phase)
+
+| Route | Feature | Phase |
+|-------|---------|-------|
+| `/delivery/reports` | reports | 10.10 |
+| `/delivery/analytics` | analytics | 10.10 |
+| `/delivery/analytics/heatmaps` | heatmaps | 10.10 |
+| `/delivery/analytics/kpis` | kpis | 10.10 |
+| `/delivery/ai/recommendations` | aiRecommendations | 10.10 |
+| `/delivery/automation` | automation | 10.10 |
+
+## Admin
+
+| Route | Feature | Status |
+|-------|---------|--------|
+| `/delivery/permissions` | permissions | ✅ Catalog API |
+| `/delivery/roles` | roles | Phase gate |
+| `/delivery/audit` | audit | ✅ Audit API |
+| `/delivery/api-keys` | apiKeys | Phase gate |
+| `/delivery/health` | health | ✅ Health API |
+| `/delivery/capabilities` | capabilities | ✅ Capabilities API |
+| `/delivery/notifications` | notifications | Phase 10.9 |
+| `/delivery/incidents` | incidents | Phase 10.5 |
+| `/delivery/support` | support | Phase 10.9 |
+| `/delivery/vendor-portal` | vendorPortal | Phase 10.9 |
+
+## Segment files (every route)
+
+Each route directory includes: `page.tsx`, `loading.tsx`, `error.tsx`
+Root: `layout.tsx`, `not-found.tsx`
diff --git a/frontend/docs/delivery-ui-checklist.md b/frontend/docs/delivery-ui-checklist.md
new file mode 100644
index 0000000..8b8ebda
--- /dev/null
+++ b/frontend/docs/delivery-ui-checklist.md
@@ -0,0 +1,76 @@
+# Delivery UI Checklist
+
+Per-screen requirements for Torbat Driver frontend.
+
+## Global (every screen)
+
+- [x] Loading state (`loading.tsx` + in-page loader)
+- [x] Error state (`error.tsx` + `DeliveryPageError`)
+- [x] Empty state (`DeliveryEmptyState` / DS `EmptyState`)
+- [x] Permission state (`PermissionDeniedState`)
+- [x] Responsive layout (mobile drawer + desktop sidebar)
+- [x] Breadcrumb (where applicable)
+- [x] RTL layout
+- [x] Dark mode tokens
+
+## Live CRUD screens
+
+### Organizations `/delivery/organizations`
+- [x] List, create, edit, delete
+- [x] Search, pagination, CSV export
+- [x] Status badges
+- [x] Permission check
+
+### Branches `/delivery/branches`
+- [x] Organization selector on create
+- [x] Full CRUD
+- [x] API: hubs
+
+### Drivers `/delivery/drivers`
+- [x] Paginated list
+- [x] Link to detail
+- [x] Create with org/hub
+- [x] Edit, delete
+
+### Driver detail `/delivery/drivers/[id]`
+- [x] Profile tab
+- [x] Lifecycle tab + actions (activate, suspend, …)
+- [x] Credentials tab (read)
+- [x] Documents tab (read)
+- [x] Timeline from lifecycle API
+
+### Integrations
+- [x] External providers CRUD
+- [x] Routing engines CRUD
+
+### Admin
+- [x] Settings upsert
+- [x] Audit search
+- [x] Permissions catalog
+- [x] Health status
+- [x] Capabilities grid
+
+## Phase-gated screens (checklist when backend enables)
+
+When `features.` becomes `true` in `/capabilities`:
+
+- [ ] Replace phase gate with real implementation
+- [ ] Connect WebSocket for tracking/dispatch
+- [ ] Integrate shared map component
+- [ ] Add realtime polling fallback
+
+## Dashboard checklist
+
+- [x] Executive: real org/hub/driver counts
+- [ ] Dispatcher: dispatch queue metrics (10.5)
+- [ ] Live ops: tracking sessions (10.7)
+- [ ] Fleet: vehicle counts (10.2)
+
+## Validation loop
+
+```bash
+npm run validate:delivery
+npm run build
+```
+
+Review [delivery-validation-report.md](./delivery-validation-report.md) after each backend phase upgrade.
diff --git a/frontend/docs/delivery-validation-report.md b/frontend/docs/delivery-validation-report.md
new file mode 100644
index 0000000..a84e3c7
--- /dev/null
+++ b/frontend/docs/delivery-validation-report.md
@@ -0,0 +1,76 @@
+# Delivery Frontend Validation Report
+
+**Date:** 2026-07-26
+**Module version:** Phase 10.1 UI
+**Backend:** delivery-service v0.10.1.0 @ :8007
+
+## Automated checks
+
+| Check | Command | Result |
+|-------|---------|--------|
+| Route thinness | `npm run validate:delivery-routes` | ✅ PASS (56 routes) |
+| Architecture boundaries | `npm run validate:architecture` | ✅ PASS (delivery marker registered) |
+| TypeScript compile | `next build` (compile phase) | ✅ Compiled successfully |
+| ESLint (delivery) | No delivery-specific errors | ✅ PASS |
+
+## API connectivity
+
+| Screen | Mock/placeholder? | Backend endpoint |
+|--------|-------------------|------------------|
+| Executive dashboard | No | organizations, hubs, drivers list |
+| Organizations | No | CRUD `/api/v1/organizations` |
+| Branches (hubs) | No | CRUD `/api/v1/hubs` |
+| Drivers list | No | CRUD `/api/v1/drivers` |
+| Driver detail | No | get, lifecycle, credentials, documents |
+| External providers | No | CRUD |
+| Routing engines | No | CRUD |
+| Configurations | No | CRUD |
+| Settings | No | GET + PUT |
+| Audit | No | GET with entity_type/id |
+| Permissions | No | catalog |
+| Health / Capabilities | No | public endpoints |
+| Dispatch, orders, fleet, maps (live), settlement, AI | **Phase gate** (not mock) | Awaits backend phase |
+
+## Intentional phase-gated screens
+
+34 screens map to `PHASE_REGISTRY` entries. They display:
+- Current roadmap phase number
+- Feature flag name from `/capabilities`
+- Links to capabilities page and dashboard
+
+This is **production-honest** behavior — no fake tables or dummy CRUD for APIs that do not exist.
+
+## BFF
+
+| Path | Upstream |
+|------|----------|
+| `/api/delivery/*` | `DELIVERY_SERVICE_URL` → `http://localhost:8007` |
+
+Headers forwarded: `Authorization`, `X-Tenant-ID`, `Content-Type`, `Accept`
+
+## Known limitations
+
+1. **Realtime:** No WebSocket client until Phase 10.7 backend support
+2. **Map tiles:** Placeholder canvas until shared map provider integrated
+3. **Roles / API keys:** Identity platform concern; phase-gated until dedicated APIs
+4. **Build:** Full `next build` may require clean `.next` on Windows if manifest race occurs (compile succeeds)
+
+## Regression commands
+
+```bash
+cd frontend
+npm run validate:delivery
+npm run build
+node scripts/scaffold-delivery-module.mjs # idempotent regen
+```
+
+## Sign-off criteria met
+
+- [x] Module under `frontend/modules/delivery/`
+- [x] Thin routes under `app/delivery/`
+- [x] BFF proxy configured
+- [x] All Phase 10.0–10.1 APIs wired
+- [x] Future screens capability-gated (no mocks)
+- [x] Loading / error / empty / permission states
+- [x] ESLint + architecture validation
+- [x] Documentation generated
diff --git a/frontend/modules/delivery/README.md b/frontend/modules/delivery/README.md
new file mode 100644
index 0000000..cd5d4fa
--- /dev/null
+++ b/frontend/modules/delivery/README.md
@@ -0,0 +1,16 @@
+# Torbat Driver — Delivery Module
+
+Business UI for `/delivery/*`. Routes are thin re-exports in `app/delivery/`.
+
+## Structure
+
+- `features/` — page implementations
+- `components/` — portal shell, CRUD factory, phase gates
+- `services/delivery-api.ts` — real backend client (BFF `/api/delivery`)
+- `constants/` — nav, permissions, phase registry
+
+## Scaffold
+
+```bash
+node scripts/scaffold-delivery-module.mjs
+```
diff --git a/frontend/modules/delivery/components/DeliveryBreadcrumbs.tsx b/frontend/modules/delivery/components/DeliveryBreadcrumbs.tsx
new file mode 100644
index 0000000..0e44ba5
--- /dev/null
+++ b/frontend/modules/delivery/components/DeliveryBreadcrumbs.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { Breadcrumbs } from "@/components/ds";
+
+export function DeliveryBreadcrumbs({ current }: { current: string }) {
+ return (
+
+ );
+}
+
+export function DeliveryDetailBreadcrumbs({
+ section,
+ current,
+ sectionHref,
+}: {
+ section: string;
+ current: string;
+ sectionHref: string;
+}) {
+ return (
+
+ );
+}
diff --git a/frontend/modules/delivery/components/DeliveryListCrudPage.tsx b/frontend/modules/delivery/components/DeliveryListCrudPage.tsx
new file mode 100644
index 0000000..344e49e
--- /dev/null
+++ b/frontend/modules/delivery/components/DeliveryListCrudPage.tsx
@@ -0,0 +1,429 @@
+"use client";
+
+import { useCallback, 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, RefreshCw, Eye } from "lucide-react";
+import Link from "next/link";
+import { z } from "zod";
+import {
+ Button,
+ Dialog,
+ Drawer,
+ Input,
+ ConfirmDialog,
+ Select,
+ Textarea,
+ FormField,
+} from "@/components/ds";
+import { DeliveryTablePage, DeliveryStatGrid } from "@/modules/delivery/design-system";
+import {
+ DeliveryPageLoader,
+ DeliveryPageError,
+ exportToCsv,
+} from "@/modules/delivery/pages/shared";
+import { useTenantId } from "@/hooks/useTenantId";
+import { useDeliveryLookups } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { useDeliveryPermissions } from "@/modules/delivery/hooks/useDeliveryPermissions";
+import { DeliveryStatusChip } from "@/modules/delivery/design-system";
+import { PermissionDeniedState } from "@/src/shared/ui";
+
+export type DeliveryFieldConfig = {
+ name: string;
+ label: string;
+ type?: "text" | "number" | "email" | "tel" | "textarea" | "select" | "organization" | "hub";
+ options?: { value: string; label: string }[];
+ required?: boolean;
+ createOnly?: boolean;
+ editOnly?: boolean;
+ placeholder?: string;
+};
+
+export type DeliveryColumnConfig = {
+ key: string;
+ header: string;
+ type?: "status" | "datetime" | "text" | "link";
+ linkPrefix?: string;
+ render?: (row: Record) => React.ReactNode;
+};
+
+type ApiResource = {
+ list: (
+ tenantId: string,
+ params?: Record
+ ) => Promise[] | { items: Record[]; total: number }>;
+ get?: (tenantId: string, id: string) => Promise>;
+ create?: (tenantId: string, body: Record) => Promise;
+ update?: (tenantId: string, id: string, body: Record) => Promise;
+ remove?: (tenantId: string, id: string) => Promise;
+};
+
+export type DeliveryListConfig = {
+ title: string;
+ description?: string;
+ breadcrumb?: string;
+ resourceKey: string;
+ permission?: string;
+ api: ApiResource;
+ columns: DeliveryColumnConfig[];
+ createFields?: DeliveryFieldConfig[];
+ editFields?: DeliveryFieldConfig[];
+ readOnly?: boolean;
+ detailHref?: (row: Record) => string | null;
+ rowActions?: {
+ label: string;
+ onClick: (tenantId: string, row: Record) => Promise;
+ variant?: "default" | "outline" | "danger";
+ }[];
+};
+
+function buildSchema(fields: DeliveryFieldConfig[]) {
+ 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 normalizeList(
+ data: Record[] | { items: Record[]; total: number }
+): Record[] {
+ if (Array.isArray(data)) return data;
+ return data.items ?? [];
+}
+
+function renderCell(col: DeliveryColumnConfig, 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);
+ }
+ }
+ if (col.type === "link" && col.linkPrefix && row.id) {
+ return (
+
+ {val != null ? String(val) : "—"}
+
+ );
+ }
+ return val != null ? String(val) : "—";
+}
+
+function renderField(
+ field: DeliveryFieldConfig,
+ register: ReturnType["register"],
+ mode: "create" | "edit",
+ lookups: { organizations: Record[]; hubs: Record[] }
+) {
+ if (mode === "create" && field.editOnly) return null;
+ if (mode === "edit" && field.createOnly) return null;
+
+ if (field.type === "textarea") {
+ return (
+
+
+
+ );
+ }
+ if (field.type === "select" && field.options) {
+ return (
+
+
+
+ );
+ }
+ if (field.type === "organization") {
+ return (
+
+
+
+ );
+ }
+ if (field.type === "hub") {
+ return (
+
+
+
+ );
+ }
+ return (
+
+
+
+ );
+}
+
+export function createDeliveryListPage(config: DeliveryListConfig) {
+ return function DeliveryListPage() {
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const perms = useDeliveryPermissions();
+ const lookupsQ = useDeliveryLookups(tenantId);
+ 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.filter((f) => !f.createOnly),
+ { name: "version", label: "نسخه", required: true, editOnly: true },
+ ];
+
+ const createSchema = buildSchema(createFields);
+ const editSchema = buildSchema(editFields);
+
+ const listQ = useQuery({
+ queryKey: ["delivery", tenantId, config.resourceKey, page],
+ queryFn: async () => normalizeList(await 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 = useCallback(
+ () => qc.invalidateQueries({ queryKey: ["delivery", tenantId, config.resourceKey] }),
+ [qc, tenantId, config.resourceKey]
+ );
+
+ const createM = useMutation({
+ mutationFn: (d: FieldValues) => config.api.create!(tenantId!, d as Record),
+ onSuccess: () => {
+ toast.success("با موفقیت ایجاد شد");
+ setCreateOpen(false);
+ createForm.reset();
+ invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ const updateM = useMutation({
+ mutationFn: (d: FieldValues) =>
+ config.api.update!(tenantId!, String(editRow!.id), d as Record),
+ onSuccess: () => {
+ toast.success("با موفقیت بهروزرسانی شد");
+ setEditRow(null);
+ invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ const deleteM = useMutation({
+ mutationFn: (id: string) => config.api.remove!(tenantId!, id),
+ onSuccess: () => {
+ toast.success("حذف شد");
+ setDeleteId(null);
+ invalidate();
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || listQ.isLoading || lookupsQ.isLoading) return ;
+ if (listQ.error) return listQ.refetch()} />;
+ if (config.permission && !perms.can(config.permission)) {
+ return ;
+ }
+
+ const rows = listQ.data ?? [];
+ const lookups = lookupsQ.data ?? { organizations: [], hubs: [] };
+ const canCreate = !config.readOnly && config.api.create && (!config.permission || perms.can(config.permission.replace(".view", ".create")));
+ const canEdit = !config.readOnly && config.api.update;
+ const canDelete = !config.readOnly && config.api.remove;
+
+ const tableColumns = config.columns.map((c) => ({
+ ...c,
+ render: (row: Record) => renderCell(c, row),
+ }));
+
+ return (
+ <>
+
+
+
+ {canCreate ? (
+
+ ) : null}
+
+ }
+ />
+
+ r.status === "active").length),
+ },
+ ]}
+ />
+
+
+
+ setEditRow(null)}
+ title={`ویرایش ${config.title}`}
+ >
+ {editRow ? (
+
+ ) : null}
+
+
+ setDeleteId(null)}
+ title="حذف"
+ description="آیا از حذف این مورد اطمینان دارید؟"
+ confirmLabel="حذف"
+ danger
+ onConfirm={() => deleteId && deleteM.mutate(deleteId)}
+ />
+
+ {/* Row action buttons rendered via table extension - inline actions in footer */}
+ {rows.length > 0 && (canEdit || canDelete || config.rowActions) ? (
+
+
+
+
+ | عملیات سریع |
+
+
+
+ {rows.slice(0, 5).map((row) => (
+
+ |
+ {config.detailHref?.(row) ? (
+
+
+
+ ) : null}
+ {canEdit ? (
+
+ ) : null}
+ {canDelete ? (
+
+ ) : null}
+ {config.rowActions?.map((a) => (
+
+ ))}
+ |
+
+ ))}
+
+
+
+ ) : null}
+ >
+ );
+ };
+}
diff --git a/frontend/modules/delivery/components/DeliveryMapPlaceholder.tsx b/frontend/modules/delivery/components/DeliveryMapPlaceholder.tsx
new file mode 100644
index 0000000..3d50b0b
--- /dev/null
+++ b/frontend/modules/delivery/components/DeliveryMapPlaceholder.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import { MapPin, Navigation, Layers } from "lucide-react";
+import { Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { useDeliveryCapabilities } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader } from "@/modules/delivery/pages/shared";
+import type { PhaseGateConfig } from "@/modules/delivery/types";
+
+function MapSkeleton({ title }: { title: string }) {
+ return (
+
+
+
+
+
+
رابط نقشه عملیاتی
+
+ مارکر رانندگان، pickup، delivery، خوشهبندی، ترافیک و پیشنمایش مسیر.
+
+
+
+
+ Live Markers
+
+
+
+ Route Preview
+
+
+
+
+
+
+
+ اتصال WebSocket/SSE برای بهروزرسانی لحظهای موقعیت در فاز ۱۰.۷ پیادهسازی میشود.
+
+
+
+ );
+}
+
+export function createDeliveryMapPage(config: PhaseGateConfig, title?: string) {
+ const pageTitle = title ?? config.title;
+ const PhasePage = createDeliveryPhasePage(config, true);
+
+ return function DeliveryMapPage() {
+ const caps = useDeliveryCapabilities();
+ if (caps.isLoading) return ;
+ if (caps.data?.features?.[config.feature] === true) {
+ return ;
+ }
+ return ;
+ };
+}
diff --git a/frontend/modules/delivery/components/DeliveryPhaseGate.tsx b/frontend/modules/delivery/components/DeliveryPhaseGate.tsx
new file mode 100644
index 0000000..c9b79dc
--- /dev/null
+++ b/frontend/modules/delivery/components/DeliveryPhaseGate.tsx
@@ -0,0 +1,67 @@
+"use client";
+
+import Link from "next/link";
+import { Rocket, MapPin } from "lucide-react";
+import { Button, Card, CardContent, Badge, EmptyState } from "@/components/ds";
+import { useDeliveryCapabilities } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import type { PhaseGateConfig } from "@/modules/delivery/types";
+
+export function DeliveryPhaseGate({
+ config,
+ children,
+ showMapHint,
+}: {
+ config: PhaseGateConfig;
+ children?: React.ReactNode;
+ showMapHint?: boolean;
+}) {
+ const caps = useDeliveryCapabilities();
+
+ if (caps.isLoading) return ;
+ if (caps.error) return caps.refetch()} />;
+
+ const enabled = caps.data?.features?.[config.feature] === true;
+ if (enabled && children) return <>{children}>;
+
+ return (
+
+
+
+
+
+ {showMapHint ? (
+
+ ) : (
+
+ )}
+
+
+
{config.title}
+
{config.description}
+
+ فاز {config.phase}
+
+ این قابلیت پس از فعالسازی در backend از طریق{" "}
+ /capabilities در دسترس خواهد بود.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function createDeliveryPhasePage(config: PhaseGateConfig, showMapHint = false) {
+ return function DeliveryPhasePage() {
+ return ;
+ };
+}
diff --git a/frontend/modules/delivery/components/DeliveryPortalShell.tsx b/frontend/modules/delivery/components/DeliveryPortalShell.tsx
new file mode 100644
index 0000000..33865ad
--- /dev/null
+++ b/frontend/modules/delivery/components/DeliveryPortalShell.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 {
+ DELIVERY_PORTAL,
+ navForDelivery,
+ filterNavByCapabilities,
+} from "@/modules/delivery/constants/portals";
+import { useDeliveryCapabilities } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+
+function isActive(pathname: string, href: string) {
+ const base = href.split("?")[0];
+ if (base === "/delivery") return pathname === "/delivery";
+ return pathname === base || pathname.startsWith(`${base}/`);
+}
+
+export function DeliveryPortalShell({
+ children,
+ title,
+}: {
+ children: React.ReactNode;
+ title?: string;
+}) {
+ const pathname = usePathname();
+ const { mode, toggle } = useColorMode();
+ const caps = useDeliveryCapabilities();
+ const [openGroups, setOpenGroups] = useState>({});
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ const nav = useMemo(
+ () => filterNavByCapabilities(navForDelivery(), 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 ?? DELIVERY_PORTAL.label}
+
+
+
+
+ تغییر پورتال
+
+
+
+ >
+ );
+
+ return (
+
+
+ {mobileOpen ? (
+
+
+ ) : null}
+
+
+
+ {DELIVERY_PORTAL.label}
+
+ {children}
+
+
+ );
+}
diff --git a/frontend/modules/delivery/components/createPortalLayout.tsx b/frontend/modules/delivery/components/createPortalLayout.tsx
new file mode 100644
index 0000000..1a97f59
--- /dev/null
+++ b/frontend/modules/delivery/components/createPortalLayout.tsx
@@ -0,0 +1,53 @@
+"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 { DeliveryPortalShell } from "@/modules/delivery/components/DeliveryPortalShell";
+
+export type DeliveryPortalId = "operations";
+
+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: DeliveryPortalId) {
+ return function PortalLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+ };
+}
+
+export function DeliveryRootLayout({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname();
+ const isHub = pathname === "/delivery/hub";
+
+ if (isHub) {
+ return {children};
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/frontend/modules/delivery/constants/permissions.ts b/frontend/modules/delivery/constants/permissions.ts
new file mode 100644
index 0000000..ad824a3
--- /dev/null
+++ b/frontend/modules/delivery/constants/permissions.ts
@@ -0,0 +1,26 @@
+/** Delivery permission constants — mirrors backend delivery.* tree. */
+
+export const DELIVERY_VIEW = "delivery.view";
+export const DELIVERY_MANAGE = "delivery.manage";
+
+export const ORG_VIEW = "delivery.organizations.view";
+export const ORG_CREATE = "delivery.organizations.create";
+export const ORG_UPDATE = "delivery.organizations.update";
+export const ORG_DELETE = "delivery.organizations.delete";
+
+export const HUB_VIEW = "delivery.hubs.view";
+export const HUB_CREATE = "delivery.hubs.create";
+
+export const DRIVER_VIEW = "delivery.drivers.view";
+export const DRIVER_CREATE = "delivery.drivers.create";
+export const DRIVER_UPDATE = "delivery.drivers.update";
+export const DRIVER_DELETE = "delivery.drivers.delete";
+export const DRIVER_LIFECYCLE_VIEW = "delivery.drivers.lifecycle.view";
+export const DRIVER_CREDENTIALS_VIEW = "delivery.drivers.credentials.view";
+export const DRIVER_CREDENTIALS_MANAGE = "delivery.drivers.credentials.manage";
+export const DRIVER_DOCUMENTS_VIEW = "delivery.drivers.documents.view";
+export const DRIVER_DOCUMENTS_MANAGE = "delivery.drivers.documents.manage";
+
+export const SETTINGS_VIEW = "delivery.settings.view";
+export const SETTINGS_MANAGE = "delivery.settings.manage";
+export const AUDIT_VIEW = "delivery.audit.view";
diff --git a/frontend/modules/delivery/constants/phaseRegistry.ts b/frontend/modules/delivery/constants/phaseRegistry.ts
new file mode 100644
index 0000000..d3d9dd2
--- /dev/null
+++ b/frontend/modules/delivery/constants/phaseRegistry.ts
@@ -0,0 +1,257 @@
+import type { PhaseGateConfig } from "@/modules/delivery/types";
+
+/** Maps future screens to backend capability flags and roadmap phases. */
+export const PHASE_REGISTRY: Record = {
+ dispatcher: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "داشبورد دیسپچر",
+ description: "صف دیسپچ، تخصیص خودکار و مدیریت عملیات لحظهای در فاز ۱۰.۵ فعال میشود.",
+ },
+ operations: {
+ feature: "tracking",
+ phase: "10.7",
+ title: "عملیات زنده",
+ description: "رهگیری لحظهای، وضعیت سفارش و نقشه عملیات در فاز ۱۰.۷.",
+ },
+ fleetDashboard: {
+ feature: "fleet",
+ phase: "10.2",
+ title: "داشبورد ناوگان",
+ description: "مدیریت خودرو، نگهداری و ظرفیت ناوگان در فاز ۱۰.۲.",
+ },
+ orders: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "سفارشها",
+ description: "مدیریت سفارش، وضعیت و چرخه حیات تحویل.",
+ },
+ dispatchQueue: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "صف دیسپچ",
+ description: "صف اولویتبندی شده سفارشهای در انتظار تخصیص.",
+ },
+ assignment: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "مرکز تخصیص",
+ description: "تخصیص و بازتخصیص راننده به سفارش.",
+ },
+ liveTracking: {
+ feature: "tracking",
+ phase: "10.7",
+ title: "رهگیری زنده",
+ description: "موقعیت لحظهای رانندگان و سفارشها روی نقشه.",
+ },
+ customerTracking: {
+ feature: "tracking",
+ phase: "10.7",
+ title: "رهگیری مشتری",
+ description: "لینک رهگیری عمومی برای مشتریان.",
+ },
+ mapView: {
+ feature: "routing_engine",
+ phase: "10.6",
+ title: "نمای نقشه",
+ description: "نقشه عملیاتی با مارکرها، خوشهبندی و ترافیک.",
+ },
+ routePlanner: {
+ feature: "routing_engine",
+ phase: "10.6",
+ title: "برنامهریزی مسیر",
+ description: "طراحی و پیشنمایش مسیر تحویل.",
+ },
+ routeOptimization: {
+ feature: "routing_engine",
+ phase: "10.6",
+ title: "بهینهسازی مسیر",
+ description: "بهینهسازی چندمسیره و ETA.",
+ },
+ geofence: {
+ feature: "routing_engine",
+ phase: "10.6",
+ title: "ژئوفنس",
+ description: "تعریف و نظارت بر محدودههای جغرافیایی.",
+ },
+ deliveryZones: {
+ feature: "routing_engine",
+ phase: "10.3",
+ title: "مناطق تحویل",
+ description: "تعریف zoneهای کاری رانندگان.",
+ },
+ vehicles: {
+ feature: "fleet",
+ phase: "10.2",
+ title: "خودروها",
+ description: "مدیریت ناوگان و وضعیت خودرو.",
+ },
+ vehicleTypes: {
+ feature: "fleet",
+ phase: "10.2",
+ title: "انواع خودرو",
+ description: "کلاسبندی ظرفیت و نوع خودرو.",
+ },
+ maintenance: {
+ feature: "fleet",
+ phase: "10.2",
+ title: "نگهداری ناوگان",
+ description: "برنامه نگهداری و سرویس خودروها.",
+ },
+ driverAvailability: {
+ feature: "fleet",
+ phase: "10.3",
+ title: "دسترسی رانندگان",
+ description: "وضعیت آنلاین/آفلاین و ظرفیت راننده.",
+ },
+ driverPerformance: {
+ feature: "tracking",
+ phase: "10.10",
+ title: "عملکرد رانندگان",
+ description: "KPI، امتیاز و گزارش عملکرد.",
+ },
+ schedules: {
+ feature: "fleet",
+ phase: "10.3",
+ title: "برنامهها",
+ description: "برنامه کاری و شیفتبندی.",
+ },
+ shifts: {
+ feature: "fleet",
+ phase: "10.3",
+ title: "شیفتها",
+ description: "مدیریت شیفت کاری رانندگان.",
+ },
+ tasks: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "وظایف",
+ description: "وظایف عملیاتی و چکلیست.",
+ },
+ pickups: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "جمعآوری",
+ description: "مدیریت pickupها.",
+ },
+ deliveries: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "تحویلها",
+ description: "مدیریت deliveryها.",
+ },
+ returns: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "مرجوعی",
+ description: "فرآیند بازگشت کالا.",
+ },
+ failed: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "تحویل ناموفق",
+ description: "مدیریت تحویلهای ناموفق.",
+ },
+ cod: {
+ feature: "settlement",
+ phase: "10.8",
+ title: "COD",
+ description: "مدیریت پرداخت در محل.",
+ },
+ settlements: {
+ feature: "settlement",
+ phase: "10.8",
+ title: "تسویه",
+ description: "تسویه حساب رانندگان و فروشندگان.",
+ },
+ payments: {
+ feature: "settlement",
+ phase: "10.8",
+ title: "پرداختها",
+ description: "تراکنشهای مالی تحویل.",
+ },
+ wallet: {
+ feature: "settlement",
+ phase: "10.8",
+ title: "کیف پول راننده",
+ description: "موجودی و تراکنش کیف پول.",
+ },
+ commissions: {
+ feature: "settlement",
+ phase: "10.8",
+ title: "کمیسیون",
+ description: "محاسبه و پرداخت کمیسیون.",
+ },
+ vendorPortal: {
+ feature: "dispatch_engine",
+ phase: "10.9",
+ title: "پورتال فروشنده",
+ description: "پنل فروشنده برای ثبت و رهگیری سفارش.",
+ },
+ notifications: {
+ feature: "dispatch_engine",
+ phase: "10.9",
+ title: "اعلانها",
+ description: "اعلانهای عملیاتی از Communication.",
+ },
+ incidents: {
+ feature: "dispatch_engine",
+ phase: "10.5",
+ title: "حوادث",
+ description: "ثبت و پیگیری حوادث عملیاتی.",
+ },
+ support: {
+ feature: "dispatch_engine",
+ phase: "10.9",
+ title: "تیکت پشتیبانی",
+ description: "تیکتهای پشتیبانی عملیات.",
+ },
+ reports: {
+ feature: "tracking",
+ phase: "10.10",
+ title: "گزارشها",
+ description: "گزارشهای عملیاتی و مالی.",
+ },
+ analytics: {
+ feature: "tracking",
+ phase: "10.10",
+ title: "آنالیتیکس",
+ description: "تحلیل دادههای لجستیک.",
+ },
+ heatmaps: {
+ feature: "tracking",
+ phase: "10.10",
+ title: "نقشه حرارتی",
+ description: "تراکم سفارش و تحویل.",
+ },
+ kpis: {
+ feature: "tracking",
+ phase: "10.10",
+ title: "KPIها",
+ description: "شاخصهای کلیدی عملکرد.",
+ },
+ aiRecommendations: {
+ feature: "ai",
+ phase: "10.10",
+ title: "پیشنهادات AI",
+ description: "پیشنهاد تخصیص و بهینهسازی.",
+ },
+ automation: {
+ feature: "ai",
+ phase: "10.10",
+ title: "اتوماسیون",
+ description: "قوانین خودکار عملیات.",
+ },
+ roles: {
+ feature: "foundation",
+ phase: "10.0",
+ title: "نقشها",
+ description: "مدیریت نقش از Identity Platform — API اختصاصی در فاز بعد.",
+ },
+ apiKeys: {
+ feature: "foundation",
+ phase: "10.9",
+ title: "کلید API",
+ description: "مدیریت کلیدهای یکپارچهسازی.",
+ },
+};
diff --git a/frontend/modules/delivery/constants/portals.ts b/frontend/modules/delivery/constants/portals.ts
new file mode 100644
index 0000000..e26413d
--- /dev/null
+++ b/frontend/modules/delivery/constants/portals.ts
@@ -0,0 +1,238 @@
+import type { LucideIcon } from "lucide-react";
+import {
+ LayoutDashboard,
+ Truck,
+ Users,
+ Map,
+ Package,
+ Building2,
+ Settings,
+ Shield,
+ Activity,
+ BarChart3,
+ Bell,
+ Wallet,
+ Route,
+ Radio,
+ Zap,
+ ClipboardList,
+ Car,
+ Clock,
+ AlertTriangle,
+ Headphones,
+ Key,
+ Plug,
+ Store,
+ Brain,
+ Cog,
+} from "lucide-react";
+
+export type DeliveryPortalId = "operations" | "admin";
+
+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 DELIVERY_PORTAL = {
+ id: "operations" as const,
+ label: "تربت درایور",
+ description: "پلتفرم لجستیک، پیک و ناوگان سازمانی",
+ basePath: "/delivery",
+ icon: Truck,
+};
+
+export const OPERATIONS_NAV: PortalNavGroup[] = [
+ {
+ id: "dashboards",
+ label: "داشبوردها",
+ icon: LayoutDashboard,
+ items: [
+ { href: "/delivery", label: "داشبورد اجرایی" },
+ { href: "/delivery/dispatcher", label: "دیسپچر", feature: "dispatch_engine" },
+ { href: "/delivery/operations", label: "عملیات زنده", feature: "tracking" },
+ { href: "/delivery/fleet-dashboard", label: "ناوگان", feature: "fleet" },
+ ],
+ },
+ {
+ id: "drivers",
+ label: "رانندگان",
+ icon: Users,
+ feature: "drivers",
+ items: [
+ { href: "/delivery/drivers", label: "فهرست رانندگان", feature: "drivers" },
+ { href: "/delivery/drivers/availability", label: "دسترسی", feature: "fleet" },
+ { href: "/delivery/drivers/performance", label: "عملکرد", feature: "tracking" },
+ ],
+ },
+ {
+ id: "orders",
+ label: "سفارشها",
+ icon: Package,
+ feature: "dispatch_engine",
+ items: [
+ { href: "/delivery/orders", label: "سفارشها", feature: "dispatch_engine" },
+ { href: "/delivery/dispatch/queue", label: "صف دیسپچ", feature: "dispatch_engine" },
+ { href: "/delivery/dispatch/assignment", label: "مرکز تخصیص", feature: "dispatch_engine" },
+ { href: "/delivery/pickups", label: "جمعآوری", feature: "dispatch_engine" },
+ { href: "/delivery/deliveries", label: "تحویلها", feature: "dispatch_engine" },
+ { href: "/delivery/returns", label: "مرجوعی", feature: "dispatch_engine" },
+ { href: "/delivery/failed", label: "تحویل ناموفق", feature: "dispatch_engine" },
+ ],
+ },
+ {
+ id: "tracking",
+ label: "رهگیری",
+ icon: Radio,
+ feature: "tracking",
+ items: [
+ { href: "/delivery/tracking/live", label: "رهگیری زنده", feature: "tracking" },
+ { href: "/delivery/tracking/customer", label: "رهگیری مشتری", feature: "tracking" },
+ ],
+ },
+ {
+ id: "maps",
+ label: "نقشه",
+ icon: Map,
+ feature: "routing_engine",
+ items: [
+ { href: "/delivery/maps/view", label: "نمای نقشه", feature: "routing_engine" },
+ { href: "/delivery/maps/routes/planner", label: "برنامهریزی مسیر", feature: "routing_engine" },
+ { href: "/delivery/maps/routes/optimization", label: "بهینهسازی", feature: "routing_engine" },
+ { href: "/delivery/maps/geofence", label: "ژئوفنس", feature: "routing_engine" },
+ { href: "/delivery/maps/zones", label: "مناطق تحویل", feature: "routing_engine" },
+ ],
+ },
+ {
+ id: "fleet",
+ label: "ناوگان",
+ icon: Car,
+ feature: "fleet",
+ items: [
+ { href: "/delivery/vehicles", label: "خودروها", feature: "fleet" },
+ { href: "/delivery/vehicle-types", label: "انواع خودرو", feature: "fleet" },
+ { href: "/delivery/fleet/maintenance", label: "نگهداری", feature: "fleet" },
+ { href: "/delivery/schedules", label: "برنامهها", feature: "fleet" },
+ { href: "/delivery/shifts", label: "شیفتها", feature: "fleet" },
+ { href: "/delivery/tasks", label: "وظایف", feature: "dispatch_engine" },
+ ],
+ },
+ {
+ id: "foundation",
+ label: "زیرساخت",
+ icon: Building2,
+ items: [
+ { href: "/delivery/organizations", label: "سازمانها" },
+ { href: "/delivery/branches", label: "شعب / هابها" },
+ { href: "/delivery/configurations", label: "پیکربندی" },
+ ],
+ },
+ {
+ id: "finance",
+ label: "مالی",
+ icon: Wallet,
+ feature: "settlement",
+ items: [
+ { href: "/delivery/cod", label: "COD", feature: "settlement" },
+ { href: "/delivery/settlements", label: "تسویه", feature: "settlement" },
+ { href: "/delivery/payments", label: "پرداختها", feature: "settlement" },
+ { href: "/delivery/wallet", label: "کیف پول راننده", feature: "settlement" },
+ { href: "/delivery/commissions", label: "کمیسیون", feature: "settlement" },
+ ],
+ },
+ {
+ id: "integrations",
+ label: "یکپارچهسازی",
+ icon: Plug,
+ items: [
+ { href: "/delivery/integrations/providers", label: "ارائهدهندگان خارجی" },
+ { href: "/delivery/integrations/routing", label: "موتور مسیریابی" },
+ { href: "/delivery/vendor-portal", label: "پورتال فروشنده", feature: "dispatch_engine" },
+ ],
+ },
+ {
+ id: "insights",
+ label: "گزارش و تحلیل",
+ icon: BarChart3,
+ feature: "tracking",
+ items: [
+ { href: "/delivery/reports", label: "گزارشها", feature: "tracking" },
+ { href: "/delivery/analytics", label: "آنالیتیکس", feature: "tracking" },
+ { href: "/delivery/analytics/heatmaps", label: "نقشه حرارتی", feature: "tracking" },
+ { href: "/delivery/analytics/kpis", label: "KPIها", feature: "tracking" },
+ ],
+ },
+ {
+ id: "support",
+ label: "پشتیبانی",
+ icon: Headphones,
+ feature: "dispatch_engine",
+ items: [
+ { href: "/delivery/notifications", label: "اعلانها", feature: "dispatch_engine" },
+ { href: "/delivery/incidents", label: "حوادث", feature: "dispatch_engine" },
+ { href: "/delivery/support", label: "تیکتها", feature: "dispatch_engine" },
+ ],
+ },
+ {
+ id: "ai",
+ label: "هوش مصنوعی",
+ icon: Brain,
+ feature: "ai",
+ items: [
+ { href: "/delivery/ai/recommendations", label: "پیشنهادات", feature: "ai" },
+ { href: "/delivery/automation", label: "اتوماسیون", feature: "ai" },
+ ],
+ },
+ {
+ id: "admin",
+ label: "مدیریت",
+ icon: Settings,
+ items: [
+ { href: "/delivery/settings", label: "تنظیمات" },
+ { href: "/delivery/permissions", label: "مجوزها" },
+ { href: "/delivery/roles", label: "نقشها" },
+ { href: "/delivery/audit", label: "ممیزی" },
+ { href: "/delivery/api-keys", label: "کلید API" },
+ { href: "/delivery/health", label: "سلامت سرویس" },
+ { href: "/delivery/capabilities", label: "قابلیتها" },
+ ],
+ },
+];
+
+export function navForDelivery(): PortalNavGroup[] {
+ return OPERATIONS_NAV;
+}
+
+export function filterNavByCapabilities(
+ nav: PortalNavGroup[],
+ features?: Record
+): PortalNavGroup[] {
+ if (!features) return nav;
+
+ const isEnabled = (feature?: string | null) => {
+ if (!feature) return true;
+ return features[feature] === true;
+ };
+
+ return nav
+ .map((group) => {
+ if (group.feature && !isEnabled(group.feature)) return null;
+ if (group.items) {
+ const items = group.items.filter((i) => isEnabled(i.feature));
+ if (items.length === 0 && !group.href) return null;
+ return { ...group, items };
+ }
+ return group;
+ })
+ .filter(Boolean) as PortalNavGroup[];
+}
diff --git a/frontend/modules/delivery/design-system/DeliveryStatGrid.tsx b/frontend/modules/delivery/design-system/DeliveryStatGrid.tsx
new file mode 100644
index 0000000..ee84498
--- /dev/null
+++ b/frontend/modules/delivery/design-system/DeliveryStatGrid.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { StatCard } from "@/components/ds";
+
+export function DeliveryStatGrid({
+ stats,
+}: {
+ stats: { label: string; value: string; hint?: string }[];
+}) {
+ return (
+
+ {stats.map((s) => (
+
+ ))}
+
+ );
+}
diff --git a/frontend/modules/delivery/design-system/DeliveryStatusChip.tsx b/frontend/modules/delivery/design-system/DeliveryStatusChip.tsx
new file mode 100644
index 0000000..53b10f5
--- /dev/null
+++ b/frontend/modules/delivery/design-system/DeliveryStatusChip.tsx
@@ -0,0 +1,10 @@
+"use client";
+
+import { Badge } from "@/components/ds";
+import { driverStatusLabels, lifecycleStatusLabels, lifecycleStatusTone } from "./tokens";
+
+export function DeliveryStatusChip({ status }: { status: string }) {
+ const label = driverStatusLabels[status] ?? lifecycleStatusLabels[status] ?? status;
+ const tone = lifecycleStatusTone[status] ?? "default";
+ return {label};
+}
diff --git a/frontend/modules/delivery/design-system/DeliveryTablePage.tsx b/frontend/modules/delivery/design-system/DeliveryTablePage.tsx
new file mode 100644
index 0000000..cbea301
--- /dev/null
+++ b/frontend/modules/delivery/design-system/DeliveryTablePage.tsx
@@ -0,0 +1,152 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { PageHeader, DataTable, EmptyState, Input, Pagination, Skeleton } from "@/components/ds";
+import { Search } from "lucide-react";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export type DeliveryTableColumn = {
+ key: string;
+ header: string;
+ className?: string;
+ render?: (row: Record) => React.ReactNode;
+ searchable?: boolean;
+ sortable?: boolean;
+ defaultVisible?: boolean;
+};
+
+export function DeliveryTablePage({
+ title,
+ description,
+ breadcrumb,
+ columns,
+ rows,
+ empty,
+ actions,
+ searchPlaceholder = "جستجو…",
+ filterFn,
+ toolbar,
+ page,
+ pageSize,
+ onPageChange,
+ selectedIds,
+ onSelectionChange,
+ bulkActions,
+ loading,
+}: {
+ title: string;
+ description?: string;
+ breadcrumb?: string;
+ columns: DeliveryTableColumn[];
+ rows: Record[];
+ empty?: React.ReactNode;
+ actions?: React.ReactNode;
+ searchPlaceholder?: string;
+ filterFn?: (row: Record, query: string) => boolean;
+ toolbar?: React.ReactNode;
+ page?: number;
+ pageSize?: number;
+ onPageChange?: (page: number) => void;
+ selectedIds?: Set;
+ onSelectionChange?: (ids: Set) => void;
+ bulkActions?: React.ReactNode;
+ loading?: boolean;
+}) {
+ const [query, setQuery] = useState("");
+ const [sortKey, setSortKey] = useState(null);
+ const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
+
+ const visibleColumns = columns;
+
+ const filtered = useMemo(() => {
+ let data = rows;
+ const q = query.trim().toLowerCase();
+ if (q) {
+ data = filterFn
+ ? data.filter((r) => filterFn(r, q))
+ : data.filter((r) =>
+ visibleColumns.some((c) => {
+ if (c.searchable === false) return false;
+ const val = r[c.key];
+ return val != null && String(val).toLowerCase().includes(q);
+ })
+ );
+ }
+ if (sortKey) {
+ data = [...data].sort((a, b) => {
+ const av = a[sortKey];
+ const bv = b[sortKey];
+ const cmp = String(av ?? "").localeCompare(String(bv ?? ""), "fa");
+ return sortDir === "asc" ? cmp : -cmp;
+ });
+ }
+ return data;
+ }, [rows, query, visibleColumns, filterFn, sortKey, sortDir]);
+
+ const paged = useMemo(() => {
+ if (!pageSize || !page) return filtered;
+ const start = (page - 1) * pageSize;
+ return filtered.slice(start, start + pageSize);
+ }, [filtered, page, pageSize]);
+
+ const totalPages = pageSize ? Math.max(1, Math.ceil(filtered.length / pageSize)) : 1;
+
+ if (loading) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+ {breadcrumb ?
: null}
+
+
+
+
+ setQuery(e.target.value)}
+ />
+
+ {toolbar}
+
+ {selectedIds && selectedIds.size > 0 && bulkActions ? (
+
+ {selectedIds.size} مورد انتخاب شده
+ {bulkActions}
+
+ ) : null}
+ {paged.length === 0 ? (
+ empty ??
+ ) : (
+ <>
+
({
+ key: c.key,
+ header: c.header,
+ className: c.className,
+ render: c.render
+ ? (row: Record) => c.render!(row)
+ : undefined,
+ }))}
+ rows={paged}
+ />
+ {pageSize && page && onPageChange ? (
+
+ ) : null}
+ >
+ )}
+
+ );
+}
diff --git a/frontend/modules/delivery/design-system/index.ts b/frontend/modules/delivery/design-system/index.ts
new file mode 100644
index 0000000..bcfcf6f
--- /dev/null
+++ b/frontend/modules/delivery/design-system/index.ts
@@ -0,0 +1,4 @@
+export * from "./tokens";
+export * from "./DeliveryStatusChip";
+export * from "./DeliveryTablePage";
+export { DeliveryStatGrid } from "./DeliveryStatGrid";
diff --git a/frontend/modules/delivery/design-system/tokens.ts b/frontend/modules/delivery/design-system/tokens.ts
new file mode 100644
index 0000000..4e96fe2
--- /dev/null
+++ b/frontend/modules/delivery/design-system/tokens.ts
@@ -0,0 +1,49 @@
+/** Delivery module visual tokens — logistics blue/teal palette. */
+export const deliveryTokens = {
+ accent: "var(--delivery-accent)",
+ accentSoft: "var(--delivery-accent-soft)",
+ map: "var(--delivery-map)",
+ success: "var(--delivery-success)",
+ warning: "var(--delivery-warning)",
+ danger: "var(--delivery-danger)",
+} as const;
+
+export const lifecycleStatusLabels: Record = {
+ draft: "پیشنویس",
+ active: "فعال",
+ inactive: "غیرفعال",
+ suspended: "معلق",
+ archived: "آرشیو",
+ pending: "در انتظار",
+ blocked: "مسدود",
+};
+
+export const lifecycleStatusTone: Record<
+ string,
+ "default" | "primary" | "success" | "warning" | "danger" | "info"
+> = {
+ draft: "default",
+ active: "success",
+ inactive: "default",
+ suspended: "warning",
+ archived: "danger",
+ pending: "info",
+ blocked: "danger",
+};
+
+export const driverStatusLabels: Record = {
+ pending: "در انتظار",
+ active: "فعال",
+ suspended: "معلق",
+ inactive: "غیرفعال",
+ blocked: "مسدود",
+ archived: "آرشیو",
+};
+
+export const providerKindLabels: Record = {
+ routing_engine: "موتور مسیریابی",
+ fleet: "ناوگان",
+ courier: "پیک",
+ maps: "نقشه",
+ custom: "سفارشی",
+};
diff --git a/frontend/modules/delivery/features/aiRecommendations.tsx b/frontend/modules/delivery/features/aiRecommendations.tsx
new file mode 100644
index 0000000..74e58de
--- /dev/null
+++ b/frontend/modules/delivery/features/aiRecommendations.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const AiRecommendationsPage = createDeliveryPhasePage(PHASE_REGISTRY.aiRecommendations);
+export default AiRecommendationsPage;
diff --git a/frontend/modules/delivery/features/analytics.tsx b/frontend/modules/delivery/features/analytics.tsx
new file mode 100644
index 0000000..fdbd8d0
--- /dev/null
+++ b/frontend/modules/delivery/features/analytics.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const AnalyticsPage = createDeliveryPhasePage(PHASE_REGISTRY.analytics);
+export default AnalyticsPage;
diff --git a/frontend/modules/delivery/features/apiKeys.tsx b/frontend/modules/delivery/features/apiKeys.tsx
new file mode 100644
index 0000000..a22e841
--- /dev/null
+++ b/frontend/modules/delivery/features/apiKeys.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ApiKeysPage = createDeliveryPhasePage(PHASE_REGISTRY.apiKeys);
+export default ApiKeysPage;
diff --git a/frontend/modules/delivery/features/assignment.tsx b/frontend/modules/delivery/features/assignment.tsx
new file mode 100644
index 0000000..824e041
--- /dev/null
+++ b/frontend/modules/delivery/features/assignment.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const AssignmentCenterPage = createDeliveryPhasePage(PHASE_REGISTRY.assignment);
+export default AssignmentCenterPage;
diff --git a/frontend/modules/delivery/features/audit.tsx b/frontend/modules/delivery/features/audit.tsx
new file mode 100644
index 0000000..cd4c32e
--- /dev/null
+++ b/frontend/modules/delivery/features/audit.tsx
@@ -0,0 +1,53 @@
+"use client";
+
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError, DeliveryEmptyState } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Input, FormField, Button } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const AuditLogsPage = function DeliveryAuditPage() {
+ const { tenantId } = useTenantId();
+ const [entityType, setEntityType] = useState("driver");
+ const [entityId, setEntityId] = useState("");
+ const [submitted, setSubmitted] = useState<{ type: string; id: string } | null>(null);
+
+ const q = useQuery({
+ queryKey: ["delivery", tenantId, "audit", submitted],
+ queryFn: () => deliveryApi.audit.list(tenantId!, {
+ entity_type: submitted!.type,
+ entity_id: submitted!.id,
+ }),
+ enabled: !!tenantId && !!submitted,
+ });
+
+ return (
+
+
+
+
+
+ setEntityType(e.target.value)} />
+ setEntityId(e.target.value)} />
+
+
+
+ {submitted && q.isLoading ?
: null}
+ {q.error ?
q.refetch()} /> : null}
+ {submitted && !q.isLoading && !q.error && (q.data?.length ?? 0) === 0 ? (
+
+ ) : null}
+
+ {(q.data ?? []).map((log) => (
+
+ {String(log.action)} — {new Date(String(log.created_at)).toLocaleString("fa-IR")}
+ {log.message ? {String(log.message)}
: null}
+
+ ))}
+
+
+ );
+};
+export default AuditLogsPage;
diff --git a/frontend/modules/delivery/features/automation.tsx b/frontend/modules/delivery/features/automation.tsx
new file mode 100644
index 0000000..6ba576d
--- /dev/null
+++ b/frontend/modules/delivery/features/automation.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const AutomationPage = createDeliveryPhasePage(PHASE_REGISTRY.automation);
+export default AutomationPage;
diff --git a/frontend/modules/delivery/features/branches.tsx b/frontend/modules/delivery/features/branches.tsx
new file mode 100644
index 0000000..c48d012
--- /dev/null
+++ b/frontend/modules/delivery/features/branches.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const BranchesPage = createDeliveryListPage({
+ title: "شعب و هابها",
+ breadcrumb: "شعب",
+ resourceKey: "hubs",
+ permission: "delivery.hubs.view",
+ api: deliveryApi.hubs,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ { name: "status", label: "وضعیت", type: "select", options: [
+ { value: "active", label: "فعال" },
+ { value: "draft", label: "پیشنویس" },
+ ]},
+ ],
+});
+
+export default BranchesPage;
diff --git a/frontend/modules/delivery/features/capabilities.tsx b/frontend/modules/delivery/features/capabilities.tsx
new file mode 100644
index 0000000..64eb3ae
--- /dev/null
+++ b/frontend/modules/delivery/features/capabilities.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { useDeliveryCapabilities } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const CapabilitiesPage = function DeliveryCapabilitiesPage() {
+ const q = useDeliveryCapabilities();
+ 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/delivery/features/cod.tsx b/frontend/modules/delivery/features/cod.tsx
new file mode 100644
index 0000000..90dc3d3
--- /dev/null
+++ b/frontend/modules/delivery/features/cod.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const CodManagementPage = createDeliveryPhasePage(PHASE_REGISTRY.cod);
+export default CodManagementPage;
diff --git a/frontend/modules/delivery/features/commissions.tsx b/frontend/modules/delivery/features/commissions.tsx
new file mode 100644
index 0000000..1232d65
--- /dev/null
+++ b/frontend/modules/delivery/features/commissions.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const CommissionsPage = createDeliveryPhasePage(PHASE_REGISTRY.commissions);
+export default CommissionsPage;
diff --git a/frontend/modules/delivery/features/configurations.tsx b/frontend/modules/delivery/features/configurations.tsx
new file mode 100644
index 0000000..e46e52b
--- /dev/null
+++ b/frontend/modules/delivery/features/configurations.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const ConfigurationsPage = createDeliveryListPage({
+ title: "پیکربندی",
+ breadcrumb: "پیکربندی",
+ resourceKey: "configurations",
+ permission: "delivery.configurations.view",
+ api: deliveryApi.configurations,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ ],
+});
+
+export default ConfigurationsPage;
diff --git a/frontend/modules/delivery/features/customerTracking.tsx b/frontend/modules/delivery/features/customerTracking.tsx
new file mode 100644
index 0000000..6edb729
--- /dev/null
+++ b/frontend/modules/delivery/features/customerTracking.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const CustomerTrackingPage = createDeliveryPhasePage(PHASE_REGISTRY.customerTracking);
+export default CustomerTrackingPage;
diff --git a/frontend/modules/delivery/features/dashboard.tsx b/frontend/modules/delivery/features/dashboard.tsx
new file mode 100644
index 0000000..35eb3e0
--- /dev/null
+++ b/frontend/modules/delivery/features/dashboard.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import Link from "next/link";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { useTenantId } from "@/hooks/useTenantId";
+import { useDeliveryCapabilities, useDeliveryMetrics } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, StatCard, Card, CardContent, Button, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const ExecutiveDashboard = function DeliveryExecutiveDashboard() {
+ const { tenantId } = useTenantId();
+ const caps = useDeliveryCapabilities();
+ const metrics = useDeliveryMetrics();
+
+ const countsQ = useQuery({
+ queryKey: ["delivery", tenantId, "dashboard-counts"],
+ queryFn: async () => {
+ const [orgs, hubs, drivers] = await Promise.all([
+ deliveryApi.organizations.list(tenantId!, { page: 1, page_size: 500 }),
+ deliveryApi.hubs.list(tenantId!, { page: 1, page_size: 500 }),
+ deliveryApi.drivers.list(tenantId!, { page: 1, page_size: 500 }),
+ ]);
+ const driverItems = Array.isArray(drivers) ? drivers : drivers.items;
+ return {
+ organizations: orgs.length,
+ hubs: hubs.length,
+ drivers: driverItems.length,
+ driversActive: driverItems.filter((d) => d.status === "active").length,
+ };
+ },
+ enabled: !!tenantId,
+ });
+
+ if (!tenantId || countsQ.isLoading || caps.isLoading) return ;
+ if (countsQ.error) return countsQ.refetch()} />;
+
+ const c = countsQ.data!;
+
+ return (
+
+
+
Torbat Driver v{caps.data?.version}}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {metrics.data ? (
+
+
+ فاز {metrics.data.phase} — {metrics.data.note}
+
+
+ ) : null}
+
+ );
+};
+
+export default ExecutiveDashboard;
diff --git a/frontend/modules/delivery/features/deliveries.tsx b/frontend/modules/delivery/features/deliveries.tsx
new file mode 100644
index 0000000..42f5687
--- /dev/null
+++ b/frontend/modules/delivery/features/deliveries.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DeliveriesPage = createDeliveryPhasePage(PHASE_REGISTRY.deliveries);
+export default DeliveriesPage;
diff --git a/frontend/modules/delivery/features/deliveryZones.tsx b/frontend/modules/delivery/features/deliveryZones.tsx
new file mode 100644
index 0000000..82aa527
--- /dev/null
+++ b/frontend/modules/delivery/features/deliveryZones.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DeliveryZonesPage = createDeliveryMapPage(PHASE_REGISTRY.deliveryZones, "مناطق تحویل");
+export default DeliveryZonesPage;
diff --git a/frontend/modules/delivery/features/dispatchQueue.tsx b/frontend/modules/delivery/features/dispatchQueue.tsx
new file mode 100644
index 0000000..c005a3d
--- /dev/null
+++ b/frontend/modules/delivery/features/dispatchQueue.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DispatchQueuePage = createDeliveryPhasePage(PHASE_REGISTRY.dispatchQueue);
+export default DispatchQueuePage;
diff --git a/frontend/modules/delivery/features/dispatcher.tsx b/frontend/modules/delivery/features/dispatcher.tsx
new file mode 100644
index 0000000..ac6d986
--- /dev/null
+++ b/frontend/modules/delivery/features/dispatcher.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DispatcherDashboard = createDeliveryPhasePage(PHASE_REGISTRY.dispatcher);
+export default DispatcherDashboard;
diff --git a/frontend/modules/delivery/features/driverAvailability.tsx b/frontend/modules/delivery/features/driverAvailability.tsx
new file mode 100644
index 0000000..c38df67
--- /dev/null
+++ b/frontend/modules/delivery/features/driverAvailability.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DriverAvailabilityPage = createDeliveryPhasePage(PHASE_REGISTRY.driverAvailability);
+export default DriverAvailabilityPage;
diff --git a/frontend/modules/delivery/features/driverDetail.tsx b/frontend/modules/delivery/features/driverDetail.tsx
new file mode 100644
index 0000000..98e22e1
--- /dev/null
+++ b/frontend/modules/delivery/features/driverDetail.tsx
@@ -0,0 +1,160 @@
+"use client";
+
+import { useParams } from "next/navigation";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Button, Badge, Tabs, ConfirmDialog } from "@/components/ds";
+import { DeliveryDetailBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import { DeliveryStatusChip } from "@/modules/delivery/design-system";
+import { useDeliveryPermissions } from "@/modules/delivery/hooks/useDeliveryPermissions";
+import { useState } from "react";
+
+export const DriverDetailPage = function DriverDetail() {
+ const params = useParams();
+ const driverId = String(params.id);
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const perms = useDeliveryPermissions();
+ const [tab, setTab] = useState("profile");
+ const [lifecycleAction, setLifecycleAction] = useState(null);
+
+ const driverQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver", driverId],
+ queryFn: () => deliveryApi.drivers.get(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId,
+ });
+
+ const lifecycleQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-lifecycle", driverId],
+ queryFn: () => deliveryApi.drivers.lifecycle(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "lifecycle",
+ });
+
+ const credsQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-creds", driverId],
+ queryFn: () => deliveryApi.drivers.credentials.list(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "credentials",
+ });
+
+ const docsQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-docs", driverId],
+ queryFn: () => deliveryApi.drivers.documents.list(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "documents",
+ });
+
+ const lifecycleM = useMutation({
+ mutationFn: (action: string) => {
+ const body = { version: driverQ.data!.version as number };
+ const drivers = deliveryApi.drivers;
+ switch (action) {
+ case "activate":
+ return drivers.activate(tenantId!, driverId, body);
+ case "suspend":
+ return drivers.suspend(tenantId!, driverId, body);
+ case "resume":
+ return drivers.resume(tenantId!, driverId, body);
+ case "deactivate":
+ return drivers.deactivate(tenantId!, driverId, body);
+ case "block":
+ return drivers.block(tenantId!, driverId, body);
+ case "unblock":
+ return drivers.unblock(tenantId!, driverId, body);
+ case "archive":
+ return drivers.archive(tenantId!, driverId, body);
+ default:
+ throw new Error("عملیات نامعتبر");
+ }
+ },
+ onSuccess: () => {
+ toast.success("وضعیت بهروز شد");
+ setLifecycleAction(null);
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver", driverId] });
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver-lifecycle", driverId] });
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || driverQ.isLoading) return ;
+ if (driverQ.error) return driverQ.refetch()} />;
+
+ const d = driverQ.data!;
+ const lifecycleActions = ["activate", "suspend", "resume", "deactivate", "block", "unblock", "archive"];
+
+ return (
+
+
+
}
+ />
+
+ {tab === "profile" ? (
+
+ ایمیل: {String(d.email ?? "—")}
+ نسخه: {String(d.version)}
+ ایجاد: {new Date(String(d.created_at)).toLocaleString("fa-IR")}
+ {d.notes ? {String(d.notes)}
: null}
+
+ ) : null}
+ {tab === "lifecycle" ? (
+
+
+ {lifecycleActions.map((a) => (
+
+ ))}
+
+ {(lifecycleQ.data ?? []).map((ev) => (
+
+ {String(ev.action)}: {String(ev.from_status)} → {String(ev.to_status)}
+ {new Date(String(ev.created_at)).toLocaleString("fa-IR")}
+
+ ))}
+
+ ) : null}
+ {tab === "credentials" ? (
+
+ {(credsQ.data ?? []).map((c) => (
+
+ {String(c.kind)} — {String(c.number)}
+ {c.is_verified ? تأیید شده : null}
+
+ ))}
+
+ ) : null}
+ {tab === "documents" ? (
+
+ {(docsQ.data ?? []).map((doc) => (
+
+ {String(doc.title)} — {String(doc.kind)}
+
+ ))}
+
+ ) : null}
+
setLifecycleAction(null)}
+ title="تغییر وضعیت"
+ description={`آیا از اجرای ${lifecycleAction} اطمینان دارید؟`}
+ onConfirm={() => lifecycleAction && lifecycleM.mutate(lifecycleAction)}
+ loading={lifecycleM.isPending}
+ />
+
+ );
+};
+
+export default DriverDetailPage;
diff --git a/frontend/modules/delivery/features/driverPerformance.tsx b/frontend/modules/delivery/features/driverPerformance.tsx
new file mode 100644
index 0000000..d86e26b
--- /dev/null
+++ b/frontend/modules/delivery/features/driverPerformance.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DriverPerformancePage = createDeliveryPhasePage(PHASE_REGISTRY.driverPerformance);
+export default DriverPerformancePage;
diff --git a/frontend/modules/delivery/features/drivers.tsx b/frontend/modules/delivery/features/drivers.tsx
new file mode 100644
index 0000000..8d3e383
--- /dev/null
+++ b/frontend/modules/delivery/features/drivers.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const DriversPage = createDeliveryListPage({
+ title: "رانندگان",
+ breadcrumb: "رانندگان",
+ resourceKey: "drivers",
+ permission: "delivery.drivers.view",
+ api: {
+ list: async (tenantId, params) => {
+ const res = await deliveryApi.drivers.list(tenantId, params);
+ return Array.isArray(res) ? res : res.items;
+ },
+ get: deliveryApi.drivers.get,
+ create: deliveryApi.drivers.create,
+ update: deliveryApi.drivers.update,
+ remove: deliveryApi.drivers.remove,
+ },
+ columns: [
+ { key: "code", header: "کد", type: "link", linkPrefix: "/delivery/drivers" },
+ { key: "display_name", header: "نام" },
+ { key: "mobile", header: "موبایل" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "hub_id", label: "هاب", type: "hub" },
+ { name: "code", label: "کد", required: true },
+ { name: "display_name", label: "نام نمایشی", required: true },
+ { name: "mobile", label: "موبایل" },
+ { name: "email", label: "ایمیل", type: "email" },
+ ],
+ detailHref: (row) => row.id ? `/delivery/drivers/${row.id}` : null,
+});
+
+export default DriversPage;
diff --git a/frontend/modules/delivery/features/externalProviders.tsx b/frontend/modules/delivery/features/externalProviders.tsx
new file mode 100644
index 0000000..907cedf
--- /dev/null
+++ b/frontend/modules/delivery/features/externalProviders.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const ExternalProvidersPage = createDeliveryListPage({
+ title: "ارائهدهندگان خارجی",
+ breadcrumb: "ارائهدهندگان خارجی",
+ resourceKey: "externalProviders",
+ permission: "delivery.external_providers.view",
+ api: deliveryApi.externalProviders,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ { name: "provider_kind", label: "نوع", type: "select", options: [
+ { value: "routing_engine", label: "مسیریابی" },
+ { value: "fleet", label: "ناوگان" },
+ { value: "courier", label: "پیک" },
+ { value: "maps", label: "نقشه" },
+ ]},
+ ],
+});
+
+export default ExternalProvidersPage;
diff --git a/frontend/modules/delivery/features/failed.tsx b/frontend/modules/delivery/features/failed.tsx
new file mode 100644
index 0000000..a0b7a81
--- /dev/null
+++ b/frontend/modules/delivery/features/failed.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const FailedDeliveriesPage = createDeliveryPhasePage(PHASE_REGISTRY.failed);
+export default FailedDeliveriesPage;
diff --git a/frontend/modules/delivery/features/fleetDashboard.tsx b/frontend/modules/delivery/features/fleetDashboard.tsx
new file mode 100644
index 0000000..825e3f5
--- /dev/null
+++ b/frontend/modules/delivery/features/fleetDashboard.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const FleetDashboard = createDeliveryPhasePage(PHASE_REGISTRY.fleetDashboard);
+export default FleetDashboard;
diff --git a/frontend/modules/delivery/features/geofence.tsx b/frontend/modules/delivery/features/geofence.tsx
new file mode 100644
index 0000000..ed86e61
--- /dev/null
+++ b/frontend/modules/delivery/features/geofence.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const GeofencePage = createDeliveryMapPage(PHASE_REGISTRY.geofence, "ژئوفنس");
+export default GeofencePage;
diff --git a/frontend/modules/delivery/features/health.tsx b/frontend/modules/delivery/features/health.tsx
new file mode 100644
index 0000000..84a2991
--- /dev/null
+++ b/frontend/modules/delivery/features/health.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { useDeliveryHealth } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const HealthPage = function DeliveryHealthPage() {
+ const q = useDeliveryHealth();
+ 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/delivery/features/heatmaps.tsx b/frontend/modules/delivery/features/heatmaps.tsx
new file mode 100644
index 0000000..253ca9a
--- /dev/null
+++ b/frontend/modules/delivery/features/heatmaps.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const HeatMapsPage = createDeliveryPhasePage(PHASE_REGISTRY.heatmaps);
+export default HeatMapsPage;
diff --git a/frontend/modules/delivery/features/hub.tsx b/frontend/modules/delivery/features/hub.tsx
new file mode 100644
index 0000000..6bfba71
--- /dev/null
+++ b/frontend/modules/delivery/features/hub.tsx
@@ -0,0 +1,33 @@
+"use client";
+
+import Link from "next/link";
+import { useMe } from "@/hooks/useMe";
+import { DELIVERY_PORTAL } from "@/modules/delivery/constants/portals";
+import { Card, CardContent, Button, LoadingState, ErrorState } from "@/components/ds";
+
+export const DeliveryHub = function DeliveryHubPage() {
+ const { me, loading, error, reload } = useMe();
+ if (loading) return ;
+ if (error) return ;
+ if (!me?.current_tenant_id) {
+ return ;
+ }
+
+ return (
+
+
تربت درایور
+
پلتفرم لجستیک و پیک سازمانی
+
+
+ {DELIVERY_PORTAL.label}
+ {DELIVERY_PORTAL.description}
+
+
+
+
+
+
+ );
+};
+
+export default DeliveryHub;
diff --git a/frontend/modules/delivery/features/incidents.tsx b/frontend/modules/delivery/features/incidents.tsx
new file mode 100644
index 0000000..da929b9
--- /dev/null
+++ b/frontend/modules/delivery/features/incidents.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const IncidentsPage = createDeliveryPhasePage(PHASE_REGISTRY.incidents);
+export default IncidentsPage;
diff --git a/frontend/modules/delivery/features/kpis.tsx b/frontend/modules/delivery/features/kpis.tsx
new file mode 100644
index 0000000..700d55a
--- /dev/null
+++ b/frontend/modules/delivery/features/kpis.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const PerformanceKpisPage = createDeliveryPhasePage(PHASE_REGISTRY.kpis);
+export default PerformanceKpisPage;
diff --git a/frontend/modules/delivery/features/liveTracking.tsx b/frontend/modules/delivery/features/liveTracking.tsx
new file mode 100644
index 0000000..d9eda59
--- /dev/null
+++ b/frontend/modules/delivery/features/liveTracking.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const LiveTrackingPage = createDeliveryPhasePage(PHASE_REGISTRY.liveTracking);
+export default LiveTrackingPage;
diff --git a/frontend/modules/delivery/features/maintenance.tsx b/frontend/modules/delivery/features/maintenance.tsx
new file mode 100644
index 0000000..080338c
--- /dev/null
+++ b/frontend/modules/delivery/features/maintenance.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const FleetMaintenancePage = createDeliveryPhasePage(PHASE_REGISTRY.maintenance);
+export default FleetMaintenancePage;
diff --git a/frontend/modules/delivery/features/mapView.tsx b/frontend/modules/delivery/features/mapView.tsx
new file mode 100644
index 0000000..2838e55
--- /dev/null
+++ b/frontend/modules/delivery/features/mapView.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const MapViewPage = createDeliveryMapPage(PHASE_REGISTRY.mapView, "نمای نقشه");
+export default MapViewPage;
diff --git a/frontend/modules/delivery/features/notifications.tsx b/frontend/modules/delivery/features/notifications.tsx
new file mode 100644
index 0000000..f6c1674
--- /dev/null
+++ b/frontend/modules/delivery/features/notifications.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const NotificationsPage = createDeliveryPhasePage(PHASE_REGISTRY.notifications);
+export default NotificationsPage;
diff --git a/frontend/modules/delivery/features/operations.tsx b/frontend/modules/delivery/features/operations.tsx
new file mode 100644
index 0000000..61e3c31
--- /dev/null
+++ b/frontend/modules/delivery/features/operations.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const LiveOperationsDashboard = createDeliveryPhasePage(PHASE_REGISTRY.operations);
+export default LiveOperationsDashboard;
diff --git a/frontend/modules/delivery/features/orders.tsx b/frontend/modules/delivery/features/orders.tsx
new file mode 100644
index 0000000..4595350
--- /dev/null
+++ b/frontend/modules/delivery/features/orders.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const OrdersPage = createDeliveryPhasePage(PHASE_REGISTRY.orders);
+export default OrdersPage;
diff --git a/frontend/modules/delivery/features/organizations.tsx b/frontend/modules/delivery/features/organizations.tsx
new file mode 100644
index 0000000..91622a7
--- /dev/null
+++ b/frontend/modules/delivery/features/organizations.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const OrganizationsPage = createDeliveryListPage({
+ title: "سازمانها",
+ breadcrumb: "سازمانها",
+ resourceKey: "organizations",
+ permission: "delivery.organizations.view",
+ api: deliveryApi.organizations,
+ 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 },
+ { name: "status", label: "وضعیت", type: "select", options: [
+ { value: "draft", label: "پیشنویس" },
+ { value: "active", label: "فعال" },
+ ]},
+ ],
+});
+
+export default OrganizationsPage;
diff --git a/frontend/modules/delivery/features/payments.tsx b/frontend/modules/delivery/features/payments.tsx
new file mode 100644
index 0000000..0c42134
--- /dev/null
+++ b/frontend/modules/delivery/features/payments.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const PaymentsPage = createDeliveryPhasePage(PHASE_REGISTRY.payments);
+export default PaymentsPage;
diff --git a/frontend/modules/delivery/features/permissions.tsx b/frontend/modules/delivery/features/permissions.tsx
new file mode 100644
index 0000000..02f85bf
--- /dev/null
+++ b/frontend/modules/delivery/features/permissions.tsx
@@ -0,0 +1,31 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const PermissionsPage = function DeliveryPermissionsPage() {
+ const { tenantId } = useTenantId();
+ const q = useQuery({
+ queryKey: ["delivery", tenantId, "permissions-catalog"],
+ queryFn: () => deliveryApi.permissions.catalog(tenantId!),
+ enabled: !!tenantId,
+ });
+ if (!tenantId || q.isLoading) return ;
+ if (q.error) return q.refetch()} />;
+ return (
+
+
+
+
+ {q.data?.permissions.map((p) => (
+ {p}
+ ))}
+
+
+ );
+};
+export default PermissionsPage;
diff --git a/frontend/modules/delivery/features/pickups.tsx b/frontend/modules/delivery/features/pickups.tsx
new file mode 100644
index 0000000..f11360c
--- /dev/null
+++ b/frontend/modules/delivery/features/pickups.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const PickupsPage = createDeliveryPhasePage(PHASE_REGISTRY.pickups);
+export default PickupsPage;
diff --git a/frontend/modules/delivery/features/reports.tsx b/frontend/modules/delivery/features/reports.tsx
new file mode 100644
index 0000000..26484d3
--- /dev/null
+++ b/frontend/modules/delivery/features/reports.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ReportsPage = createDeliveryPhasePage(PHASE_REGISTRY.reports);
+export default ReportsPage;
diff --git a/frontend/modules/delivery/features/returns.tsx b/frontend/modules/delivery/features/returns.tsx
new file mode 100644
index 0000000..6eb487d
--- /dev/null
+++ b/frontend/modules/delivery/features/returns.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ReturnsPage = createDeliveryPhasePage(PHASE_REGISTRY.returns);
+export default ReturnsPage;
diff --git a/frontend/modules/delivery/features/roles.tsx b/frontend/modules/delivery/features/roles.tsx
new file mode 100644
index 0000000..4a0cf12
--- /dev/null
+++ b/frontend/modules/delivery/features/roles.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const RolesPage = createDeliveryPhasePage(PHASE_REGISTRY.roles);
+export default RolesPage;
diff --git a/frontend/modules/delivery/features/routeOptimization.tsx b/frontend/modules/delivery/features/routeOptimization.tsx
new file mode 100644
index 0000000..82d65eb
--- /dev/null
+++ b/frontend/modules/delivery/features/routeOptimization.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const RouteOptimizationPage = createDeliveryMapPage(PHASE_REGISTRY.routeOptimization, "بهینهسازی");
+export default RouteOptimizationPage;
diff --git a/frontend/modules/delivery/features/routePlanner.tsx b/frontend/modules/delivery/features/routePlanner.tsx
new file mode 100644
index 0000000..69a513a
--- /dev/null
+++ b/frontend/modules/delivery/features/routePlanner.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const RoutePlannerPage = createDeliveryMapPage(PHASE_REGISTRY.routePlanner, "برنامهریزی مسیر");
+export default RoutePlannerPage;
diff --git a/frontend/modules/delivery/features/routingEngines.tsx b/frontend/modules/delivery/features/routingEngines.tsx
new file mode 100644
index 0000000..1bde88a
--- /dev/null
+++ b/frontend/modules/delivery/features/routingEngines.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const RoutingEnginesPage = createDeliveryListPage({
+ title: "موتورهای مسیریابی",
+ breadcrumb: "موتورهای مسیریابی",
+ resourceKey: "routingEngines",
+ permission: "delivery.routing_engines.view",
+ api: deliveryApi.routingEngines,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ],
+});
+
+export default RoutingEnginesPage;
diff --git a/frontend/modules/delivery/features/schedules.tsx b/frontend/modules/delivery/features/schedules.tsx
new file mode 100644
index 0000000..29f46cf
--- /dev/null
+++ b/frontend/modules/delivery/features/schedules.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const SchedulesPage = createDeliveryPhasePage(PHASE_REGISTRY.schedules);
+export default SchedulesPage;
diff --git a/frontend/modules/delivery/features/settings.tsx b/frontend/modules/delivery/features/settings.tsx
new file mode 100644
index 0000000..beab3fd
--- /dev/null
+++ b/frontend/modules/delivery/features/settings.tsx
@@ -0,0 +1,73 @@
+"use client";
+
+import { useState } from "react";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Button, Input, FormField } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import { useDeliveryLookups } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+
+export const SettingsPage = function DeliverySettingsPage() {
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const lookups = useDeliveryLookups(tenantId);
+ const [key, setKey] = useState("");
+ const [value, setValue] = useState("");
+ const [orgId, setOrgId] = useState("");
+
+ const listQ = useQuery({
+ queryKey: ["delivery", tenantId, "settings"],
+ queryFn: () => deliveryApi.settings.list(tenantId!),
+ enabled: !!tenantId,
+ });
+
+ const saveM = useMutation({
+ mutationFn: () => deliveryApi.settings.upsert(tenantId!, {
+ organization_id: orgId || null,
+ key,
+ value: JSON.parse(value || "{}"),
+ }),
+ onSuccess: () => {
+ toast.success("تنظیمات ذخیره شد");
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "settings"] });
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || listQ.isLoading || lookups.isLoading) return ;
+ if (listQ.error) return listQ.refetch()} />;
+
+ return (
+
+
+
+
+
+
+
+
+ setKey(e.target.value)} />
+ setValue(e.target.value)} placeholder='{"enabled":true}' />
+
+
+
+
+ {(listQ.data ?? []).map((s) => (
+
+ {String(s.key)}
+ {JSON.stringify(s.value, null, 2)}
+
+ ))}
+
+
+ );
+};
+export default SettingsPage;
diff --git a/frontend/modules/delivery/features/settlements.tsx b/frontend/modules/delivery/features/settlements.tsx
new file mode 100644
index 0000000..0f4be22
--- /dev/null
+++ b/frontend/modules/delivery/features/settlements.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const SettlementsPage = createDeliveryPhasePage(PHASE_REGISTRY.settlements);
+export default SettlementsPage;
diff --git a/frontend/modules/delivery/features/shifts.tsx b/frontend/modules/delivery/features/shifts.tsx
new file mode 100644
index 0000000..1c2f478
--- /dev/null
+++ b/frontend/modules/delivery/features/shifts.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ShiftsPage = createDeliveryPhasePage(PHASE_REGISTRY.shifts);
+export default ShiftsPage;
diff --git a/frontend/modules/delivery/features/support.tsx b/frontend/modules/delivery/features/support.tsx
new file mode 100644
index 0000000..6c4dfe1
--- /dev/null
+++ b/frontend/modules/delivery/features/support.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const SupportTicketsPage = createDeliveryPhasePage(PHASE_REGISTRY.support);
+export default SupportTicketsPage;
diff --git a/frontend/modules/delivery/features/tasks.tsx b/frontend/modules/delivery/features/tasks.tsx
new file mode 100644
index 0000000..6b7dece
--- /dev/null
+++ b/frontend/modules/delivery/features/tasks.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const TasksPage = createDeliveryPhasePage(PHASE_REGISTRY.tasks);
+export default TasksPage;
diff --git a/frontend/modules/delivery/features/vehicleTypes.tsx b/frontend/modules/delivery/features/vehicleTypes.tsx
new file mode 100644
index 0000000..7d4b127
--- /dev/null
+++ b/frontend/modules/delivery/features/vehicleTypes.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const VehicleTypesPage = createDeliveryPhasePage(PHASE_REGISTRY.vehicleTypes);
+export default VehicleTypesPage;
diff --git a/frontend/modules/delivery/features/vehicles.tsx b/frontend/modules/delivery/features/vehicles.tsx
new file mode 100644
index 0000000..8b1efc8
--- /dev/null
+++ b/frontend/modules/delivery/features/vehicles.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const VehiclesPage = createDeliveryPhasePage(PHASE_REGISTRY.vehicles);
+export default VehiclesPage;
diff --git a/frontend/modules/delivery/features/vendorPortal.tsx b/frontend/modules/delivery/features/vendorPortal.tsx
new file mode 100644
index 0000000..a3ae737
--- /dev/null
+++ b/frontend/modules/delivery/features/vendorPortal.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const VendorPortalPage = createDeliveryPhasePage(PHASE_REGISTRY.vendorPortal);
+export default VendorPortalPage;
diff --git a/frontend/modules/delivery/features/wallet.tsx b/frontend/modules/delivery/features/wallet.tsx
new file mode 100644
index 0000000..96b6888
--- /dev/null
+++ b/frontend/modules/delivery/features/wallet.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const DriverWalletPage = createDeliveryPhasePage(PHASE_REGISTRY.wallet);
+export default DriverWalletPage;
diff --git a/frontend/modules/delivery/hooks/useDeliveryCapabilities.ts b/frontend/modules/delivery/hooks/useDeliveryCapabilities.ts
new file mode 100644
index 0000000..8a92857
--- /dev/null
+++ b/frontend/modules/delivery/hooks/useDeliveryCapabilities.ts
@@ -0,0 +1,42 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export function useDeliveryCapabilities() {
+ return useQuery({
+ queryKey: ["delivery", "capabilities"],
+ queryFn: () => deliveryApi.capabilities(),
+ staleTime: 60_000,
+ });
+}
+
+export function useDeliveryHealth() {
+ return useQuery({
+ queryKey: ["delivery", "health"],
+ queryFn: () => deliveryApi.health(),
+ });
+}
+
+export function useDeliveryMetrics() {
+ return useQuery({
+ queryKey: ["delivery", "metrics"],
+ queryFn: () => deliveryApi.metrics(),
+ staleTime: 30_000,
+ });
+}
+
+export function useDeliveryLookups(tenantId: string | null) {
+ return useQuery({
+ queryKey: ["delivery", tenantId, "lookups"],
+ queryFn: async () => {
+ const [organizations, hubs] = await Promise.all([
+ deliveryApi.organizations.list(tenantId!, { page: 1, page_size: 500 }),
+ deliveryApi.hubs.list(tenantId!, { page: 1, page_size: 500 }),
+ ]);
+ return { organizations, hubs };
+ },
+ enabled: !!tenantId,
+ staleTime: 30_000,
+ });
+}
diff --git a/frontend/modules/delivery/hooks/useDeliveryPermissions.ts b/frontend/modules/delivery/hooks/useDeliveryPermissions.ts
new file mode 100644
index 0000000..e46b307
--- /dev/null
+++ b/frontend/modules/delivery/hooks/useDeliveryPermissions.ts
@@ -0,0 +1,45 @@
+"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useTenantId } from "@/hooks/useTenantId";
+import { useMe } from "@/hooks/useMe";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+/** Permission checks — backend is authoritative; owner gets full access in UI. */
+export function useDeliveryPermissions() {
+ const { tenantId } = useTenantId();
+ const { me } = useMe();
+
+ const catalogQ = useQuery({
+ queryKey: ["delivery", tenantId, "permissions-catalog"],
+ queryFn: () => deliveryApi.permissions.catalog(tenantId!),
+ enabled: !!tenantId,
+ staleTime: 5 * 60_000,
+ });
+
+ const membership = me?.memberships.find((m) => m.tenant_id === tenantId);
+ const isOwner = membership?.is_owner ?? false;
+ const isPlatformAdmin = me?.platform_role === "platform_admin";
+
+ const can = (permission?: string) => {
+ if (!permission) return true;
+ if (isOwner || isPlatformAdmin) return true;
+ if (permission.endsWith(".view") || permission === "delivery.view") return !!tenantId;
+ return false;
+ };
+
+ const canManage = (viewPerm?: string, managePerm?: string) => {
+ if (isOwner || isPlatformAdmin) return true;
+ if (managePerm) return can(managePerm);
+ if (viewPerm) return can(viewPerm.replace(".view", ".manage"));
+ return can("delivery.manage");
+ };
+
+ return {
+ can,
+ canManage,
+ isOwner,
+ catalog: catalogQ.data,
+ isLoading: catalogQ.isLoading,
+ };
+}
diff --git a/frontend/modules/delivery/index.ts b/frontend/modules/delivery/index.ts
new file mode 100644
index 0000000..861c945
--- /dev/null
+++ b/frontend/modules/delivery/index.ts
@@ -0,0 +1,3 @@
+export { deliveryApi, DeliveryApiError } from "./services/delivery-api";
+export { DELIVERY_PORTAL, navForDelivery } from "./constants/portals";
+export { DeliveryPortalShell } from "./components/DeliveryPortalShell";
diff --git a/frontend/modules/delivery/pages/shared.tsx b/frontend/modules/delivery/pages/shared.tsx
new file mode 100644
index 0000000..fc00944
--- /dev/null
+++ b/frontend/modules/delivery/pages/shared.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import { LoadingState, ErrorState, EmptyState } from "@/components/ds";
+import { DeliveryApiError } from "@/modules/delivery/services/delivery-api";
+import { PermissionDeniedState } from "@/src/shared/ui";
+
+export function DeliveryPageLoader({ label = "در حال بارگذاری…" }: { label?: string }) {
+ return ;
+}
+
+export function DeliveryPageError({
+ error,
+ onRetry,
+}: {
+ error: unknown;
+ onRetry?: () => void;
+}) {
+ if (error instanceof DeliveryApiError && error.status === 403) {
+ return (
+
+ );
+ }
+ const message = error instanceof Error ? error.message : "خطا در دریافت دادهها";
+ return ;
+}
+
+export function DeliveryEmptyState({
+ title = "موردی یافت نشد",
+ description = "هنوز دادهای ثبت نشده است.",
+ action,
+}: {
+ title?: string;
+ description?: string;
+ action?: React.ReactNode;
+}) {
+ 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/delivery/services/delivery-api.ts b/frontend/modules/delivery/services/delivery-api.ts
new file mode 100644
index 0000000..b7e2b1f
--- /dev/null
+++ b/frontend/modules/delivery/services/delivery-api.ts
@@ -0,0 +1,247 @@
+/**
+ * Delivery Platform API client — Torbat Driver (real backend only).
+ * Browser → /api/delivery BFF; SSR → direct upstream :8007
+ */
+
+import { getValidAccessToken, refreshSession } from "@/lib/auth";
+import type {
+ CapabilitiesResponse,
+ DriverListResponse,
+ HealthResponse,
+ ListParams,
+ MetricsResponse,
+ PaginatedList,
+ PermissionsCatalogResponse,
+} from "@/modules/delivery/types";
+
+const DELIVERY_BASE =
+ typeof window !== "undefined"
+ ? "/api/delivery"
+ : process.env.DELIVERY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_DELIVERY_API_URL ||
+ "http://localhost:8007";
+
+export class DeliveryApiError extends Error {
+ constructor(
+ public status: number,
+ public code: string,
+ message: string
+ ) {
+ super(message);
+ this.name = "DeliveryApiError";
+ }
+}
+
+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
+ : `${DELIVERY_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 DeliveryApiError(
+ 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 crudResource(basePath: string) {
+ return {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`${basePath}${qs(params)}`, { tenantId }),
+ get: (tenantId: string, id: string) =>
+ request>(`${basePath}/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(basePath, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`${basePath}/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ remove: (tenantId: string, id: string) =>
+ request>(`${basePath}/${id}/delete`, {
+ tenantId,
+ method: "POST",
+ }),
+ };
+}
+
+export const deliveryApi = {
+ health: (): Promise =>
+ fetch(`${DELIVERY_BASE}/health`).then((r) => r.json()),
+
+ capabilities: (): Promise =>
+ fetch(`${DELIVERY_BASE}/capabilities`).then((r) => r.json()),
+
+ metrics: (): Promise =>
+ fetch(`${DELIVERY_BASE}/metrics`).then((r) => r.json()),
+
+ permissions: {
+ catalog: (tenantId: string) =>
+ request(`/api/v1/permissions/catalog`, { tenantId }),
+ },
+
+ organizations: crudResource("/api/v1/organizations"),
+ hubs: crudResource("/api/v1/hubs"),
+ externalProviders: crudResource("/api/v1/external-providers"),
+ routingEngines: crudResource("/api/v1/routing-engines"),
+ configurations: crudResource("/api/v1/configurations"),
+
+ settings: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(`/api/v1/settings${qs(params)}`, { tenantId }),
+ upsert: (tenantId: string, body: Record) =>
+ request>(`/api/v1/settings`, {
+ tenantId,
+ method: "PUT",
+ body: JSON.stringify(body),
+ }),
+ },
+
+ audit: {
+ list: (
+ tenantId: string,
+ params: { entity_type: string; entity_id: string; limit?: number }
+ ) => request[]>(`/api/v1/audit${qs(params)}`, { tenantId }),
+ },
+
+ drivers: {
+ list: (tenantId: string, params?: ListParams) =>
+ request[]>(
+ `/api/v1/drivers${qs(params)}`,
+ { tenantId }
+ ).then(normalizeDriverList),
+ get: (tenantId: string, id: string) =>
+ request>(`/api/v1/drivers/${id}`, { tenantId }),
+ create: (tenantId: string, body: Record) =>
+ request>(`/api/v1/drivers`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ update: (tenantId: string, id: string, body: Record) =>
+ request>(`/api/v1/drivers/${id}`, {
+ tenantId,
+ method: "PATCH",
+ body: JSON.stringify(body),
+ }),
+ remove: (tenantId: string, id: string) =>
+ request>(`/api/v1/drivers/${id}/delete`, {
+ tenantId,
+ method: "POST",
+ }),
+ activate: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "activate", body),
+ suspend: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "suspend", body),
+ resume: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "resume", body),
+ deactivate: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "deactivate", body),
+ block: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "block", body),
+ unblock: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "unblock", body),
+ archive: (tenantId: string, id: string, body: Record) =>
+ lifecycleAction(tenantId, id, "archive", body),
+ lifecycle: (tenantId: string, id: string) =>
+ request[]>(`/api/v1/drivers/${id}/lifecycle`, { tenantId }),
+ credentials: {
+ list: (tenantId: string, driverId: string) =>
+ request[]>(`/api/v1/drivers/${driverId}/credentials`, {
+ tenantId,
+ }),
+ create: (tenantId: string, driverId: string, body: Record) =>
+ request>(`/api/v1/drivers/${driverId}/credentials`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ documents: {
+ list: (tenantId: string, driverId: string) =>
+ request[]>(`/api/v1/drivers/${driverId}/documents`, { tenantId }),
+ create: (tenantId: string, driverId: string, body: Record) =>
+ request>(`/api/v1/drivers/${driverId}/documents`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ }),
+ },
+ },
+};
+
+function lifecycleAction(
+ tenantId: string,
+ id: string,
+ action: string,
+ body: Record
+) {
+ return request>(`/api/v1/drivers/${id}/${action}`, {
+ tenantId,
+ method: "POST",
+ body: JSON.stringify(body),
+ });
+}
+
+function normalizeDriverList(
+ data: DriverListResponse | Record[]
+): PaginatedList> {
+ if (Array.isArray(data)) {
+ return { items: data, total: data.length, page: 1, page_size: data.length };
+ }
+ return {
+ items: data.items ?? [],
+ total: data.total ?? 0,
+ page: data.page ?? 1,
+ page_size: data.page_size ?? 20,
+ };
+}
diff --git a/frontend/modules/delivery/types/index.ts b/frontend/modules/delivery/types/index.ts
new file mode 100644
index 0000000..fc8eaf8
--- /dev/null
+++ b/frontend/modules/delivery/types/index.ts
@@ -0,0 +1,54 @@
+export type HealthResponse = {
+ status: string;
+ service: string;
+ version: string;
+};
+
+export type CapabilitiesResponse = {
+ service: string;
+ version: string;
+ phase: string;
+ commercial_product: string;
+ features: Record;
+ independence?: Record;
+};
+
+export type MetricsResponse = {
+ service: string;
+ version: string;
+ phase: string;
+ metrics: Record;
+ note?: string;
+};
+
+export type PermissionsCatalogResponse = {
+ prefix: string;
+ prefixes: string[];
+ permissions: string[];
+ count: number;
+};
+
+export type ListParams = Record;
+
+export type PaginatedList = {
+ items: T[];
+ total: number;
+ page: number;
+ page_size: number;
+};
+
+export type DriverListResponse = {
+ items: Record[];
+ total: number;
+ page: number;
+ page_size: number;
+};
+
+export type DeliveryPortalId = "operations" | "admin";
+
+export type PhaseGateConfig = {
+ feature: string;
+ phase: string;
+ title: string;
+ description: string;
+};
diff --git a/frontend/scripts/scaffold-delivery-module.mjs b/frontend/scripts/scaffold-delivery-module.mjs
new file mode 100644
index 0000000..b1af106
--- /dev/null
+++ b/frontend/scripts/scaffold-delivery-module.mjs
@@ -0,0 +1,890 @@
+#!/usr/bin/env node
+/**
+ * Scaffolds Torbat Driver (Delivery) frontend module:
+ * - Feature pages from PAGE_REGISTRY
+ * - Thin app/delivery routes
+ * - BFF proxy
+ */
+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 MOD = path.join(ROOT, "modules", "delivery");
+const APP = path.join(ROOT, "app", "delivery");
+const FEATURES = path.join(MOD, "features");
+
+/** [routePath, exportName, featureFile] */
+const PAGE_REGISTRY = [
+ ["", "ExecutiveDashboard", "dashboard"],
+ ["hub", "DeliveryHub", "hub"],
+ ["dispatcher", "DispatcherDashboard", "dispatcher"],
+ ["operations", "LiveOperationsDashboard", "operations"],
+ ["fleet-dashboard", "FleetDashboard", "fleetDashboard"],
+ ["drivers", "DriversPage", "drivers"],
+ ["drivers/availability", "DriverAvailabilityPage", "driverAvailability"],
+ ["drivers/performance", "DriverPerformancePage", "driverPerformance"],
+ ["orders", "OrdersPage", "orders"],
+ ["dispatch/queue", "DispatchQueuePage", "dispatchQueue"],
+ ["dispatch/assignment", "AssignmentCenterPage", "assignment"],
+ ["tracking/live", "LiveTrackingPage", "liveTracking"],
+ ["tracking/customer", "CustomerTrackingPage", "customerTracking"],
+ ["maps/view", "MapViewPage", "mapView"],
+ ["maps/routes/planner", "RoutePlannerPage", "routePlanner"],
+ ["maps/routes/optimization", "RouteOptimizationPage", "routeOptimization"],
+ ["maps/geofence", "GeofencePage", "geofence"],
+ ["maps/zones", "DeliveryZonesPage", "deliveryZones"],
+ ["organizations", "OrganizationsPage", "organizations"],
+ ["branches", "BranchesPage", "branches"],
+ ["vehicles", "VehiclesPage", "vehicles"],
+ ["vehicle-types", "VehicleTypesPage", "vehicleTypes"],
+ ["fleet/maintenance", "FleetMaintenancePage", "maintenance"],
+ ["schedules", "SchedulesPage", "schedules"],
+ ["shifts", "ShiftsPage", "shifts"],
+ ["tasks", "TasksPage", "tasks"],
+ ["pickups", "PickupsPage", "pickups"],
+ ["deliveries", "DeliveriesPage", "deliveries"],
+ ["returns", "ReturnsPage", "returns"],
+ ["failed", "FailedDeliveriesPage", "failed"],
+ ["cod", "CodManagementPage", "cod"],
+ ["settlements", "SettlementsPage", "settlements"],
+ ["payments", "PaymentsPage", "payments"],
+ ["wallet", "DriverWalletPage", "wallet"],
+ ["commissions", "CommissionsPage", "commissions"],
+ ["integrations/providers", "ExternalProvidersPage", "externalProviders"],
+ ["integrations/routing", "RoutingEnginesPage", "routingEngines"],
+ ["vendor-portal", "VendorPortalPage", "vendorPortal"],
+ ["notifications", "NotificationsPage", "notifications"],
+ ["incidents", "IncidentsPage", "incidents"],
+ ["support", "SupportTicketsPage", "support"],
+ ["reports", "ReportsPage", "reports"],
+ ["analytics", "AnalyticsPage", "analytics"],
+ ["analytics/heatmaps", "HeatMapsPage", "heatmaps"],
+ ["analytics/kpis", "PerformanceKpisPage", "kpis"],
+ ["ai/recommendations", "AiRecommendationsPage", "aiRecommendations"],
+ ["automation", "AutomationPage", "automation"],
+ ["configurations", "ConfigurationsPage", "configurations"],
+ ["settings", "SettingsPage", "settings"],
+ ["permissions", "PermissionsPage", "permissions"],
+ ["roles", "RolesPage", "roles"],
+ ["audit", "AuditLogsPage", "audit"],
+ ["api-keys", "ApiKeysPage", "apiKeys"],
+ ["health", "HealthPage", "health"],
+ ["capabilities", "CapabilitiesPage", "capabilities"],
+];
+
+function ensureDir(d) {
+ fs.mkdirSync(d, { recursive: true });
+}
+
+function writeFeature(name, content) {
+ ensureDir(FEATURES);
+ fs.writeFileSync(path.join(FEATURES, `${name}.tsx`), content);
+}
+
+const listPage = (exportName, apiKey, title, perm, createFieldsExtra) => `"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const ${exportName} = createDeliveryListPage({
+ title: "${title}",
+ breadcrumb: "${title}",
+ resourceKey: "${apiKey}",
+ permission: "${perm}",
+ api: deliveryApi.${apiKey},
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: ${createFieldsExtra || `[
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ { name: "status", label: "وضعیت", type: "select", options: [
+ { value: "draft", label: "پیشنویس" },
+ { value: "active", label: "فعال" },
+ ]},
+ ]`},
+});
+
+export default ${exportName};
+`;
+
+const phasePage = (exportName, registryKey) => `"use client";
+
+import { createDeliveryPhasePage } from "@/modules/delivery/components/DeliveryPhaseGate";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ${exportName} = createDeliveryPhasePage(PHASE_REGISTRY.${registryKey});
+export default ${exportName};
+`;
+
+const mapPage = (exportName, registryKey, title) => `"use client";
+
+import { createDeliveryMapPage } from "@/modules/delivery/components/DeliveryMapPlaceholder";
+import { PHASE_REGISTRY } from "@/modules/delivery/constants/phaseRegistry";
+
+export const ${exportName} = createDeliveryMapPage(PHASE_REGISTRY.${registryKey}, "${title}");
+export default ${exportName};
+`;
+
+// Real API list pages
+writeFeature("organizations", listPage("OrganizationsPage", "organizations", "سازمانها", "delivery.organizations.view"));
+writeFeature("branches", `"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const BranchesPage = createDeliveryListPage({
+ title: "شعب و هابها",
+ breadcrumb: "شعب",
+ resourceKey: "hubs",
+ permission: "delivery.hubs.view",
+ api: deliveryApi.hubs,
+ columns: [
+ { key: "code", header: "کد" },
+ { key: "name", header: "نام" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ { name: "status", label: "وضعیت", type: "select", options: [
+ { value: "active", label: "فعال" },
+ { value: "draft", label: "پیشنویس" },
+ ]},
+ ],
+});
+
+export default BranchesPage;
+`);
+writeFeature("externalProviders", listPage("ExternalProvidersPage", "externalProviders", "ارائهدهندگان خارجی", "delivery.external_providers.view", `[
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ { name: "provider_kind", label: "نوع", type: "select", options: [
+ { value: "routing_engine", label: "مسیریابی" },
+ { value: "fleet", label: "ناوگان" },
+ { value: "courier", label: "پیک" },
+ { value: "maps", label: "نقشه" },
+ ]},
+ ]`));
+writeFeature("routingEngines", listPage("RoutingEnginesPage", "routingEngines", "موتورهای مسیریابی", "delivery.routing_engines.view", `[
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ { name: "name", label: "نام", required: true },
+ ]`));
+writeFeature("configurations", listPage("ConfigurationsPage", "configurations", "پیکربندی", "delivery.configurations.view", `[
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "code", label: "کد", required: true },
+ ]`));
+
+// Drivers - special list with detail link
+writeFeature("drivers", `"use client";
+
+import { createDeliveryListPage } from "@/modules/delivery/components/DeliveryListCrudPage";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+
+export const DriversPage = createDeliveryListPage({
+ title: "رانندگان",
+ breadcrumb: "رانندگان",
+ resourceKey: "drivers",
+ permission: "delivery.drivers.view",
+ api: {
+ list: async (tenantId, params) => {
+ const res = await deliveryApi.drivers.list(tenantId, params);
+ return Array.isArray(res) ? res : res.items;
+ },
+ get: deliveryApi.drivers.get,
+ create: deliveryApi.drivers.create,
+ update: deliveryApi.drivers.update,
+ remove: deliveryApi.drivers.remove,
+ },
+ columns: [
+ { key: "code", header: "کد", type: "link", linkPrefix: "/delivery/drivers" },
+ { key: "display_name", header: "نام" },
+ { key: "mobile", header: "موبایل" },
+ { key: "status", header: "وضعیت", type: "status" },
+ { key: "created_at", header: "ایجاد", type: "datetime" },
+ ],
+ createFields: [
+ { name: "organization_id", label: "سازمان", type: "organization", required: true },
+ { name: "hub_id", label: "هاب", type: "hub" },
+ { name: "code", label: "کد", required: true },
+ { name: "display_name", label: "نام نمایشی", required: true },
+ { name: "mobile", label: "موبایل" },
+ { name: "email", label: "ایمیل", type: "email" },
+ ],
+ detailHref: (row) => row.id ? \`/delivery/drivers/\${row.id}\` : null,
+});
+
+export default DriversPage;
+`);
+
+// Phase-gated pages
+const phasePages = [
+ ["dispatcher", "DispatcherDashboard", "dispatcher"],
+ ["operations", "LiveOperationsDashboard", "operations"],
+ ["fleetDashboard", "FleetDashboard", "fleetDashboard"],
+ ["driverAvailability", "DriverAvailabilityPage", "driverAvailability"],
+ ["driverPerformance", "DriverPerformancePage", "driverPerformance"],
+ ["orders", "OrdersPage", "orders"],
+ ["dispatchQueue", "DispatchQueuePage", "dispatchQueue"],
+ ["assignment", "AssignmentCenterPage", "assignment"],
+ ["liveTracking", "LiveTrackingPage", "liveTracking"],
+ ["customerTracking", "CustomerTrackingPage", "customerTracking"],
+ ["vehicles", "VehiclesPage", "vehicles"],
+ ["vehicleTypes", "VehicleTypesPage", "vehicleTypes"],
+ ["maintenance", "FleetMaintenancePage", "maintenance"],
+ ["schedules", "SchedulesPage", "schedules"],
+ ["shifts", "ShiftsPage", "shifts"],
+ ["tasks", "TasksPage", "tasks"],
+ ["pickups", "PickupsPage", "pickups"],
+ ["deliveries", "DeliveriesPage", "deliveries"],
+ ["returns", "ReturnsPage", "returns"],
+ ["failed", "FailedDeliveriesPage", "failed"],
+ ["cod", "CodManagementPage", "cod"],
+ ["settlements", "SettlementsPage", "settlements"],
+ ["payments", "PaymentsPage", "payments"],
+ ["wallet", "DriverWalletPage", "wallet"],
+ ["commissions", "CommissionsPage", "commissions"],
+ ["vendorPortal", "VendorPortalPage", "vendorPortal"],
+ ["notifications", "NotificationsPage", "notifications"],
+ ["incidents", "IncidentsPage", "incidents"],
+ ["support", "SupportTicketsPage", "support"],
+ ["reports", "ReportsPage", "reports"],
+ ["analytics", "AnalyticsPage", "analytics"],
+ ["heatmaps", "HeatMapsPage", "heatmaps"],
+ ["kpis", "PerformanceKpisPage", "kpis"],
+ ["aiRecommendations", "AiRecommendationsPage", "aiRecommendations"],
+ ["automation", "AutomationPage", "automation"],
+ ["roles", "RolesPage", "roles"],
+ ["apiKeys", "ApiKeysPage", "apiKeys"],
+];
+
+for (const [file, exportName, key] of phasePages) {
+ writeFeature(file, phasePage(exportName, key));
+}
+
+const mapPages = [
+ ["mapView", "MapViewPage", "mapView", "نمای نقشه"],
+ ["routePlanner", "RoutePlannerPage", "routePlanner", "برنامهریزی مسیر"],
+ ["routeOptimization", "RouteOptimizationPage", "routeOptimization", "بهینهسازی"],
+ ["geofence", "GeofencePage", "geofence", "ژئوفنس"],
+ ["deliveryZones", "DeliveryZonesPage", "deliveryZones", "مناطق تحویل"],
+];
+for (const [file, exportName, key, title] of mapPages) {
+ writeFeature(file, mapPage(exportName, key, title));
+}
+
+// Dashboard
+writeFeature("dashboard", `"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import Link from "next/link";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { useTenantId } from "@/hooks/useTenantId";
+import { useDeliveryCapabilities, useDeliveryMetrics } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, StatCard, Card, CardContent, Button, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const ExecutiveDashboard = function DeliveryExecutiveDashboard() {
+ const { tenantId } = useTenantId();
+ const caps = useDeliveryCapabilities();
+ const metrics = useDeliveryMetrics();
+
+ const countsQ = useQuery({
+ queryKey: ["delivery", tenantId, "dashboard-counts"],
+ queryFn: async () => {
+ const [orgs, hubs, drivers] = await Promise.all([
+ deliveryApi.organizations.list(tenantId!, { page: 1, page_size: 500 }),
+ deliveryApi.hubs.list(tenantId!, { page: 1, page_size: 500 }),
+ deliveryApi.drivers.list(tenantId!, { page: 1, page_size: 500 }),
+ ]);
+ const driverItems = Array.isArray(drivers) ? drivers : drivers.items;
+ return {
+ organizations: orgs.length,
+ hubs: hubs.length,
+ drivers: driverItems.length,
+ driversActive: driverItems.filter((d) => d.status === "active").length,
+ };
+ },
+ enabled: !!tenantId,
+ });
+
+ if (!tenantId || countsQ.isLoading || caps.isLoading) return ;
+ if (countsQ.error) return countsQ.refetch()} />;
+
+ const c = countsQ.data!;
+
+ return (
+
+
+
Torbat Driver v{caps.data?.version}}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {metrics.data ? (
+
+
+ فاز {metrics.data.phase} — {metrics.data.note}
+
+
+ ) : null}
+
+ );
+};
+
+export default ExecutiveDashboard;
+`);
+
+writeFeature("hub", `"use client";
+
+import Link from "next/link";
+import { useMe } from "@/hooks/useMe";
+import { DELIVERY_PORTAL } from "@/modules/delivery/constants/portals";
+import { Card, CardContent, Button, LoadingState, ErrorState } from "@/components/ds";
+
+export const DeliveryHub = function DeliveryHubPage() {
+ const { me, loading, error, reload } = useMe();
+ if (loading) return ;
+ if (error) return ;
+ if (!me?.current_tenant_id) {
+ return ;
+ }
+
+ return (
+
+
تربت درایور
+
پلتفرم لجستیک و پیک سازمانی
+
+
+ {DELIVERY_PORTAL.label}
+ {DELIVERY_PORTAL.description}
+
+
+
+
+
+
+ );
+};
+
+export default DeliveryHub;
+`);
+
+writeFeature("health", `"use client";
+
+import { useDeliveryHealth } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const HealthPage = function DeliveryHealthPage() {
+ const q = useDeliveryHealth();
+ if (q.isLoading) return ;
+ if (q.error) return q.refetch()} />;
+ return (
+
+
+
+
+ {q.data?.status}
+ {q.data?.service} — v{q.data?.version}
+
+
+ );
+};
+export default HealthPage;
+`);
+
+writeFeature("capabilities", `"use client";
+
+import { useDeliveryCapabilities } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const CapabilitiesPage = function DeliveryCapabilitiesPage() {
+ const q = useDeliveryCapabilities();
+ 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;
+`);
+
+writeFeature("permissions", `"use client";
+
+import { useQuery } from "@tanstack/react-query";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Badge } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const PermissionsPage = function DeliveryPermissionsPage() {
+ const { tenantId } = useTenantId();
+ const q = useQuery({
+ queryKey: ["delivery", tenantId, "permissions-catalog"],
+ queryFn: () => deliveryApi.permissions.catalog(tenantId!),
+ enabled: !!tenantId,
+ });
+ if (!tenantId || q.isLoading) return ;
+ if (q.error) return q.refetch()} />;
+ return (
+
+
+
+
+ {q.data?.permissions.map((p) => (
+ {p}
+ ))}
+
+
+ );
+};
+export default PermissionsPage;
+`);
+
+writeFeature("settings", `"use client";
+
+import { useState } from "react";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Button, Input, FormField } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import { useDeliveryLookups } from "@/modules/delivery/hooks/useDeliveryCapabilities";
+
+export const SettingsPage = function DeliverySettingsPage() {
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const lookups = useDeliveryLookups(tenantId);
+ const [key, setKey] = useState("");
+ const [value, setValue] = useState("");
+ const [orgId, setOrgId] = useState("");
+
+ const listQ = useQuery({
+ queryKey: ["delivery", tenantId, "settings"],
+ queryFn: () => deliveryApi.settings.list(tenantId!),
+ enabled: !!tenantId,
+ });
+
+ const saveM = useMutation({
+ mutationFn: () => deliveryApi.settings.upsert(tenantId!, {
+ organization_id: orgId || null,
+ key,
+ value: JSON.parse(value || "{}"),
+ }),
+ onSuccess: () => {
+ toast.success("تنظیمات ذخیره شد");
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "settings"] });
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || listQ.isLoading || lookups.isLoading) return ;
+ if (listQ.error) return listQ.refetch()} />;
+
+ return (
+
+
+
+
+
+
+
+
+ setKey(e.target.value)} />
+ setValue(e.target.value)} placeholder='{"enabled":true}' />
+
+
+
+
+ {(listQ.data ?? []).map((s) => (
+
+ {String(s.key)}
+ {JSON.stringify(s.value, null, 2)}
+
+ ))}
+
+
+ );
+};
+export default SettingsPage;
+`);
+
+writeFeature("audit", `"use client";
+
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError, DeliveryEmptyState } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Input, FormField, Button } from "@/components/ds";
+import { DeliveryBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+
+export const AuditLogsPage = function DeliveryAuditPage() {
+ const { tenantId } = useTenantId();
+ const [entityType, setEntityType] = useState("driver");
+ const [entityId, setEntityId] = useState("");
+ const [submitted, setSubmitted] = useState<{ type: string; id: string } | null>(null);
+
+ const q = useQuery({
+ queryKey: ["delivery", tenantId, "audit", submitted],
+ queryFn: () => deliveryApi.audit.list(tenantId!, {
+ entity_type: submitted!.type,
+ entity_id: submitted!.id,
+ }),
+ enabled: !!tenantId && !!submitted,
+ });
+
+ return (
+
+
+
+
+
+ setEntityType(e.target.value)} />
+ setEntityId(e.target.value)} />
+
+
+
+ {submitted && q.isLoading ?
: null}
+ {q.error ?
q.refetch()} /> : null}
+ {submitted && !q.isLoading && !q.error && (q.data?.length ?? 0) === 0 ? (
+
+ ) : null}
+
+ {(q.data ?? []).map((log) => (
+
+ {String(log.action)} — {new Date(String(log.created_at)).toLocaleString("fa-IR")}
+ {log.message ? {String(log.message)}
: null}
+
+ ))}
+
+
+ );
+};
+export default AuditLogsPage;
+`);
+
+// Driver detail - dynamic route feature
+writeFeature("driverDetail", `"use client";
+
+import { useParams } from "next/navigation";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { useTenantId } from "@/hooks/useTenantId";
+import { deliveryApi } from "@/modules/delivery/services/delivery-api";
+import { DeliveryPageLoader, DeliveryPageError } from "@/modules/delivery/pages/shared";
+import { PageHeader, Card, CardContent, Button, Badge, Tabs, ConfirmDialog } from "@/components/ds";
+import { DeliveryDetailBreadcrumbs } from "@/modules/delivery/components/DeliveryBreadcrumbs";
+import { DeliveryStatusChip } from "@/modules/delivery/design-system";
+import { useDeliveryPermissions } from "@/modules/delivery/hooks/useDeliveryPermissions";
+import { useState } from "react";
+
+export const DriverDetailPage = function DriverDetail() {
+ const params = useParams();
+ const driverId = String(params.id);
+ const { tenantId } = useTenantId();
+ const qc = useQueryClient();
+ const perms = useDeliveryPermissions();
+ const [tab, setTab] = useState("profile");
+ const [lifecycleAction, setLifecycleAction] = useState(null);
+
+ const driverQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver", driverId],
+ queryFn: () => deliveryApi.drivers.get(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId,
+ });
+
+ const lifecycleQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-lifecycle", driverId],
+ queryFn: () => deliveryApi.drivers.lifecycle(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "lifecycle",
+ });
+
+ const credsQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-creds", driverId],
+ queryFn: () => deliveryApi.drivers.credentials.list(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "credentials",
+ });
+
+ const docsQ = useQuery({
+ queryKey: ["delivery", tenantId, "driver-docs", driverId],
+ queryFn: () => deliveryApi.drivers.documents.list(tenantId!, driverId),
+ enabled: !!tenantId && !!driverId && tab === "documents",
+ });
+
+ const lifecycleM = useMutation({
+ mutationFn: (action: string) => {
+ const api = deliveryApi.drivers as Record;
+ const fn = api[action];
+ return fn(tenantId!, driverId, { version: driverQ.data!.version });
+ },
+ onSuccess: () => {
+ toast.success("وضعیت بهروز شد");
+ setLifecycleAction(null);
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver", driverId] });
+ qc.invalidateQueries({ queryKey: ["delivery", tenantId, "driver-lifecycle", driverId] });
+ },
+ onError: (e: Error) => toast.error(e.message),
+ });
+
+ if (!tenantId || driverQ.isLoading) return ;
+ if (driverQ.error) return driverQ.refetch()} />;
+
+ const d = driverQ.data!;
+ const lifecycleActions = ["activate", "suspend", "resume", "deactivate", "block", "unblock", "archive"];
+
+ return (
+
+
+
}
+ />
+
+ {tab === "profile" ? (
+
+ ایمیل: {String(d.email ?? "—")}
+ نسخه: {String(d.version)}
+ ایجاد: {new Date(String(d.created_at)).toLocaleString("fa-IR")}
+ {d.notes ? {String(d.notes)}
: null}
+
+ ) : null}
+ {tab === "lifecycle" ? (
+
+
+ {lifecycleActions.map((a) => (
+
+ ))}
+
+ {(lifecycleQ.data ?? []).map((ev) => (
+
+ {String(ev.action)}: {String(ev.from_status)} → {String(ev.to_status)}
+ {new Date(String(ev.created_at)).toLocaleString("fa-IR")}
+
+ ))}
+
+ ) : null}
+ {tab === "credentials" ? (
+
+ {(credsQ.data ?? []).map((c) => (
+
+ {String(c.kind)} — {String(c.number)}
+ {c.is_verified ? تأیید شده : null}
+
+ ))}
+
+ ) : null}
+ {tab === "documents" ? (
+
+ {(docsQ.data ?? []).map((doc) => (
+
+ {String(doc.title)} — {String(doc.kind)}
+
+ ))}
+
+ ) : null}
+
setLifecycleAction(null)}
+ title="تغییر وضعیت"
+ description={\`آیا از اجرای \${lifecycleAction} اطمینان دارید؟\`}
+ onConfirm={() => lifecycleAction && lifecycleM.mutate(lifecycleAction)}
+ loading={lifecycleM.isPending}
+ />
+
+ );
+};
+
+export default DriverDetailPage;
+`);
+
+// Generate routes
+for (const [routePath, exportName, featureFile] of PAGE_REGISTRY) {
+ const dir = path.join(APP, routePath);
+ ensureDir(dir);
+ fs.writeFileSync(
+ path.join(dir, "page.tsx"),
+ `"use client";\n\nexport { ${exportName} as default } from "@/modules/delivery/features/${featureFile}";\n`
+ );
+ fs.writeFileSync(
+ path.join(dir, "loading.tsx"),
+ `import { LoadingState } from "@/components/ds";\n\nexport default function Loading() {\n return ;\n}\n`
+ );
+ fs.writeFileSync(
+ path.join(dir, "error.tsx"),
+ `"use client";\n\nimport { ErrorState } from "@/components/ds";\n\nexport default function Error({ error, reset }: { error: Error; reset: () => void }) {\n return ;\n}\n`
+ );
+}
+
+// Driver dynamic route
+const driverDetailDir = path.join(APP, "drivers", "[id]");
+ensureDir(driverDetailDir);
+fs.writeFileSync(
+ path.join(driverDetailDir, "page.tsx"),
+ `"use client";\n\nexport { DriverDetailPage as default } from "@/modules/delivery/features/driverDetail";\n`
+);
+fs.writeFileSync(path.join(driverDetailDir, "loading.tsx"), `import { LoadingState } from "@/components/ds";\nexport default function Loading() { return ; }\n`);
+fs.writeFileSync(path.join(driverDetailDir, "error.tsx"), `"use client";\nimport { ErrorState } from "@/components/ds";\nexport default function Error({ error, reset }: { error: Error; reset: () => void }) { return ; }\n`);
+
+// Root layout
+fs.writeFileSync(
+ path.join(APP, "layout.tsx"),
+ `"use client";\n\nimport { DeliveryRootLayout } from "@/modules/delivery/components/createPortalLayout";\n\nexport default DeliveryRootLayout;\n`
+);
+
+fs.writeFileSync(
+ path.join(APP, "not-found.tsx"),
+ `import Link from "next/link";\nimport { Button, EmptyState } from "@/components/ds";\n\nexport default function DeliveryNotFound() {\n return (\n \n } />\n
\n );\n}\n`
+);
+
+// BFF
+const bffDir = path.join(ROOT, "app", "api", "delivery", "[...path]");
+ensureDir(bffDir);
+fs.writeFileSync(
+ path.join(bffDir, "route.ts"),
+ `import { NextRequest, NextResponse } from "next/server";
+
+const UPSTREAM =
+ process.env.DELIVERY_SERVICE_URL ||
+ process.env.NEXT_PUBLIC_DELIVERY_API_URL ||
+ "http://localhost:8007";
+
+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: "delivery_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);
+}
+`
+);
+
+// Module index
+fs.writeFileSync(
+ path.join(MOD, "index.ts"),
+ `export { deliveryApi, DeliveryApiError } from "./services/delivery-api";
+export { DELIVERY_PORTAL, navForDelivery } from "./constants/portals";
+export { DeliveryPortalShell } from "./components/DeliveryPortalShell";
+`
+);
+
+fs.writeFileSync(
+ path.join(MOD, "README.md"),
+ `# Torbat Driver — Delivery Module
+
+Business UI for \`/delivery/*\`. Routes are thin re-exports in \`app/delivery/\`.
+
+## Structure
+
+- \`features/\` — page implementations
+- \`components/\` — portal shell, CRUD factory, phase gates
+- \`services/delivery-api.ts\` — real backend client (BFF \`/api/delivery\`)
+- \`constants/\` — nav, permissions, phase registry
+
+## Scaffold
+
+\`\`\`bash
+node scripts/scaffold-delivery-module.mjs
+\`\`\`
+`
+);
+
+console.log(`Scaffolded ${PAGE_REGISTRY.length + 1} delivery routes and features.`);
diff --git a/frontend/scripts/validate-architecture.mjs b/frontend/scripts/validate-architecture.mjs
index 836cfe7..5e7ce8a 100644
--- a/frontend/scripts/validate-architecture.mjs
+++ b/frontend/scripts/validate-architecture.mjs
@@ -41,6 +41,7 @@ const DOMAIN_PATH_MARKERS = {
beauty: "modules/beauty",
healthcare: "src/modules/healthcare",
hospitality: "modules/hospitality",
+ delivery: "modules/delivery",
admin: "components/admin",
};
@@ -92,9 +93,25 @@ const CROSS_DOMAIN_RULES = [
"@/components/healthcare/",
"@/modules/beauty/",
"@/modules/accounting/",
+ "@/modules/delivery/",
"@/src/modules/healthcare/",
],
},
+ {
+ name: "delivery → other domains",
+ fileTest: (rel) =>
+ rel.startsWith("app/delivery/") || rel.startsWith("modules/delivery/"),
+ forbidden: [
+ "@/components/accounting/",
+ "@/components/beauty/",
+ "@/components/healthcare/",
+ "@/modules/beauty/",
+ "@/modules/accounting/",
+ "@/modules/hospitality/",
+ "@/src/modules/healthcare/",
+ "@/src/modules/accounting/",
+ ],
+ },
{
name: "design system → domains",
fileTest: (rel) => rel.startsWith("components/ds/"),
diff --git a/frontend/scripts/validate-delivery-routes.mjs b/frontend/scripts/validate-delivery-routes.mjs
new file mode 100644
index 0000000..f3186e0
--- /dev/null
+++ b/frontend/scripts/validate-delivery-routes.mjs
@@ -0,0 +1,35 @@
+#!/usr/bin/env node
+/** Validates app/delivery routes are thin re-exports (≤20 lines). */
+import { readFileSync, readdirSync, statSync } from "node:fs";
+import { join, relative } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
+const APP = join(ROOT, "app", "delivery");
+const MAX_LINES = 25;
+
+function walk(dir, files = []) {
+ for (const e of readdirSync(dir)) {
+ const full = join(dir, e);
+ if (statSync(full).isDirectory()) walk(full, files);
+ else if (e === "page.tsx") files.push(full);
+ }
+ return files;
+}
+
+const violations = [];
+for (const file of walk(APP)) {
+ const content = readFileSync(file, "utf8");
+ const lines = content.split("\n").length;
+ const rel = relative(ROOT, file);
+ if (lines > MAX_LINES) violations.push(`${rel}: ${lines} lines`);
+ if (!content.includes("@/modules/delivery/")) {
+ violations.push(`${rel}: must re-export from @/modules/delivery/`);
+ }
+}
+
+if (violations.length) {
+ console.error("Delivery route validation failed:\n" + violations.join("\n"));
+ process.exit(1);
+}
+console.log("Delivery routes OK.");