#!/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.`);