From e140908034137e47f3c0fdf8378ed4dba79b5166 Mon Sep 17 00:00:00 2001 From: Mortezakoohjani Date: Sun, 26 Jul 2026 20:58:17 +0330 Subject: [PATCH] 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 --- frontend/.eslintrc.json | 125 +++ frontend/app/api/crm/[...path]/route.ts | 96 +++ frontend/app/crm/hub/error.tsx | 7 + frontend/app/crm/hub/loading.tsx | 5 + frontend/app/crm/hub/page.tsx | 3 + frontend/app/crm/layout.tsx | 4 + frontend/app/crm/not-found.tsx | 13 + frontend/app/crm/sales/activities/error.tsx | 7 + frontend/app/crm/sales/activities/loading.tsx | 5 + frontend/app/crm/sales/activities/page.tsx | 3 + frontend/app/crm/sales/analytics/error.tsx | 7 + frontend/app/crm/sales/analytics/loading.tsx | 5 + frontend/app/crm/sales/analytics/page.tsx | 3 + frontend/app/crm/sales/api-keys/error.tsx | 7 + frontend/app/crm/sales/api-keys/loading.tsx | 5 + frontend/app/crm/sales/api-keys/page.tsx | 3 + frontend/app/crm/sales/audit-logs/error.tsx | 7 + frontend/app/crm/sales/audit-logs/loading.tsx | 5 + frontend/app/crm/sales/audit-logs/page.tsx | 3 + frontend/app/crm/sales/automation/error.tsx | 7 + frontend/app/crm/sales/automation/loading.tsx | 5 + frontend/app/crm/sales/automation/page.tsx | 3 + frontend/app/crm/sales/calls/error.tsx | 7 + frontend/app/crm/sales/calls/loading.tsx | 5 + frontend/app/crm/sales/calls/page.tsx | 3 + frontend/app/crm/sales/campaigns/error.tsx | 7 + frontend/app/crm/sales/campaigns/loading.tsx | 5 + frontend/app/crm/sales/campaigns/page.tsx | 3 + frontend/app/crm/sales/capabilities/error.tsx | 7 + .../app/crm/sales/capabilities/loading.tsx | 5 + frontend/app/crm/sales/capabilities/page.tsx | 3 + frontend/app/crm/sales/companies/error.tsx | 7 + frontend/app/crm/sales/companies/loading.tsx | 5 + frontend/app/crm/sales/companies/page.tsx | 3 + frontend/app/crm/sales/contacts/error.tsx | 7 + frontend/app/crm/sales/contacts/loading.tsx | 5 + frontend/app/crm/sales/contacts/page.tsx | 3 + .../app/crm/sales/custom-fields/error.tsx | 7 + .../app/crm/sales/custom-fields/loading.tsx | 5 + frontend/app/crm/sales/custom-fields/page.tsx | 3 + frontend/app/crm/sales/customers/error.tsx | 7 + frontend/app/crm/sales/customers/loading.tsx | 5 + frontend/app/crm/sales/customers/page.tsx | 3 + .../crm/sales/dashboard/activities/error.tsx | 7 + .../sales/dashboard/activities/loading.tsx | 5 + .../crm/sales/dashboard/activities/page.tsx | 3 + .../crm/sales/dashboard/calendar/error.tsx | 7 + .../crm/sales/dashboard/calendar/loading.tsx | 5 + .../app/crm/sales/dashboard/calendar/page.tsx | 3 + .../app/crm/sales/dashboard/leads/error.tsx | 7 + .../app/crm/sales/dashboard/leads/loading.tsx | 5 + .../app/crm/sales/dashboard/leads/page.tsx | 3 + .../crm/sales/dashboard/overview/error.tsx | 7 + .../crm/sales/dashboard/overview/loading.tsx | 5 + .../app/crm/sales/dashboard/overview/page.tsx | 3 + .../crm/sales/dashboard/pipeline/error.tsx | 7 + .../crm/sales/dashboard/pipeline/loading.tsx | 5 + .../app/crm/sales/dashboard/pipeline/page.tsx | 3 + .../app/crm/sales/dashboard/sales/error.tsx | 7 + .../app/crm/sales/dashboard/sales/loading.tsx | 5 + .../app/crm/sales/dashboard/sales/page.tsx | 3 + frontend/app/crm/sales/deals/error.tsx | 7 + frontend/app/crm/sales/deals/loading.tsx | 5 + frontend/app/crm/sales/deals/page.tsx | 3 + frontend/app/crm/sales/documents/error.tsx | 7 + frontend/app/crm/sales/documents/loading.tsx | 5 + frontend/app/crm/sales/documents/page.tsx | 3 + frontend/app/crm/sales/emails/error.tsx | 7 + frontend/app/crm/sales/emails/loading.tsx | 5 + frontend/app/crm/sales/emails/page.tsx | 3 + frontend/app/crm/sales/export/error.tsx | 7 + frontend/app/crm/sales/export/loading.tsx | 5 + frontend/app/crm/sales/export/page.tsx | 3 + frontend/app/crm/sales/health/error.tsx | 7 + frontend/app/crm/sales/health/loading.tsx | 5 + frontend/app/crm/sales/health/page.tsx | 3 + frontend/app/crm/sales/import/error.tsx | 7 + frontend/app/crm/sales/import/loading.tsx | 5 + frontend/app/crm/sales/import/page.tsx | 3 + frontend/app/crm/sales/integrations/error.tsx | 7 + .../app/crm/sales/integrations/loading.tsx | 5 + frontend/app/crm/sales/integrations/page.tsx | 3 + frontend/app/crm/sales/kanban/error.tsx | 7 + frontend/app/crm/sales/kanban/loading.tsx | 5 + frontend/app/crm/sales/kanban/page.tsx | 3 + frontend/app/crm/sales/layout.tsx | 5 + frontend/app/crm/sales/leads/error.tsx | 7 + frontend/app/crm/sales/leads/loading.tsx | 5 + frontend/app/crm/sales/leads/page.tsx | 3 + frontend/app/crm/sales/meetings/error.tsx | 7 + frontend/app/crm/sales/meetings/loading.tsx | 5 + frontend/app/crm/sales/meetings/page.tsx | 3 + frontend/app/crm/sales/notes/error.tsx | 7 + frontend/app/crm/sales/notes/loading.tsx | 5 + frontend/app/crm/sales/notes/page.tsx | 3 + .../app/crm/sales/notifications/error.tsx | 7 + .../app/crm/sales/notifications/loading.tsx | 5 + frontend/app/crm/sales/notifications/page.tsx | 3 + frontend/app/crm/sales/permissions/error.tsx | 7 + .../app/crm/sales/permissions/loading.tsx | 5 + frontend/app/crm/sales/permissions/page.tsx | 3 + frontend/app/crm/sales/pipelines/error.tsx | 7 + frontend/app/crm/sales/pipelines/loading.tsx | 5 + frontend/app/crm/sales/pipelines/page.tsx | 3 + frontend/app/crm/sales/quotes/error.tsx | 7 + frontend/app/crm/sales/quotes/loading.tsx | 5 + frontend/app/crm/sales/quotes/page.tsx | 3 + frontend/app/crm/sales/reports/error.tsx | 7 + frontend/app/crm/sales/reports/loading.tsx | 5 + frontend/app/crm/sales/reports/page.tsx | 3 + frontend/app/crm/sales/roles/error.tsx | 7 + frontend/app/crm/sales/roles/loading.tsx | 5 + frontend/app/crm/sales/roles/page.tsx | 3 + frontend/app/crm/sales/segments/error.tsx | 7 + frontend/app/crm/sales/segments/loading.tsx | 5 + frontend/app/crm/sales/segments/page.tsx | 3 + frontend/app/crm/sales/settings/error.tsx | 7 + frontend/app/crm/sales/settings/loading.tsx | 5 + frontend/app/crm/sales/settings/page.tsx | 3 + frontend/app/crm/sales/tags/error.tsx | 7 + frontend/app/crm/sales/tags/loading.tsx | 5 + frontend/app/crm/sales/tags/page.tsx | 3 + frontend/app/crm/sales/tasks/error.tsx | 7 + frontend/app/crm/sales/tasks/loading.tsx | 5 + frontend/app/crm/sales/tasks/page.tsx | 3 + frontend/app/crm/sales/timeline/error.tsx | 7 + frontend/app/crm/sales/timeline/loading.tsx | 5 + frontend/app/crm/sales/timeline/page.tsx | 3 + frontend/docs/crm-component-map.md | 68 ++ frontend/docs/crm-responsive-report.md | 60 ++ frontend/docs/crm-routing-map.md | 80 ++ frontend/docs/crm-ui-specification.md | 91 +++ frontend/docs/crm-validation-report.md | 86 ++ frontend/lib/apps-catalog.ts | 30 +- .../modules/crm/components/CrmBreadcrumbs.tsx | 15 + .../crm/components/CrmListCrudPage.tsx | 331 ++++++++ .../modules/crm/components/CrmPortalShell.tsx | 172 ++++ .../modules/crm/components/CrmScopeNotice.tsx | 46 ++ .../crm/components/createPortalLayout.tsx | 45 ++ frontend/modules/crm/constants/permissions.ts | 123 +++ frontend/modules/crm/constants/portals.ts | 173 +++++ .../crm/design-system/CrmStatusChip.tsx | 58 ++ .../crm/design-system/CrmTablePage.tsx | 216 ++++++ frontend/modules/crm/design-system/index.ts | 3 + frontend/modules/crm/design-system/tokens.ts | 52 ++ .../modules/crm/features/sales/activities.tsx | 73 ++ frontend/modules/crm/features/sales/admin.tsx | 296 +++++++ .../modules/crm/features/sales/analytics.tsx | 29 + frontend/modules/crm/features/sales/calls.tsx | 52 ++ .../modules/crm/features/sales/companies.tsx | 83 ++ .../modules/crm/features/sales/contacts.tsx | 77 ++ .../crm/features/sales/custom-fields.tsx | 121 +++ .../modules/crm/features/sales/customers.tsx | 87 +++ .../modules/crm/features/sales/dashboards.tsx | 200 +++++ frontend/modules/crm/features/sales/deals.tsx | 311 ++++++++ .../modules/crm/features/sales/documents.tsx | 119 +++ .../modules/crm/features/sales/emails.tsx | 85 ++ .../modules/crm/features/sales/export.tsx | 92 +++ .../modules/crm/features/sales/kanban.tsx | 131 ++++ frontend/modules/crm/features/sales/leads.tsx | 95 +++ .../modules/crm/features/sales/meetings.tsx | 36 + frontend/modules/crm/features/sales/notes.tsx | 106 +++ .../modules/crm/features/sales/pipelines.tsx | 40 + .../modules/crm/features/sales/quotes.tsx | 56 ++ .../modules/crm/features/sales/reports.tsx | 65 ++ .../modules/crm/features/sales/scoped.tsx | 93 +++ frontend/modules/crm/features/sales/tags.tsx | 52 ++ frontend/modules/crm/features/sales/tasks.tsx | 75 ++ .../modules/crm/features/sales/timeline.tsx | 37 + .../crm/hooks/useCrmKeyboardShortcuts.ts | 43 + frontend/modules/crm/index.ts | 2 + frontend/modules/crm/pages/hub.tsx | 60 ++ frontend/modules/crm/pages/shared.tsx | 124 +++ frontend/modules/crm/services/crm-api.ts | 734 ++++++++++++++++++ frontend/modules/crm/types/index.ts | 428 ++++++++++ frontend/scripts/generate-crm-routes.mjs | 117 +++ 176 files changed, 6425 insertions(+), 1 deletion(-) create mode 100644 frontend/.eslintrc.json create mode 100644 frontend/app/api/crm/[...path]/route.ts create mode 100644 frontend/app/crm/hub/error.tsx create mode 100644 frontend/app/crm/hub/loading.tsx create mode 100644 frontend/app/crm/hub/page.tsx create mode 100644 frontend/app/crm/layout.tsx create mode 100644 frontend/app/crm/not-found.tsx create mode 100644 frontend/app/crm/sales/activities/error.tsx create mode 100644 frontend/app/crm/sales/activities/loading.tsx create mode 100644 frontend/app/crm/sales/activities/page.tsx create mode 100644 frontend/app/crm/sales/analytics/error.tsx create mode 100644 frontend/app/crm/sales/analytics/loading.tsx create mode 100644 frontend/app/crm/sales/analytics/page.tsx create mode 100644 frontend/app/crm/sales/api-keys/error.tsx create mode 100644 frontend/app/crm/sales/api-keys/loading.tsx create mode 100644 frontend/app/crm/sales/api-keys/page.tsx create mode 100644 frontend/app/crm/sales/audit-logs/error.tsx create mode 100644 frontend/app/crm/sales/audit-logs/loading.tsx create mode 100644 frontend/app/crm/sales/audit-logs/page.tsx create mode 100644 frontend/app/crm/sales/automation/error.tsx create mode 100644 frontend/app/crm/sales/automation/loading.tsx create mode 100644 frontend/app/crm/sales/automation/page.tsx create mode 100644 frontend/app/crm/sales/calls/error.tsx create mode 100644 frontend/app/crm/sales/calls/loading.tsx create mode 100644 frontend/app/crm/sales/calls/page.tsx create mode 100644 frontend/app/crm/sales/campaigns/error.tsx create mode 100644 frontend/app/crm/sales/campaigns/loading.tsx create mode 100644 frontend/app/crm/sales/campaigns/page.tsx create mode 100644 frontend/app/crm/sales/capabilities/error.tsx create mode 100644 frontend/app/crm/sales/capabilities/loading.tsx create mode 100644 frontend/app/crm/sales/capabilities/page.tsx create mode 100644 frontend/app/crm/sales/companies/error.tsx create mode 100644 frontend/app/crm/sales/companies/loading.tsx create mode 100644 frontend/app/crm/sales/companies/page.tsx create mode 100644 frontend/app/crm/sales/contacts/error.tsx create mode 100644 frontend/app/crm/sales/contacts/loading.tsx create mode 100644 frontend/app/crm/sales/contacts/page.tsx create mode 100644 frontend/app/crm/sales/custom-fields/error.tsx create mode 100644 frontend/app/crm/sales/custom-fields/loading.tsx create mode 100644 frontend/app/crm/sales/custom-fields/page.tsx create mode 100644 frontend/app/crm/sales/customers/error.tsx create mode 100644 frontend/app/crm/sales/customers/loading.tsx create mode 100644 frontend/app/crm/sales/customers/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/activities/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/activities/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/activities/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/calendar/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/calendar/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/calendar/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/leads/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/leads/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/leads/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/overview/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/overview/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/overview/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/pipeline/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/pipeline/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/pipeline/page.tsx create mode 100644 frontend/app/crm/sales/dashboard/sales/error.tsx create mode 100644 frontend/app/crm/sales/dashboard/sales/loading.tsx create mode 100644 frontend/app/crm/sales/dashboard/sales/page.tsx create mode 100644 frontend/app/crm/sales/deals/error.tsx create mode 100644 frontend/app/crm/sales/deals/loading.tsx create mode 100644 frontend/app/crm/sales/deals/page.tsx create mode 100644 frontend/app/crm/sales/documents/error.tsx create mode 100644 frontend/app/crm/sales/documents/loading.tsx create mode 100644 frontend/app/crm/sales/documents/page.tsx create mode 100644 frontend/app/crm/sales/emails/error.tsx create mode 100644 frontend/app/crm/sales/emails/loading.tsx create mode 100644 frontend/app/crm/sales/emails/page.tsx create mode 100644 frontend/app/crm/sales/export/error.tsx create mode 100644 frontend/app/crm/sales/export/loading.tsx create mode 100644 frontend/app/crm/sales/export/page.tsx create mode 100644 frontend/app/crm/sales/health/error.tsx create mode 100644 frontend/app/crm/sales/health/loading.tsx create mode 100644 frontend/app/crm/sales/health/page.tsx create mode 100644 frontend/app/crm/sales/import/error.tsx create mode 100644 frontend/app/crm/sales/import/loading.tsx create mode 100644 frontend/app/crm/sales/import/page.tsx create mode 100644 frontend/app/crm/sales/integrations/error.tsx create mode 100644 frontend/app/crm/sales/integrations/loading.tsx create mode 100644 frontend/app/crm/sales/integrations/page.tsx create mode 100644 frontend/app/crm/sales/kanban/error.tsx create mode 100644 frontend/app/crm/sales/kanban/loading.tsx create mode 100644 frontend/app/crm/sales/kanban/page.tsx create mode 100644 frontend/app/crm/sales/layout.tsx create mode 100644 frontend/app/crm/sales/leads/error.tsx create mode 100644 frontend/app/crm/sales/leads/loading.tsx create mode 100644 frontend/app/crm/sales/leads/page.tsx create mode 100644 frontend/app/crm/sales/meetings/error.tsx create mode 100644 frontend/app/crm/sales/meetings/loading.tsx create mode 100644 frontend/app/crm/sales/meetings/page.tsx create mode 100644 frontend/app/crm/sales/notes/error.tsx create mode 100644 frontend/app/crm/sales/notes/loading.tsx create mode 100644 frontend/app/crm/sales/notes/page.tsx create mode 100644 frontend/app/crm/sales/notifications/error.tsx create mode 100644 frontend/app/crm/sales/notifications/loading.tsx create mode 100644 frontend/app/crm/sales/notifications/page.tsx create mode 100644 frontend/app/crm/sales/permissions/error.tsx create mode 100644 frontend/app/crm/sales/permissions/loading.tsx create mode 100644 frontend/app/crm/sales/permissions/page.tsx create mode 100644 frontend/app/crm/sales/pipelines/error.tsx create mode 100644 frontend/app/crm/sales/pipelines/loading.tsx create mode 100644 frontend/app/crm/sales/pipelines/page.tsx create mode 100644 frontend/app/crm/sales/quotes/error.tsx create mode 100644 frontend/app/crm/sales/quotes/loading.tsx create mode 100644 frontend/app/crm/sales/quotes/page.tsx create mode 100644 frontend/app/crm/sales/reports/error.tsx create mode 100644 frontend/app/crm/sales/reports/loading.tsx create mode 100644 frontend/app/crm/sales/reports/page.tsx create mode 100644 frontend/app/crm/sales/roles/error.tsx create mode 100644 frontend/app/crm/sales/roles/loading.tsx create mode 100644 frontend/app/crm/sales/roles/page.tsx create mode 100644 frontend/app/crm/sales/segments/error.tsx create mode 100644 frontend/app/crm/sales/segments/loading.tsx create mode 100644 frontend/app/crm/sales/segments/page.tsx create mode 100644 frontend/app/crm/sales/settings/error.tsx create mode 100644 frontend/app/crm/sales/settings/loading.tsx create mode 100644 frontend/app/crm/sales/settings/page.tsx create mode 100644 frontend/app/crm/sales/tags/error.tsx create mode 100644 frontend/app/crm/sales/tags/loading.tsx create mode 100644 frontend/app/crm/sales/tags/page.tsx create mode 100644 frontend/app/crm/sales/tasks/error.tsx create mode 100644 frontend/app/crm/sales/tasks/loading.tsx create mode 100644 frontend/app/crm/sales/tasks/page.tsx create mode 100644 frontend/app/crm/sales/timeline/error.tsx create mode 100644 frontend/app/crm/sales/timeline/loading.tsx create mode 100644 frontend/app/crm/sales/timeline/page.tsx create mode 100644 frontend/docs/crm-component-map.md create mode 100644 frontend/docs/crm-responsive-report.md create mode 100644 frontend/docs/crm-routing-map.md create mode 100644 frontend/docs/crm-ui-specification.md create mode 100644 frontend/docs/crm-validation-report.md create mode 100644 frontend/modules/crm/components/CrmBreadcrumbs.tsx create mode 100644 frontend/modules/crm/components/CrmListCrudPage.tsx create mode 100644 frontend/modules/crm/components/CrmPortalShell.tsx create mode 100644 frontend/modules/crm/components/CrmScopeNotice.tsx create mode 100644 frontend/modules/crm/components/createPortalLayout.tsx create mode 100644 frontend/modules/crm/constants/permissions.ts create mode 100644 frontend/modules/crm/constants/portals.ts create mode 100644 frontend/modules/crm/design-system/CrmStatusChip.tsx create mode 100644 frontend/modules/crm/design-system/CrmTablePage.tsx create mode 100644 frontend/modules/crm/design-system/index.ts create mode 100644 frontend/modules/crm/design-system/tokens.ts create mode 100644 frontend/modules/crm/features/sales/activities.tsx create mode 100644 frontend/modules/crm/features/sales/admin.tsx create mode 100644 frontend/modules/crm/features/sales/analytics.tsx create mode 100644 frontend/modules/crm/features/sales/calls.tsx create mode 100644 frontend/modules/crm/features/sales/companies.tsx create mode 100644 frontend/modules/crm/features/sales/contacts.tsx create mode 100644 frontend/modules/crm/features/sales/custom-fields.tsx create mode 100644 frontend/modules/crm/features/sales/customers.tsx create mode 100644 frontend/modules/crm/features/sales/dashboards.tsx create mode 100644 frontend/modules/crm/features/sales/deals.tsx create mode 100644 frontend/modules/crm/features/sales/documents.tsx create mode 100644 frontend/modules/crm/features/sales/emails.tsx create mode 100644 frontend/modules/crm/features/sales/export.tsx create mode 100644 frontend/modules/crm/features/sales/kanban.tsx create mode 100644 frontend/modules/crm/features/sales/leads.tsx create mode 100644 frontend/modules/crm/features/sales/meetings.tsx create mode 100644 frontend/modules/crm/features/sales/notes.tsx create mode 100644 frontend/modules/crm/features/sales/pipelines.tsx create mode 100644 frontend/modules/crm/features/sales/quotes.tsx create mode 100644 frontend/modules/crm/features/sales/reports.tsx create mode 100644 frontend/modules/crm/features/sales/scoped.tsx create mode 100644 frontend/modules/crm/features/sales/tags.tsx create mode 100644 frontend/modules/crm/features/sales/tasks.tsx create mode 100644 frontend/modules/crm/features/sales/timeline.tsx create mode 100644 frontend/modules/crm/hooks/useCrmKeyboardShortcuts.ts create mode 100644 frontend/modules/crm/index.ts create mode 100644 frontend/modules/crm/pages/hub.tsx create mode 100644 frontend/modules/crm/pages/shared.tsx create mode 100644 frontend/modules/crm/services/crm-api.ts create mode 100644 frontend/modules/crm/types/index.ts create mode 100644 frontend/scripts/generate-crm-routes.mjs diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000..07d588b --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,125 @@ +{ + "extends": "next/core-web-vitals", + "overrides": [ + { + "files": ["app/accounting/**/*", "components/accounting/**/*"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@/components/beauty/**", + "@/components/healthcare/**", + "@/modules/beauty/**", + "@/modules/healthcare/**" + ], + "message": "Accounting must not import beauty or healthcare modules." + } + ] + } + ] + } + }, + { + "files": ["app/beauty/**/*", "modules/beauty/**/*"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@/components/accounting/**", + "@/components/healthcare/**", + "@/modules/accounting/**", + "@/modules/healthcare/**" + ], + "message": "Beauty must not import accounting or healthcare modules." + } + ] + } + ] + } + }, + { + "files": ["app/healthcare/**/*", "src/modules/healthcare/**/*"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@/components/accounting/**", + "@/components/beauty/**", + "@/modules/beauty/**", + "@/modules/accounting/**", + "@/src/modules/beauty/**" + ], + "message": "Healthcare must not import accounting or beauty modules." + } + ] + } + ] + } + }, + { + "files": ["app/crm/**/*", "modules/crm/**/*"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@/components/accounting/**", + "@/components/beauty/**", + "@/components/healthcare/**", + "@/modules/beauty/**", + "@/modules/healthcare/**", + "@/modules/accounting/**" + ], + "message": "CRM must not import accounting, beauty, or healthcare modules." + } + ] + } + ] + } + }, + { + "files": [ + "components/ds/**/*", + "shared/**/*", + "components/providers/**/*", + "components/AuthGuard.tsx", + "components/AdminAuthGuard.tsx", + "components/ThemeProvider.tsx", + "components/SiteHeader.tsx", + "hooks/**/*" + ], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": [ + "@/components/accounting/**", + "@/components/beauty/**", + "@/components/healthcare/**", + "@/modules/beauty/**", + "@/modules/healthcare/**", + "@/modules/accounting/**", + "@/modules/crm/**" + ], + "message": "Shared/platform code must not import business module code." + } + ] + } + ] + } + } + ] +} diff --git a/frontend/app/api/crm/[...path]/route.ts b/frontend/app/api/crm/[...path]/route.ts new file mode 100644 index 0000000..7b237c5 --- /dev/null +++ b/frontend/app/api/crm/[...path]/route.ts @@ -0,0 +1,96 @@ +import { NextRequest, NextResponse } from "next/server"; + +/** + * Same-origin BFF proxy → CRM service. + * Browser calls /api/crm/* so TLS/CORS/DNS issues + * do not block CRUD from the SuperApp origin. + */ +const UPSTREAM = + process.env.CRM_SERVICE_URL || + process.env.NEXT_PUBLIC_CRM_API_URL || + "http://localhost:8003"; + +async function proxy(req: NextRequest, pathSegments: string[]) { + const path = pathSegments.join("/"); + const url = new URL(req.url); + const target = `${UPSTREAM.replace(/\/$/, "")}/${path}${url.search}`; + + const headers = new Headers(); + const pass = ["authorization", "content-type", "x-tenant-id", "accept"]; + for (const h of pass) { + 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 outHeaders = new Headers(); + const ct = res.headers.get("content-type"); + if (ct) outHeaders.set("content-type", ct); + return new NextResponse(await res.arrayBuffer(), { + status: res.status, + headers: outHeaders, + }); + } catch (err) { + const detail = err instanceof Error ? err.message : "upstream_unreachable"; + return NextResponse.json( + { + error: { + code: "crm_proxy_error", + message: `پروکسی CRM به ${UPSTREAM} وصل نشد — ${detail}`, + }, + }, + { status: 502 } + ); + } +} + +export async function GET( + req: NextRequest, + ctx: { params: Promise<{ path: string[] }> } +) { + const { path } = await ctx.params; + return proxy(req, path); +} + +export async function POST( + req: NextRequest, + ctx: { params: Promise<{ path: string[] }> } +) { + const { path } = await ctx.params; + return proxy(req, path); +} + +export async function PATCH( + req: NextRequest, + ctx: { params: Promise<{ path: string[] }> } +) { + const { path } = await ctx.params; + return proxy(req, path); +} + +export async function PUT( + req: NextRequest, + ctx: { params: Promise<{ path: string[] }> } +) { + const { path } = await ctx.params; + return proxy(req, path); +} + +export async function DELETE( + req: NextRequest, + ctx: { params: Promise<{ path: string[] }> } +) { + const { path } = await ctx.params; + return proxy(req, path); +} diff --git a/frontend/app/crm/hub/error.tsx b/frontend/app/crm/hub/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/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/crm/hub/loading.tsx b/frontend/app/crm/hub/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/hub/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/hub/page.tsx b/frontend/app/crm/hub/page.tsx new file mode 100644 index 0000000..7625fa6 --- /dev/null +++ b/frontend/app/crm/hub/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { CrmHub as default } from "@/modules/crm/pages/hub"; diff --git a/frontend/app/crm/layout.tsx b/frontend/app/crm/layout.tsx new file mode 100644 index 0000000..88a8ba4 --- /dev/null +++ b/frontend/app/crm/layout.tsx @@ -0,0 +1,4 @@ +/** CRM root — sub-routes supply portal shells. */ +export default function CrmRootLayout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/frontend/app/crm/not-found.tsx b/frontend/app/crm/not-found.tsx new file mode 100644 index 0000000..c902e23 --- /dev/null +++ b/frontend/app/crm/not-found.tsx @@ -0,0 +1,13 @@ +import Link from "next/link"; +import { Button, EmptyState } from "@/components/ds"; + +export default function CrmNotFound() { + return ( +
+ } + /> +
+ ); +} diff --git a/frontend/app/crm/sales/activities/error.tsx b/frontend/app/crm/sales/activities/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/activities/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/crm/sales/activities/loading.tsx b/frontend/app/crm/sales/activities/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/activities/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/activities/page.tsx b/frontend/app/crm/sales/activities/page.tsx new file mode 100644 index 0000000..e1e3f83 --- /dev/null +++ b/frontend/app/crm/sales/activities/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/activities"; diff --git a/frontend/app/crm/sales/analytics/error.tsx b/frontend/app/crm/sales/analytics/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/analytics/loading.tsx b/frontend/app/crm/sales/analytics/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/analytics/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/analytics/page.tsx b/frontend/app/crm/sales/analytics/page.tsx new file mode 100644 index 0000000..0ed9829 --- /dev/null +++ b/frontend/app/crm/sales/analytics/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/analytics"; diff --git a/frontend/app/crm/sales/api-keys/error.tsx b/frontend/app/crm/sales/api-keys/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/api-keys/loading.tsx b/frontend/app/crm/sales/api-keys/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/api-keys/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/api-keys/page.tsx b/frontend/app/crm/sales/api-keys/page.tsx new file mode 100644 index 0000000..7f6eb80 --- /dev/null +++ b/frontend/app/crm/sales/api-keys/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { ApiKeysPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/audit-logs/error.tsx b/frontend/app/crm/sales/audit-logs/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/audit-logs/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/crm/sales/audit-logs/loading.tsx b/frontend/app/crm/sales/audit-logs/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/audit-logs/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/audit-logs/page.tsx b/frontend/app/crm/sales/audit-logs/page.tsx new file mode 100644 index 0000000..7a5487f --- /dev/null +++ b/frontend/app/crm/sales/audit-logs/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { AuditLogsPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/automation/error.tsx b/frontend/app/crm/sales/automation/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/automation/loading.tsx b/frontend/app/crm/sales/automation/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/automation/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/automation/page.tsx b/frontend/app/crm/sales/automation/page.tsx new file mode 100644 index 0000000..53984be --- /dev/null +++ b/frontend/app/crm/sales/automation/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { AutomationPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/calls/error.tsx b/frontend/app/crm/sales/calls/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/calls/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/crm/sales/calls/loading.tsx b/frontend/app/crm/sales/calls/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/calls/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/calls/page.tsx b/frontend/app/crm/sales/calls/page.tsx new file mode 100644 index 0000000..71514d8 --- /dev/null +++ b/frontend/app/crm/sales/calls/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/calls"; diff --git a/frontend/app/crm/sales/campaigns/error.tsx b/frontend/app/crm/sales/campaigns/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/campaigns/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/crm/sales/campaigns/loading.tsx b/frontend/app/crm/sales/campaigns/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/campaigns/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/campaigns/page.tsx b/frontend/app/crm/sales/campaigns/page.tsx new file mode 100644 index 0000000..a05ef09 --- /dev/null +++ b/frontend/app/crm/sales/campaigns/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { CampaignsPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/capabilities/error.tsx b/frontend/app/crm/sales/capabilities/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/capabilities/loading.tsx b/frontend/app/crm/sales/capabilities/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/capabilities/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/capabilities/page.tsx b/frontend/app/crm/sales/capabilities/page.tsx new file mode 100644 index 0000000..dd04ee2 --- /dev/null +++ b/frontend/app/crm/sales/capabilities/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { CapabilitiesPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/companies/error.tsx b/frontend/app/crm/sales/companies/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/companies/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/crm/sales/companies/loading.tsx b/frontend/app/crm/sales/companies/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/companies/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/companies/page.tsx b/frontend/app/crm/sales/companies/page.tsx new file mode 100644 index 0000000..8c85e04 --- /dev/null +++ b/frontend/app/crm/sales/companies/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/companies"; diff --git a/frontend/app/crm/sales/contacts/error.tsx b/frontend/app/crm/sales/contacts/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/contacts/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/crm/sales/contacts/loading.tsx b/frontend/app/crm/sales/contacts/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/contacts/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/contacts/page.tsx b/frontend/app/crm/sales/contacts/page.tsx new file mode 100644 index 0000000..16e55a6 --- /dev/null +++ b/frontend/app/crm/sales/contacts/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/contacts"; diff --git a/frontend/app/crm/sales/custom-fields/error.tsx b/frontend/app/crm/sales/custom-fields/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/custom-fields/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/crm/sales/custom-fields/loading.tsx b/frontend/app/crm/sales/custom-fields/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/custom-fields/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/custom-fields/page.tsx b/frontend/app/crm/sales/custom-fields/page.tsx new file mode 100644 index 0000000..7c0706b --- /dev/null +++ b/frontend/app/crm/sales/custom-fields/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/custom-fields"; diff --git a/frontend/app/crm/sales/customers/error.tsx b/frontend/app/crm/sales/customers/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/customers/error.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { ErrorState } from "@/components/ds"; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ; +} diff --git a/frontend/app/crm/sales/customers/loading.tsx b/frontend/app/crm/sales/customers/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/customers/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/customers/page.tsx b/frontend/app/crm/sales/customers/page.tsx new file mode 100644 index 0000000..aec818b --- /dev/null +++ b/frontend/app/crm/sales/customers/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/customers"; diff --git a/frontend/app/crm/sales/dashboard/activities/error.tsx b/frontend/app/crm/sales/dashboard/activities/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/activities/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/crm/sales/dashboard/activities/loading.tsx b/frontend/app/crm/sales/dashboard/activities/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/activities/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/activities/page.tsx b/frontend/app/crm/sales/dashboard/activities/page.tsx new file mode 100644 index 0000000..bc9f811 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/activities/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { ActivityDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/dashboard/calendar/error.tsx b/frontend/app/crm/sales/dashboard/calendar/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/calendar/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/crm/sales/dashboard/calendar/loading.tsx b/frontend/app/crm/sales/dashboard/calendar/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/calendar/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/calendar/page.tsx b/frontend/app/crm/sales/dashboard/calendar/page.tsx new file mode 100644 index 0000000..0085ede --- /dev/null +++ b/frontend/app/crm/sales/dashboard/calendar/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { CalendarDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/dashboard/leads/error.tsx b/frontend/app/crm/sales/dashboard/leads/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/leads/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/crm/sales/dashboard/leads/loading.tsx b/frontend/app/crm/sales/dashboard/leads/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/leads/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/leads/page.tsx b/frontend/app/crm/sales/dashboard/leads/page.tsx new file mode 100644 index 0000000..b162b95 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/leads/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { LeadDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/dashboard/overview/error.tsx b/frontend/app/crm/sales/dashboard/overview/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/overview/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/crm/sales/dashboard/overview/loading.tsx b/frontend/app/crm/sales/dashboard/overview/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/overview/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/overview/page.tsx b/frontend/app/crm/sales/dashboard/overview/page.tsx new file mode 100644 index 0000000..490b097 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/overview/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { OverviewDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/dashboard/pipeline/error.tsx b/frontend/app/crm/sales/dashboard/pipeline/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/pipeline/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/crm/sales/dashboard/pipeline/loading.tsx b/frontend/app/crm/sales/dashboard/pipeline/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/pipeline/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/pipeline/page.tsx b/frontend/app/crm/sales/dashboard/pipeline/page.tsx new file mode 100644 index 0000000..c6821cf --- /dev/null +++ b/frontend/app/crm/sales/dashboard/pipeline/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { PipelineDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/dashboard/sales/error.tsx b/frontend/app/crm/sales/dashboard/sales/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/dashboard/sales/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/crm/sales/dashboard/sales/loading.tsx b/frontend/app/crm/sales/dashboard/sales/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/sales/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/dashboard/sales/page.tsx b/frontend/app/crm/sales/dashboard/sales/page.tsx new file mode 100644 index 0000000..68132c7 --- /dev/null +++ b/frontend/app/crm/sales/dashboard/sales/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { SalesDashboard as default } from "@/modules/crm/features/sales/dashboards"; diff --git a/frontend/app/crm/sales/deals/error.tsx b/frontend/app/crm/sales/deals/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/deals/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/crm/sales/deals/loading.tsx b/frontend/app/crm/sales/deals/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/deals/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/deals/page.tsx b/frontend/app/crm/sales/deals/page.tsx new file mode 100644 index 0000000..650372d --- /dev/null +++ b/frontend/app/crm/sales/deals/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/deals"; diff --git a/frontend/app/crm/sales/documents/error.tsx b/frontend/app/crm/sales/documents/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/documents/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/crm/sales/documents/loading.tsx b/frontend/app/crm/sales/documents/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/documents/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/documents/page.tsx b/frontend/app/crm/sales/documents/page.tsx new file mode 100644 index 0000000..e97ec1d --- /dev/null +++ b/frontend/app/crm/sales/documents/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/documents"; diff --git a/frontend/app/crm/sales/emails/error.tsx b/frontend/app/crm/sales/emails/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/emails/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/crm/sales/emails/loading.tsx b/frontend/app/crm/sales/emails/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/emails/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/emails/page.tsx b/frontend/app/crm/sales/emails/page.tsx new file mode 100644 index 0000000..b9fed74 --- /dev/null +++ b/frontend/app/crm/sales/emails/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/emails"; diff --git a/frontend/app/crm/sales/export/error.tsx b/frontend/app/crm/sales/export/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/export/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/crm/sales/export/loading.tsx b/frontend/app/crm/sales/export/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/export/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/export/page.tsx b/frontend/app/crm/sales/export/page.tsx new file mode 100644 index 0000000..1a6e599 --- /dev/null +++ b/frontend/app/crm/sales/export/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/export"; diff --git a/frontend/app/crm/sales/health/error.tsx b/frontend/app/crm/sales/health/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/health/loading.tsx b/frontend/app/crm/sales/health/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/health/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/health/page.tsx b/frontend/app/crm/sales/health/page.tsx new file mode 100644 index 0000000..a3da2a2 --- /dev/null +++ b/frontend/app/crm/sales/health/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { HealthPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/import/error.tsx b/frontend/app/crm/sales/import/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/import/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/crm/sales/import/loading.tsx b/frontend/app/crm/sales/import/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/import/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/import/page.tsx b/frontend/app/crm/sales/import/page.tsx new file mode 100644 index 0000000..775cafd --- /dev/null +++ b/frontend/app/crm/sales/import/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { ImportPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/integrations/error.tsx b/frontend/app/crm/sales/integrations/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/integrations/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/crm/sales/integrations/loading.tsx b/frontend/app/crm/sales/integrations/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/integrations/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/integrations/page.tsx b/frontend/app/crm/sales/integrations/page.tsx new file mode 100644 index 0000000..24a7c01 --- /dev/null +++ b/frontend/app/crm/sales/integrations/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { IntegrationsPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/kanban/error.tsx b/frontend/app/crm/sales/kanban/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/kanban/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/crm/sales/kanban/loading.tsx b/frontend/app/crm/sales/kanban/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/kanban/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/kanban/page.tsx b/frontend/app/crm/sales/kanban/page.tsx new file mode 100644 index 0000000..6d8a7d4 --- /dev/null +++ b/frontend/app/crm/sales/kanban/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/kanban"; diff --git a/frontend/app/crm/sales/layout.tsx b/frontend/app/crm/sales/layout.tsx new file mode 100644 index 0000000..6804672 --- /dev/null +++ b/frontend/app/crm/sales/layout.tsx @@ -0,0 +1,5 @@ +"use client"; + +import { createPortalLayout } from "@/modules/crm/components/createPortalLayout"; + +export default createPortalLayout("sales"); diff --git a/frontend/app/crm/sales/leads/error.tsx b/frontend/app/crm/sales/leads/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/leads/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/crm/sales/leads/loading.tsx b/frontend/app/crm/sales/leads/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/leads/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/leads/page.tsx b/frontend/app/crm/sales/leads/page.tsx new file mode 100644 index 0000000..d217738 --- /dev/null +++ b/frontend/app/crm/sales/leads/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/leads"; diff --git a/frontend/app/crm/sales/meetings/error.tsx b/frontend/app/crm/sales/meetings/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/meetings/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/crm/sales/meetings/loading.tsx b/frontend/app/crm/sales/meetings/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/meetings/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/meetings/page.tsx b/frontend/app/crm/sales/meetings/page.tsx new file mode 100644 index 0000000..5f064da --- /dev/null +++ b/frontend/app/crm/sales/meetings/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/meetings"; diff --git a/frontend/app/crm/sales/notes/error.tsx b/frontend/app/crm/sales/notes/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/notes/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/crm/sales/notes/loading.tsx b/frontend/app/crm/sales/notes/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/notes/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/notes/page.tsx b/frontend/app/crm/sales/notes/page.tsx new file mode 100644 index 0000000..b1c5a64 --- /dev/null +++ b/frontend/app/crm/sales/notes/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/notes"; diff --git a/frontend/app/crm/sales/notifications/error.tsx b/frontend/app/crm/sales/notifications/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/notifications/loading.tsx b/frontend/app/crm/sales/notifications/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/notifications/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/notifications/page.tsx b/frontend/app/crm/sales/notifications/page.tsx new file mode 100644 index 0000000..775410e --- /dev/null +++ b/frontend/app/crm/sales/notifications/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { NotificationsPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/permissions/error.tsx b/frontend/app/crm/sales/permissions/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/permissions/loading.tsx b/frontend/app/crm/sales/permissions/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/permissions/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/permissions/page.tsx b/frontend/app/crm/sales/permissions/page.tsx new file mode 100644 index 0000000..c4cf943 --- /dev/null +++ b/frontend/app/crm/sales/permissions/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { PermissionsPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/pipelines/error.tsx b/frontend/app/crm/sales/pipelines/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/pipelines/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/crm/sales/pipelines/loading.tsx b/frontend/app/crm/sales/pipelines/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/pipelines/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/pipelines/page.tsx b/frontend/app/crm/sales/pipelines/page.tsx new file mode 100644 index 0000000..379dbb8 --- /dev/null +++ b/frontend/app/crm/sales/pipelines/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/pipelines"; diff --git a/frontend/app/crm/sales/quotes/error.tsx b/frontend/app/crm/sales/quotes/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/quotes/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/crm/sales/quotes/loading.tsx b/frontend/app/crm/sales/quotes/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/quotes/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/quotes/page.tsx b/frontend/app/crm/sales/quotes/page.tsx new file mode 100644 index 0000000..9ff6c0d --- /dev/null +++ b/frontend/app/crm/sales/quotes/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/quotes"; diff --git a/frontend/app/crm/sales/reports/error.tsx b/frontend/app/crm/sales/reports/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/reports/loading.tsx b/frontend/app/crm/sales/reports/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/reports/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/reports/page.tsx b/frontend/app/crm/sales/reports/page.tsx new file mode 100644 index 0000000..997967c --- /dev/null +++ b/frontend/app/crm/sales/reports/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/reports"; diff --git a/frontend/app/crm/sales/roles/error.tsx b/frontend/app/crm/sales/roles/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/roles/loading.tsx b/frontend/app/crm/sales/roles/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/roles/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/roles/page.tsx b/frontend/app/crm/sales/roles/page.tsx new file mode 100644 index 0000000..ca0b8ce --- /dev/null +++ b/frontend/app/crm/sales/roles/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { RolesPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/segments/error.tsx b/frontend/app/crm/sales/segments/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/segments/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/crm/sales/segments/loading.tsx b/frontend/app/crm/sales/segments/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/segments/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/segments/page.tsx b/frontend/app/crm/sales/segments/page.tsx new file mode 100644 index 0000000..ab3ddf4 --- /dev/null +++ b/frontend/app/crm/sales/segments/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { SegmentsPage as default } from "@/modules/crm/features/sales/scoped"; diff --git a/frontend/app/crm/sales/settings/error.tsx b/frontend/app/crm/sales/settings/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/settings/loading.tsx b/frontend/app/crm/sales/settings/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/settings/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/settings/page.tsx b/frontend/app/crm/sales/settings/page.tsx new file mode 100644 index 0000000..e20d996 --- /dev/null +++ b/frontend/app/crm/sales/settings/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { SettingsPage as default } from "@/modules/crm/features/sales/admin"; diff --git a/frontend/app/crm/sales/tags/error.tsx b/frontend/app/crm/sales/tags/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/tags/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/crm/sales/tags/loading.tsx b/frontend/app/crm/sales/tags/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/tags/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/tags/page.tsx b/frontend/app/crm/sales/tags/page.tsx new file mode 100644 index 0000000..8b2f1fb --- /dev/null +++ b/frontend/app/crm/sales/tags/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/tags"; diff --git a/frontend/app/crm/sales/tasks/error.tsx b/frontend/app/crm/sales/tasks/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/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/crm/sales/tasks/loading.tsx b/frontend/app/crm/sales/tasks/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/tasks/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/tasks/page.tsx b/frontend/app/crm/sales/tasks/page.tsx new file mode 100644 index 0000000..8fd9126 --- /dev/null +++ b/frontend/app/crm/sales/tasks/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/tasks"; diff --git a/frontend/app/crm/sales/timeline/error.tsx b/frontend/app/crm/sales/timeline/error.tsx new file mode 100644 index 0000000..b8dfa5e --- /dev/null +++ b/frontend/app/crm/sales/timeline/error.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { ErrorState } from "@/components/ds"; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + return ; +} diff --git a/frontend/app/crm/sales/timeline/loading.tsx b/frontend/app/crm/sales/timeline/loading.tsx new file mode 100644 index 0000000..141fa78 --- /dev/null +++ b/frontend/app/crm/sales/timeline/loading.tsx @@ -0,0 +1,5 @@ +import { LoadingState } from "@/components/ds"; + +export default function Loading() { + return ; +} diff --git a/frontend/app/crm/sales/timeline/page.tsx b/frontend/app/crm/sales/timeline/page.tsx new file mode 100644 index 0000000..28ae7b9 --- /dev/null +++ b/frontend/app/crm/sales/timeline/page.tsx @@ -0,0 +1,3 @@ +"use client"; + +export { default } from "@/modules/crm/features/sales/timeline"; diff --git a/frontend/docs/crm-component-map.md b/frontend/docs/crm-component-map.md new file mode 100644 index 0000000..196c9cc --- /dev/null +++ b/frontend/docs/crm-component-map.md @@ -0,0 +1,68 @@ +# CRM Component Map + +**Date:** 2026-07-26 + +## Module components (`modules/crm/`) + +| Component | Path | Purpose | +|-----------|------|---------| +| `CrmPortalShell` | `components/CrmPortalShell.tsx` | Sidebar nav, mobile drawer, theme toggle | +| `createPortalLayout` | `components/createPortalLayout.tsx` | AuthGuard + tenant gate + shell | +| `CrmBreadcrumbs` | `components/CrmBreadcrumbs.tsx` | CRM-root breadcrumbs | +| `CrmScopeNotice` | `components/CrmScopeNotice.tsx` | Out-of-scope feature messaging | +| `CrmListCrudPage` / `createCrmListPage` | `components/CrmListCrudPage.tsx` | Generic list CRUD factory | +| `CrmTablePage` | `design-system/CrmTablePage.tsx` | Search, sort, pagination, bulk bar | +| `CrmStatGrid` | `design-system/CrmTablePage.tsx` | Dashboard stat cards | +| `CrmStatusChip` | `design-system/CrmStatusChip.tsx` | Domain status badges | + +## Shared platform (`@/components/ds`) + +Used across all CRM pages: + +- `PageHeader`, `DataTable`, `EmptyState`, `LoadingState`, `ErrorState` +- `Button`, `Input`, `Textarea`, `Select`, `FormField`, `Checkbox` +- `Dialog`, `Drawer`, `ConfirmDialog` +- `Card`, `Badge`, `Pagination`, `Skeleton`, `Breadcrumbs` + +## Feature pages (`modules/crm/features/sales/`) + +| File | Default export / named | Entities | +|------|------------------------|----------| +| `dashboards.tsx` | 6 dashboard exports | Aggregates | +| `leads.tsx` | default | Lead CRUD | +| `contacts.tsx` | default | Contact CRUD | +| `companies.tsx` | default | Organization CRUD | +| `customers.tsx` | default | Combined view | +| `deals.tsx` | default | Opportunity CRUD + win/lose | +| `kanban.tsx` | default | Pipeline board | +| `pipelines.tsx` | default | Pipeline CRUD | +| `quotes.tsx` | default | Quote CRUD | +| `activities.tsx` | default | SalesActivity CRUD | +| `tasks.tsx` | default | Task create + complete | +| `meetings.tsx` | default | Meeting create | +| `calls.tsx` | default | CallLog create | +| `emails.tsx` | default | Email metadata create | +| `notes.tsx` | default | Note per entity | +| `timeline.tsx` | default | Timeline list | +| `documents.tsx` | default | Attachment refs | +| `tags.tsx` | default | Tag CRUD | +| `custom-fields.tsx` | default | CustomField CRUD | +| `export.tsx` | default | CSV export | +| `reports.tsx` | default | Client reports | +| `analytics.tsx` | default | Analytics dashboard | +| `scoped.tsx` | Campaigns, Segments, Import, Automation, Integrations, ApiKeys | Scope notices | +| `admin.tsx` | Notifications, Roles, Permissions, Audit, Settings, Health, Capabilities | Admin | + +## Services & hooks + +| Symbol | Path | +|--------|------| +| `crmApi` | `services/crm-api.ts` | +| `useCrmKeyboardShortcuts` | `hooks/useCrmKeyboardShortcuts.ts` | +| `useCrmHealth`, `useCrmCounts` | `pages/shared.tsx` | + +## Query key namespace + +Prefix: `["crm", tenantId, ...]` + +Examples: `leads`, `opportunities`, `mentions-unread`, `counts` diff --git a/frontend/docs/crm-responsive-report.md b/frontend/docs/crm-responsive-report.md new file mode 100644 index 0000000..dfb02e6 --- /dev/null +++ b/frontend/docs/crm-responsive-report.md @@ -0,0 +1,60 @@ +# CRM Responsive Report + +**Date:** 2026-07-26 + +## Breakpoints tested (design intent) + +| Breakpoint | Layout behavior | +|------------|-----------------| +| Desktop (≥1024px) | Fixed sidebar 256px, full table, multi-column dashboards | +| Tablet (768–1023px) | Collapsible nav groups, horizontal scroll tables, 2-col stat grid | +| Mobile (<768px) | Hamburger → drawer sidebar, stacked forms, single-col stats | + +## Component responsiveness + +| Component | Desktop | Tablet | Mobile | +|-----------|---------|--------|--------| +| `CrmPortalShell` | Sidebar visible | Sidebar visible | Drawer overlay | +| `CrmTablePage` | Full table | `overflow-x-auto` min-width 640px | Horizontal scroll + compact toolbar | +| `CrmStatGrid` | 4 columns (`xl:grid-cols-4`) | 2 columns | 1–2 columns | +| Kanban board | Multi-column row | Horizontal scroll columns | Swipe scroll columns | +| Dialog / Drawer | Center dialog / side drawer | Full-width dialog | Full-screen friendly padding | +| Customers split view | 2-column grid | 2-column | Stacked sections | + +## RTL + +- All layouts use logical RTL flow (Persian copy, right-aligned nav) +- Search icon positioned `right-3` for RTL input padding +- Breadcrumbs and sidebar labels right-aligned + +## Dark mode + +- Uses global `ColorModeProvider` toggle in shell +- CRM tokens defined in `:root` and `.dark` in `globals.css` +- Surfaces: `--surface`, `--canvas`, `--border` from platform DS + +## Accessibility + +- Dialog: `role="dialog"`, `aria-modal`, Escape to close +- Loading: `LoadingState` with visible label +- Tables: semantic `` via `DataTable` +- Icon buttons: `aria-label` on edit/delete/complete actions +- Sort select: `aria-label="مرتب‌سازی"` + +## Touch targets + +- Mobile menu button 44px (`size="icon"`) +- Kanban stage move buttons padded for touch +- Pagination buttons min height ~36px + +## Performance notes + +- Lists fetch up to 500 rows per resource (backend max page_size) +- Dashboard parallel `Promise.all` for counts +- TanStack Query caching per `["crm", tenantId, resource]` + +## Gaps / follow-ups + +1. Kanban drag-and-drop not implemented — uses button stage move (touch-friendly alternative) +2. Calendar dashboard is list view, not full calendar grid (no calendar API) +3. Virtualized tables not added — acceptable for ≤500 row client cap diff --git a/frontend/docs/crm-routing-map.md b/frontend/docs/crm-routing-map.md new file mode 100644 index 0000000..eab7926 --- /dev/null +++ b/frontend/docs/crm-routing-map.md @@ -0,0 +1,80 @@ +# CRM Routing Map + +**Date:** 2026-07-26 +**Pattern:** Thin `app/crm/**/page.tsx` → `modules/crm/features/**` + +## Root + +| Route | File | Module export | +|-------|------|---------------| +| `/crm` | — | Redirect via hub link | +| `/crm/hub` | `app/crm/hub/page.tsx` | `CrmHub` from `pages/hub` | + +## Sales portal layout + +`app/crm/sales/layout.tsx` → `createPortalLayout("sales")` + +## Sales routes + +| URL | Feature module | +|-----|----------------| +| `/crm/sales/dashboard/overview` | `features/sales/dashboards` → `OverviewDashboard` | +| `/crm/sales/dashboard/sales` | `SalesDashboard` | +| `/crm/sales/dashboard/pipeline` | `PipelineDashboard` | +| `/crm/sales/dashboard/leads` | `LeadDashboard` | +| `/crm/sales/dashboard/activities` | `ActivityDashboard` | +| `/crm/sales/dashboard/calendar` | `CalendarDashboard` | +| `/crm/sales/customers` | `features/sales/customers` | +| `/crm/sales/companies` | `features/sales/companies` | +| `/crm/sales/contacts` | `features/sales/contacts` | +| `/crm/sales/leads` | `features/sales/leads` | +| `/crm/sales/deals` | `features/sales/deals` | +| `/crm/sales/kanban` | `features/sales/kanban` | +| `/crm/sales/pipelines` | `features/sales/pipelines` | +| `/crm/sales/quotes` | `features/sales/quotes` | +| `/crm/sales/activities` | `features/sales/activities` | +| `/crm/sales/tasks` | `features/sales/tasks` | +| `/crm/sales/meetings` | `features/sales/meetings` | +| `/crm/sales/calls` | `features/sales/calls` | +| `/crm/sales/emails` | `features/sales/emails` | +| `/crm/sales/notes` | `features/sales/notes` | +| `/crm/sales/timeline` | `features/sales/timeline` | +| `/crm/sales/documents` | `features/sales/documents` | +| `/crm/sales/campaigns` | `features/sales/scoped` → `CampaignsPage` | +| `/crm/sales/segments` | `SegmentsPage` | +| `/crm/sales/tags` | `features/sales/tags` | +| `/crm/sales/custom-fields` | `features/sales/custom-fields` | +| `/crm/sales/import` | `ImportPage` | +| `/crm/sales/export` | `features/sales/export` | +| `/crm/sales/reports` | `features/sales/reports` | +| `/crm/sales/analytics` | `features/sales/analytics` | +| `/crm/sales/automation` | `AutomationPage` | +| `/crm/sales/notifications` | `features/sales/admin` → `NotificationsPage` | +| `/crm/sales/roles` | `RolesPage` | +| `/crm/sales/permissions` | `PermissionsPage` | +| `/crm/sales/integrations` | `IntegrationsPage` | +| `/crm/sales/api-keys` | `ApiKeysPage` | +| `/crm/sales/audit-logs` | `AuditLogsPage` | +| `/crm/sales/settings` | `SettingsPage` | +| `/crm/sales/health` | `HealthPage` | +| `/crm/sales/capabilities` | `CapabilitiesPage` | + +## Segment files (each route) + +Every route directory includes: + +- `page.tsx` — re-export only +- `loading.tsx` — `LoadingState` +- `error.tsx` — `ErrorState` + reset + +Root: `app/crm/layout.tsx`, `app/crm/not-found.tsx` + +## BFF + +| Browser path | Upstream | +|--------------|----------| +| `/api/crm/*` | `CRM_SERVICE_URL` / `localhost:8003` | + +## Generator + +Regenerate routes: `node scripts/generate-crm-routes.mjs` diff --git a/frontend/docs/crm-ui-specification.md b/frontend/docs/crm-ui-specification.md new file mode 100644 index 0000000..1de2a2f --- /dev/null +++ b/frontend/docs/crm-ui-specification.md @@ -0,0 +1,91 @@ +# CRM UI Specification + +**Product:** Torbat CRM (Enterprise Sales CRM) +**Version:** Phase 6.3 frontend integration +**Date:** 2026-07-26 + +## Product identity + +| Aspect | Choice | +|--------|--------| +| Accent | `--crm-accent` indigo (`#6366f1` light / `#818cf8` dark) | +| Tone | Professional, minimal, low noise, desktop-first | +| Language | RTL-first (Persian labels) | +| Shell | Collapsible sidebar groups, mobile drawer, dark mode toggle | + +Distinct from Accounting (sky/operational), Beauty (rose/warm), Healthcare (teal/clinical). + +## Architecture + +``` +app/crm/ # Thin routes only (page, layout, loading, error) +modules/crm/ # All business UI + services/crm-api.ts # Typed client → /api/crm BFF → :8003 + components/ # Shell, CRUD factory, breadcrumbs + design-system/ # CrmTablePage, CrmStatGrid, CrmStatusChip + features/sales/ # Page implementations + constants/portals.ts # Navigation +``` + +## Page inventory (41 routes) + +All pages under `/crm/sales/*` unless noted. + +| Area | Pages | Backend | +|------|-------|---------| +| Hub | `/crm/hub` | `/health` | +| Dashboards | overview, sales, pipeline, leads, activities, calendar | List aggregates | +| Entities | customers, companies, contacts, leads | Real CRUD APIs | +| Sales | deals, kanban, pipelines, quotes | opportunities, pipelines, quotes | +| Collaboration | activities, tasks, meetings, calls, emails, notes, timeline, documents | Phase 6.3 APIs | +| Marketing | tags | tags API; campaigns/segments scoped notice | +| Data | custom-fields, import, export | custom-fields; import N/A; export client CSV | +| Insights | reports, analytics, automation | computed / scoped | +| Admin | notifications, roles, permissions, integrations, api-keys, audit-logs, settings, health, capabilities | partial + static | + +## Standard page contract + +Every list page includes: + +- `CrmPageLoader` / skeleton via `CrmTablePage` +- `CrmPageError` with 403 permission state +- `EmptyState` with create action +- Search (client-side filter) +- Sort dropdown (client-side) +- Pagination (client-side slice; server uses page/page_size fetch) +- Bulk selection + bulk delete where API supports +- Dialog create + Drawer edit + ConfirmDialog delete +- Toast via Sonner +- Breadcrumb via `CrmBreadcrumbs` + +## Keyboard shortcuts + +| Chord | Target | +|-------|--------| +| `g` then `o` | Overview dashboard | +| `g` then `l` | Leads | +| `g` then `c` | Contacts | +| `g` then `d` | Deals | +| `g` then `k` | Kanban | + +## Out of scope (honest UI) + +Pages show `CrmScopeNotice` instead of fake data: + +- Campaigns, Segments, Import, Automation, Integrations, API Keys +- Global audit (per-entity audit only) +- `/capabilities` (health version only) + +## API conventions + +- Header: `Authorization: Bearer`, `X-Tenant-ID` +- Soft delete: `POST /{id}/delete` (leads, contacts, organizations) +- List: `page`, `page_size` — no server search/sort/filter +- Deals = `opportunities`; Customers = contacts + organizations + +## Related + +- [crm-component-map.md](./crm-component-map.md) +- [crm-routing-map.md](./crm-routing-map.md) +- [crm-validation-report.md](./crm-validation-report.md) +- [crm-responsive-report.md](./crm-responsive-report.md) diff --git a/frontend/docs/crm-validation-report.md b/frontend/docs/crm-validation-report.md new file mode 100644 index 0000000..75fcb5f --- /dev/null +++ b/frontend/docs/crm-validation-report.md @@ -0,0 +1,86 @@ +# CRM Validation Report + +**Date:** 2026-07-26 +**Scope:** CRM frontend (`modules/crm/`, `app/crm/`) + +## Quality gates + +| Gate | Result | Notes | +|------|--------|-------| +| `npm run typecheck` | ✅ Pass | All CRM modules type-clean | +| `npm run lint` | ✅ Pass | No CRM-specific warnings | +| `npm run validate:architecture` | ✅ Pass | No cross-domain imports | +| `npm run build` | ⚠️ Compile OK | Typecheck in build passed; final step hit transient Next font-manifest env error | + +## Route validation + +| Check | Count | Status | +|-------|-------|--------| +| Required pages (spec) | 41 | ✅ Generated | +| Thin `page.tsx` (re-export only) | 41 | ✅ | +| `loading.tsx` per route | 41 | ✅ | +| `error.tsx` per route | 41 | ✅ | +| Sales layout + portal gate | 1 | ✅ | +| Root layout + not-found | 2 | ✅ | + +## API validation + +| Domain | Connected | Mock/placeholder removed | +|--------|-----------|---------------------------| +| Leads | ✅ CRUD + assign + restore | ✅ | +| Contacts | ✅ CRUD + delete | ✅ | +| Organizations | ✅ CRUD + delete | ✅ | +| Opportunities (deals) | ✅ CRUD + stage + win/lose | ✅ | +| Pipelines | ✅ CRUD | ✅ | +| Activities | ✅ CRUD + complete | ✅ | +| Tasks | ✅ create + complete | ✅ | +| Meetings, Calls | ✅ create + list | ✅ | +| Quotes | ✅ CRUD | ✅ | +| Tags, Notes, Attachments, Custom fields | ✅ | ✅ | +| Timeline, Mentions | ✅ list / read | ✅ | +| Team roles/members | ✅ partial (no role list API) | ✅ honest UI | +| Campaigns, Segments, Import, Automation, Integrations, API keys | N/A backend | ✅ scope notice (no fake data) | + +## CRUD validation + +| Operation | Supported pages | +|-----------|-----------------| +| Create | All entity list pages via Dialog | +| Read | All list + dashboard aggregates | +| Update | leads, contacts, companies, deals, activities, quotes, pipelines | +| Delete | leads, contacts, companies (+ bulk) | +| Restore | leads only (API) | +| Archive | via soft-delete flags in list filter | +| Export | client CSV on export page + per-entity export button | +| Import | scope notice — no API | + +## Permission validation + +- `CrmPageError` maps `CrmApiError` 403 → permission empty state +- Static permission tree on `/crm/sales/permissions` +- No client-side RBAC gate (matches platform pattern); server enforces JWT roles + +## Navigation validation + +- All sidebar links in `constants/portals.ts` resolve to generated routes +- Hub → sales portal → all sub-pages +- Catalog tile: `apps-catalog.ts` → `/crm/hub` status `available` + +## Design system validation + +- CRM uses `@/components/ds` only (no duplicate atoms) +- Module accent via `--crm-accent` tokens +- Consistent `CrmTablePage` / `PageHeader` / card radius `rounded-2xl` + +## Known limitations (by design) + +1. No server-side search/filter/sort — client-side only +2. No list total count from API — pagination is approximate +3. Tasks/meetings: no update/delete API in backend 6.3 +4. `/capabilities` endpoint missing — health version shown instead + +## Recommendations (future) + +1. Add `GET /capabilities` to CRM backend for discovery UI +2. Add server-side filter params when backend supports them +3. Shared `useCrmPermission()` hook when Identity exposes user permissions to FE diff --git a/frontend/lib/apps-catalog.ts b/frontend/lib/apps-catalog.ts index 8ecc214..d6e7d52 100644 --- a/frontend/lib/apps-catalog.ts +++ b/frontend/lib/apps-catalog.ts @@ -22,13 +22,41 @@ export const PLATFORM_APPS: PlatformApp[] = [ status: "available", href: "/accounting", }, + { + id: "healthcare", + name: "تربت هلث", + description: "کلینیک، پزشک، بیمار، نوبت‌دهی", + icon: "🏥", + gradient: "from-rose-500 to-red-600", + status: "available", + href: "/healthcare/hub", + }, + { + id: "beauty", + name: "تربت بیوتی", + description: "سالن، نوبت، خدمات زیبایی", + icon: "💅", + gradient: "from-pink-500 to-fuchsia-600", + status: "available", + href: "/beauty/hub", + }, { id: "crm", name: "سیستم CRM", description: "مدیریت مشتری و فروش", icon: "🤝", gradient: "from-violet-500 to-purple-600", - status: "coming_soon", + status: "available", + href: "/crm/hub", + }, + { + id: "sports_center", + name: "مرکز ورزشی", + description: "عضویت، تمرین، مسابقات", + icon: "🏋️", + gradient: "from-blue-500 to-indigo-600", + status: "available", + href: "/sports-center/hub", }, { id: "ecommerce", diff --git a/frontend/modules/crm/components/CrmBreadcrumbs.tsx b/frontend/modules/crm/components/CrmBreadcrumbs.tsx new file mode 100644 index 0000000..5f705ee --- /dev/null +++ b/frontend/modules/crm/components/CrmBreadcrumbs.tsx @@ -0,0 +1,15 @@ +"use client"; + +import { Breadcrumbs } from "@/components/ds"; + +export function CrmBreadcrumbs({ + items, +}: { + items: { label: string; href?: string }[]; +}) { + return ( +
+ +
+ ); +} diff --git a/frontend/modules/crm/components/CrmListCrudPage.tsx b/frontend/modules/crm/components/CrmListCrudPage.tsx new file mode 100644 index 0000000..a3d44a8 --- /dev/null +++ b/frontend/modules/crm/components/CrmListCrudPage.tsx @@ -0,0 +1,331 @@ +"use client"; + +import { useState } from "react"; +import { useForm, type FieldValues, type DefaultValues, type Resolver } 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, RotateCcw } from "lucide-react"; +import type { z } from "zod"; +import { + Button, + Dialog, + Drawer, + Input, + EmptyState, + FormField, + ConfirmDialog, + Textarea, + Select, +} from "@/components/ds"; +import { CrmTablePage } from "@/modules/crm/design-system"; +import { CrmBreadcrumbs } from "@/modules/crm/components/CrmBreadcrumbs"; +import { CrmPageLoader, CrmPageError } from "@/modules/crm/pages/shared"; +import { useTenantId } from "@/hooks/useTenantId"; +import { exportToCsv } from "@/modules/crm/pages/shared"; + +export type CrmFieldConfig = { + name: string; + label: string; + type?: "text" | "email" | "tel" | "number" | "textarea" | "select"; + options?: { value: string; label: string }[]; + required?: boolean; + editOnly?: boolean; + createOnly?: boolean; +}; + +export type CrmListCrudConfig = { + title: string; + description?: string; + breadcrumb?: string; + resourceKey: string; + createSchema: z.ZodTypeAny; + editSchema: z.ZodTypeAny; + createDefaults: DefaultValues; + fields: CrmFieldConfig[]; + columns: { + key: string; + header: string; + render?: (row: T) => React.ReactNode; + }[]; + list: (tenantId: string) => Promise; + create: (tenantId: string, data: FieldValues) => Promise; + update?: (tenantId: string, id: string, data: FieldValues) => Promise; + remove?: (tenantId: string, id: string) => Promise; + restore?: (tenantId: string, id: string) => Promise; + exportColumns?: string[]; + filterDeleted?: boolean; +}; + +function renderField( + field: CrmFieldConfig, + register: ReturnType["register"], + mode: "create" | "edit" +) { + if (mode === "create" && field.editOnly) return null; + if (mode === "edit" && field.createOnly) return null; + + if (field.type === "textarea") { + return ( + +