TorbatYar/frontend/scripts/generate-crm-routes.mjs
Mortezakoohjani e140908034 Ship enterprise CRM frontend with real API wiring and thin app routes.
Connect all CRM pages to the BFF and CRM service, add module docs, and mark CRM available in the SuperApp catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 20:58:17 +03:30

118 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Generates CRM app routes (thin page.tsx re-exports) and stub feature files if missing.
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "..");
const APP = path.join(ROOT, "app", "crm");
const FEATURES = path.join(ROOT, "modules", "crm", "features");
const ROUTES = [
["hub", "pages/hub", "CrmHub", "pages/hub"],
["sales/dashboard/overview", "sales/dashboards", "OverviewDashboard", "sales/dashboards"],
["sales/dashboard/sales", "sales/dashboards", "SalesDashboard", "sales/dashboards"],
["sales/dashboard/pipeline", "sales/dashboards", "PipelineDashboard", "sales/dashboards"],
["sales/dashboard/leads", "sales/dashboards", "LeadDashboard", "sales/dashboards"],
["sales/dashboard/activities", "sales/dashboards", "ActivityDashboard", "sales/dashboards"],
["sales/dashboard/calendar", "sales/dashboards", "CalendarDashboard", "sales/dashboards"],
["sales/customers", "sales/customers", "default", "sales/customers"],
["sales/companies", "sales/companies", "default", "sales/companies"],
["sales/contacts", "sales/contacts", "default", "sales/contacts"],
["sales/leads", "sales/leads", "default", "sales/leads"],
["sales/deals", "sales/deals", "default", "sales/deals"],
["sales/kanban", "sales/kanban", "default", "sales/kanban"],
["sales/pipelines", "sales/pipelines", "default", "sales/pipelines"],
["sales/quotes", "sales/quotes", "default", "sales/quotes"],
["sales/activities", "sales/activities", "default", "sales/activities"],
["sales/tasks", "sales/tasks", "default", "sales/tasks"],
["sales/meetings", "sales/meetings", "default", "sales/meetings"],
["sales/calls", "sales/calls", "default", "sales/calls"],
["sales/emails", "sales/emails", "default", "sales/emails"],
["sales/notes", "sales/notes", "default", "sales/notes"],
["sales/timeline", "sales/timeline", "default", "sales/timeline"],
["sales/documents", "sales/documents", "default", "sales/documents"],
["sales/campaigns", "sales/scoped", "CampaignsPage", "sales/scoped"],
["sales/segments", "sales/scoped", "SegmentsPage", "sales/scoped"],
["sales/tags", "sales/tags", "default", "sales/tags"],
["sales/custom-fields", "sales/custom-fields", "default", "sales/custom-fields"],
["sales/import", "sales/scoped", "ImportPage", "sales/scoped"],
["sales/export", "sales/export", "default", "sales/export"],
["sales/reports", "sales/reports", "default", "sales/reports"],
["sales/analytics", "sales/analytics", "default", "sales/analytics"],
["sales/automation", "sales/scoped", "AutomationPage", "sales/scoped"],
["sales/notifications", "sales/admin", "NotificationsPage", "sales/admin"],
["sales/roles", "sales/admin", "RolesPage", "sales/admin"],
["sales/permissions", "sales/admin", "PermissionsPage", "sales/admin"],
["sales/integrations", "sales/scoped", "IntegrationsPage", "sales/scoped"],
["sales/api-keys", "sales/scoped", "ApiKeysPage", "sales/scoped"],
["sales/audit-logs", "sales/admin", "AuditLogsPage", "sales/admin"],
["sales/settings", "sales/admin", "SettingsPage", "sales/admin"],
["sales/health", "sales/admin", "HealthPage", "sales/admin"],
["sales/capabilities", "sales/admin", "CapabilitiesPage", "sales/admin"],
];
function ensureDir(dir) {
fs.mkdirSync(dir, { recursive: true });
}
function writePage(routePath, modulePath, exportName) {
const dir = path.join(APP, routePath);
ensureDir(dir);
const pageContent =
exportName === "default"
? `"use client";\n\nexport { default } from "@/modules/crm/features/${modulePath}";\n`
: `"use client";\n\nexport { ${exportName} as default } from "@/modules/crm/features/${modulePath}";\n`;
fs.writeFileSync(path.join(dir, "page.tsx"), pageContent);
for (const name of ["loading.tsx", "error.tsx"]) {
const file = path.join(dir, name);
if (!fs.existsSync(file)) {
if (name === "loading.tsx") {
fs.writeFileSync(
file,
`import { LoadingState } from "@/components/ds";\n\nexport default function Loading() {\n return <LoadingState label="در حال بارگذاری CRM" />;\n}\n`
);
} else {
fs.writeFileSync(
file,
`"use client";\n\nimport { ErrorState } from "@/components/ds";\n\nexport default function Error({ error, reset }: { error: Error; reset: () => void }) {\n return <ErrorState message={error.message} onRetry={reset} />;\n}\n`
);
}
}
}
}
// Root layouts
ensureDir(path.join(APP));
if (!fs.existsSync(path.join(APP, "layout.tsx"))) {
fs.writeFileSync(
path.join(APP, "layout.tsx"),
`/** CRM root — sub-routes supply portal shells. */\nexport default function CrmRootLayout({ children }: { children: React.ReactNode }) {\n return children;\n}\n`
);
}
if (!fs.existsSync(path.join(APP, "not-found.tsx"))) {
fs.writeFileSync(
path.join(APP, "not-found.tsx"),
`import Link from "next/link";\nimport { Button, EmptyState } from "@/components/ds";\n\nexport default function CrmNotFound() {\n return (\n <div className="flex min-h-[50vh] items-center justify-center p-6">\n <EmptyState\n title="صفحه CRM یافت نشد"\n action={<Link href="/crm/hub"><Button>بازگشت به CRM</Button></Link>}\n />\n </div>\n );\n}\n`
);
}
ensureDir(path.join(APP, "sales"));
if (!fs.existsSync(path.join(APP, "sales", "layout.tsx"))) {
fs.writeFileSync(
path.join(APP, "sales", "layout.tsx"),
`"use client";\n\nimport { createPortalLayout } from "@/modules/crm/components/createPortalLayout";\n\nexport default createPortalLayout("sales");\n`
);
}
for (const [route, mod, exp] of ROUTES) {
writePage(route, mod, exp);
}
console.log(`Generated ${ROUTES.length} CRM routes under app/crm/`);