@rebasepro/app 0.0.1-canary.4829d6e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/auth/api.d.ts +66 -0
  4. package/dist/auth/index.d.ts +13 -0
  5. package/dist/auth/types.d.ts +127 -0
  6. package/dist/auth/useRebaseAuthController.d.ts +13 -0
  7. package/dist/components/AIIcon.d.ts +17 -0
  8. package/dist/components/AuthSimulationMenu.d.ts +0 -0
  9. package/dist/components/ConfirmationDialog.d.ts +9 -0
  10. package/dist/components/Debug/UIReferenceView.d.ts +13 -0
  11. package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
  12. package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
  13. package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
  14. package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
  15. package/dist/components/Debug/collection-views/index.d.ts +3 -0
  16. package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
  17. package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
  18. package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
  19. package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
  20. package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
  21. package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
  22. package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
  23. package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
  24. package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
  25. package/dist/components/ErrorTooltip.d.ts +3 -0
  26. package/dist/components/ErrorView.d.ts +21 -0
  27. package/dist/components/LanguageToggle.d.ts +2 -0
  28. package/dist/components/LoginView/LoginView.d.ts +113 -0
  29. package/dist/components/LoginView/index.d.ts +2 -0
  30. package/dist/components/NotFoundPage.d.ts +2 -0
  31. package/dist/components/RebaseAuth.d.ts +10 -0
  32. package/dist/components/RebaseLogo.d.ts +7 -0
  33. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  34. package/dist/components/UnsavedChangesDialog.d.ts +9 -0
  35. package/dist/components/UserDisplay.d.ts +7 -0
  36. package/dist/components/UserSelectPopover.d.ts +63 -0
  37. package/dist/components/UserSettingsView.d.ts +2 -0
  38. package/dist/components/common/index.d.ts +6 -0
  39. package/dist/components/common/table_height.d.ts +5 -0
  40. package/dist/components/common/types.d.ts +66 -0
  41. package/dist/components/common/useColumnsIds.d.ts +9 -0
  42. package/dist/components/common/useDataTableController.d.ts +45 -0
  43. package/dist/components/common/useDebouncedData.d.ts +9 -0
  44. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  45. package/dist/components/index.d.ts +18 -0
  46. package/dist/contexts/AdminModeController.d.ts +4 -0
  47. package/dist/contexts/AnalyticsContext.d.ts +3 -0
  48. package/dist/contexts/AuthControllerContext.d.ts +3 -0
  49. package/dist/contexts/CollectionScopeContext.d.ts +50 -0
  50. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  51. package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
  52. package/dist/contexts/DataDriverContext.d.ts +3 -0
  53. package/dist/contexts/DataSourcesContext.d.ts +31 -0
  54. package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
  55. package/dist/contexts/DialogsProvider.d.ts +4 -0
  56. package/dist/contexts/EffectiveRoleController.d.ts +4 -0
  57. package/dist/contexts/ModeController.d.ts +4 -0
  58. package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
  59. package/dist/contexts/RebaseDataContext.d.ts +3 -0
  60. package/dist/contexts/SnackbarProvider.d.ts +2 -0
  61. package/dist/contexts/StorageSourceContext.d.ts +3 -0
  62. package/dist/contexts/StorageSourcesContext.d.ts +25 -0
  63. package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  64. package/dist/contexts/index.d.ts +17 -0
  65. package/dist/core/PluginLifecycleManager.d.ts +17 -0
  66. package/dist/core/PluginProviderStack.d.ts +21 -0
  67. package/dist/core/Rebase.d.ts +15 -0
  68. package/dist/core/RebaseProps.d.ts +291 -0
  69. package/dist/core/RebaseRouter.d.ts +4 -0
  70. package/dist/core/RebaseRoutes.d.ts +17 -0
  71. package/dist/core/index.d.ts +4 -0
  72. package/dist/hooks/ApiConfigContext.d.ts +24 -0
  73. package/dist/hooks/data/delete.d.ts +33 -0
  74. package/dist/hooks/data/save.d.ts +40 -0
  75. package/dist/hooks/data/useCollection.d.ts +62 -0
  76. package/dist/hooks/data/useData.d.ts +13 -0
  77. package/dist/hooks/data/useDataOrder.d.ts +12 -0
  78. package/dist/hooks/data/useFetch.d.ts +43 -0
  79. package/dist/hooks/data/useRelationSelector.d.ts +52 -0
  80. package/dist/hooks/index.d.ts +36 -0
  81. package/dist/hooks/useAdminModeController.d.ts +19 -0
  82. package/dist/hooks/useAnalyticsController.d.ts +5 -0
  83. package/dist/hooks/useAuthController.d.ts +11 -0
  84. package/dist/hooks/useAuthSubscription.d.ts +2 -0
  85. package/dist/hooks/useBackendStorageSource.d.ts +30 -0
  86. package/dist/hooks/useBridgeRegistration.d.ts +18 -0
  87. package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  88. package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
  89. package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  90. package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  91. package/dist/hooks/useBuildModeController.d.ts +6 -0
  92. package/dist/hooks/useClipboard.d.ts +57 -0
  93. package/dist/hooks/useCollapsedGroups.d.ts +27 -0
  94. package/dist/hooks/useComponentOverride.d.ts +32 -0
  95. package/dist/hooks/useCustomizationController.d.ts +11 -0
  96. package/dist/hooks/useDialogsController.d.ts +11 -0
  97. package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
  98. package/dist/hooks/useLargeLayout.d.ts +1 -0
  99. package/dist/hooks/useModeController.d.ts +19 -0
  100. package/dist/hooks/usePermissions.d.ts +12 -0
  101. package/dist/hooks/useRebaseClient.d.ts +5 -0
  102. package/dist/hooks/useRebaseContext.d.ts +11 -0
  103. package/dist/hooks/useRebaseRegistry.d.ts +34 -0
  104. package/dist/hooks/useResolvedComponent.d.ts +47 -0
  105. package/dist/hooks/useSlot.d.ts +18 -0
  106. package/dist/hooks/useSnackbarController.d.ts +20 -0
  107. package/dist/hooks/useStorageSource.d.ts +6 -0
  108. package/dist/hooks/useStudioBridge.d.ts +79 -0
  109. package/dist/hooks/useTranslation.d.ts +17 -0
  110. package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
  111. package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
  112. package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
  113. package/dist/index.d.ts +16 -0
  114. package/dist/index.es.js +16843 -0
  115. package/dist/index.es.js.map +1 -0
  116. package/dist/index.umd.js +17021 -0
  117. package/dist/index.umd.js.map +1 -0
  118. package/dist/internal/common.d.ts +14 -0
  119. package/dist/internal/useRestoreScroll.d.ts +15 -0
  120. package/dist/locales/de.d.ts +2 -0
  121. package/dist/locales/en.d.ts +10 -0
  122. package/dist/locales/es.d.ts +10 -0
  123. package/dist/locales/fr.d.ts +2 -0
  124. package/dist/locales/hi.d.ts +2 -0
  125. package/dist/locales/it.d.ts +2 -0
  126. package/dist/locales/pt.d.ts +7 -0
  127. package/dist/util/constants.d.ts +1 -0
  128. package/dist/util/createFormexStub.d.ts +2 -0
  129. package/dist/util/entity_cache.d.ts +22 -0
  130. package/dist/util/enums.d.ts +5 -0
  131. package/dist/util/icon_list.d.ts +5 -0
  132. package/dist/util/icons.d.ts +20 -0
  133. package/dist/util/index.d.ts +8 -0
  134. package/dist/util/previews.d.ts +4 -0
  135. package/dist/util/useStorageUploadController.d.ts +44 -0
  136. package/dist/vitePlugin.d.ts +60 -0
  137. package/dist/vitePlugin.js +113 -0
  138. package/package.json +145 -0
  139. package/src/auth/api.ts +148 -0
  140. package/src/auth/index.ts +25 -0
  141. package/src/auth/types.ts +115 -0
  142. package/src/auth/useRebaseAuthController.ts +328 -0
  143. package/src/components/AIIcon.tsx +47 -0
  144. package/src/components/AuthSimulationMenu.tsx +0 -0
  145. package/src/components/ConfirmationDialog.tsx +48 -0
  146. package/src/components/Debug/UIReferenceView.tsx +958 -0
  147. package/src/components/Debug/UIStyleGuide.tsx +156 -0
  148. package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
  149. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
  150. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
  151. package/src/components/Debug/collection-views/index.ts +3 -0
  152. package/src/components/Debug/collection-views/sample_data.ts +42 -0
  153. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
  154. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
  155. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
  156. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
  157. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
  158. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
  159. package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
  160. package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
  161. package/src/components/ErrorTooltip.tsx +12 -0
  162. package/src/components/ErrorView.tsx +94 -0
  163. package/src/components/LanguageToggle.tsx +59 -0
  164. package/src/components/LoginView/LoginView.tsx +932 -0
  165. package/src/components/LoginView/index.ts +2 -0
  166. package/src/components/NotFoundPage.tsx +27 -0
  167. package/src/components/RebaseAuth.tsx +27 -0
  168. package/src/components/RebaseLogo.tsx +29 -0
  169. package/src/components/SchemaDriftBanner.tsx +102 -0
  170. package/src/components/UnsavedChangesDialog.tsx +46 -0
  171. package/src/components/UserDisplay.tsx +54 -0
  172. package/src/components/UserSelectPopover.tsx +399 -0
  173. package/src/components/UserSettingsView.tsx +318 -0
  174. package/src/components/common/index.ts +6 -0
  175. package/src/components/common/table_height.tsx +21 -0
  176. package/src/components/common/types.tsx +66 -0
  177. package/src/components/common/useColumnsIds.tsx +222 -0
  178. package/src/components/common/useDataTableController.tsx +514 -0
  179. package/src/components/common/useDebouncedData.ts +49 -0
  180. package/src/components/common/useScrollRestoration.tsx +68 -0
  181. package/src/components/index.tsx +26 -0
  182. package/src/contexts/AdminModeController.tsx +11 -0
  183. package/src/contexts/AnalyticsContext.tsx +4 -0
  184. package/src/contexts/AuthControllerContext.tsx +4 -0
  185. package/src/contexts/CollectionScopeContext.tsx +67 -0
  186. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  187. package/src/contexts/CustomizationControllerContext.tsx +4 -0
  188. package/src/contexts/DataDriverContext.tsx +4 -0
  189. package/src/contexts/DataSourcesContext.tsx +36 -0
  190. package/src/contexts/DatabaseAdminContext.tsx +4 -0
  191. package/src/contexts/DialogsProvider.tsx +56 -0
  192. package/src/contexts/EffectiveRoleController.tsx +12 -0
  193. package/src/contexts/ModeController.tsx +11 -0
  194. package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
  195. package/src/contexts/RebaseDataContext.tsx +4 -0
  196. package/src/contexts/SnackbarProvider.tsx +15 -0
  197. package/src/contexts/StorageSourceContext.tsx +4 -0
  198. package/src/contexts/StorageSourcesContext.tsx +30 -0
  199. package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
  200. package/src/contexts/index.ts +17 -0
  201. package/src/core/PluginLifecycleManager.tsx +95 -0
  202. package/src/core/PluginProviderStack.tsx +40 -0
  203. package/src/core/Rebase.tsx +439 -0
  204. package/src/core/RebaseProps.tsx +320 -0
  205. package/src/core/RebaseRouter.tsx +17 -0
  206. package/src/core/RebaseRoutes.tsx +24 -0
  207. package/src/core/index.tsx +5 -0
  208. package/src/hooks/ApiConfigContext.tsx +41 -0
  209. package/src/hooks/data/delete.ts +55 -0
  210. package/src/hooks/data/save.ts +77 -0
  211. package/src/hooks/data/useCollection.tsx +176 -0
  212. package/src/hooks/data/useData.tsx +18 -0
  213. package/src/hooks/data/useDataOrder.ts +26 -0
  214. package/src/hooks/data/useFetch.tsx +140 -0
  215. package/src/hooks/data/useRelationSelector.tsx +234 -0
  216. package/src/hooks/index.tsx +45 -0
  217. package/src/hooks/useAdminModeController.tsx +23 -0
  218. package/src/hooks/useAnalyticsController.tsx +8 -0
  219. package/src/hooks/useAuthController.tsx +14 -0
  220. package/src/hooks/useAuthSubscription.ts +87 -0
  221. package/src/hooks/useBackendStorageSource.ts +288 -0
  222. package/src/hooks/useBridgeRegistration.tsx +32 -0
  223. package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
  224. package/src/hooks/useBuildAdminModeController.tsx +24 -0
  225. package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
  226. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
  227. package/src/hooks/useBuildModeController.tsx +71 -0
  228. package/src/hooks/useClipboard.tsx +158 -0
  229. package/src/hooks/useCollapsedGroups.ts +116 -0
  230. package/src/hooks/useComponentOverride.tsx +59 -0
  231. package/src/hooks/useCustomizationController.tsx +14 -0
  232. package/src/hooks/useDialogsController.tsx +15 -0
  233. package/src/hooks/useEffectiveRoleController.tsx +10 -0
  234. package/src/hooks/useLargeLayout.tsx +65 -0
  235. package/src/hooks/useModeController.tsx +23 -0
  236. package/src/hooks/usePermissions.ts +44 -0
  237. package/src/hooks/useRebaseClient.tsx +10 -0
  238. package/src/hooks/useRebaseContext.tsx +83 -0
  239. package/src/hooks/useRebaseRegistry.tsx +91 -0
  240. package/src/hooks/useResolvedComponent.tsx +159 -0
  241. package/src/hooks/useSlot.tsx +64 -0
  242. package/src/hooks/useSnackbarController.tsx +52 -0
  243. package/src/hooks/useStorageSource.tsx +11 -0
  244. package/src/hooks/useStudioBridge.tsx +209 -0
  245. package/src/hooks/useTranslation.ts +33 -0
  246. package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
  247. package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
  248. package/src/i18n/RebaseI18nProvider.tsx +160 -0
  249. package/src/index.ts +25 -0
  250. package/src/internal/common.tsx +16 -0
  251. package/src/internal/useRestoreScroll.tsx +69 -0
  252. package/src/locales/de.ts +902 -0
  253. package/src/locales/en.ts +980 -0
  254. package/src/locales/es.ts +922 -0
  255. package/src/locales/fr.ts +901 -0
  256. package/src/locales/hi.ts +901 -0
  257. package/src/locales/it.ts +901 -0
  258. package/src/locales/pt.ts +909 -0
  259. package/src/util/constants.ts +7 -0
  260. package/src/util/createFormexStub.tsx +66 -0
  261. package/src/util/entity_cache.ts +239 -0
  262. package/src/util/enums.ts +32 -0
  263. package/src/util/icon_list.ts +22 -0
  264. package/src/util/icons.tsx +149 -0
  265. package/src/util/index.ts +12 -0
  266. package/src/util/previews.ts +105 -0
  267. package/src/util/useStorageUploadController.tsx +350 -0
  268. package/src/vitePlugin.ts +196 -0
@@ -0,0 +1,1102 @@
1
+ import React, { useState, useEffect, useMemo, useCallback } from "react";
2
+ import {
3
+ Typography,
4
+ Button,
5
+ cls,
6
+ Container,
7
+ Card,
8
+ Alert,
9
+ defaultBorderMixin,
10
+ Separator,
11
+ TextField,
12
+ Select,
13
+ SelectItem
14
+ } from "@rebasepro/ui";
15
+ import {
16
+ RefreshCw,
17
+ UserPlus,
18
+ X,
19
+ Check,
20
+ Briefcase,
21
+ Mail,
22
+ Phone,
23
+ User,
24
+ Calendar,
25
+ AlertTriangle,
26
+ Plus
27
+ } from "lucide-react";
28
+
29
+ import { DashboardMetrics } from "./DashboardMetrics";
30
+ import { PipelineOverview } from "./PipelineOverview";
31
+ import { RecentActivity } from "./RecentActivity";
32
+ import { TasksView } from "./TasksView";
33
+ import { CalendarWidget } from "./CalendarWidget";
34
+
35
+ /* ── Types ────────────────────────────────────────────── */
36
+
37
+ interface Client {
38
+ id: string;
39
+ name: string;
40
+ email: string;
41
+ phone: string;
42
+ stage: string;
43
+ picture?: string;
44
+ }
45
+
46
+ interface Task {
47
+ id: string;
48
+ values: {
49
+ title: string;
50
+ status: string;
51
+ resolution: string | null;
52
+ dueDate: string | null;
53
+ stageId: string | null;
54
+ description: string | null;
55
+ notes: string | null;
56
+ clientId: string | null;
57
+ client?: { name?: string } | null;
58
+ priority?: string | null;
59
+ createdAt: string | null;
60
+ };
61
+ }
62
+
63
+ interface Activity {
64
+ id: string;
65
+ values: {
66
+ type: string;
67
+ description: string;
68
+ createdAt: string;
69
+ metadata?: {
70
+ taskId?: string;
71
+ taskTitle?: string;
72
+ subject?: string;
73
+ body?: string;
74
+ to?: string;
75
+ mode?: string;
76
+ };
77
+ };
78
+ }
79
+
80
+ interface PipelineOption {
81
+ id: string;
82
+ name: string;
83
+ isDefault: boolean;
84
+ color: string;
85
+ }
86
+
87
+ interface PipelineStage {
88
+ stageKey: string;
89
+ label: string;
90
+ shortLabel: string;
91
+ color: string;
92
+ icon: string;
93
+ sortOrder: number;
94
+ isTerminal: boolean;
95
+ isLostStage: boolean;
96
+ count: number;
97
+ }
98
+
99
+ /* ── Color map ────────────────────────────────────────── */
100
+
101
+ const COLOR_MAP: Record<string, string> = {
102
+ blue: "#3b82f6",
103
+ indigo: "#6366f1",
104
+ purple: "#8b5cf6",
105
+ yellow: "#eab308",
106
+ teal: "#14b8a6",
107
+ green: "#22c55e",
108
+ orange: "#f97316",
109
+ gray: "#6b7280",
110
+ red: "#ef4444",
111
+ pink: "#ec4899",
112
+ sky: "#0ea5e9",
113
+ amber: "#f59e0b"
114
+ };
115
+
116
+ function resolveColor(colorName: string): string {
117
+ return COLOR_MAP[colorName] ?? COLOR_MAP.gray;
118
+ }
119
+
120
+ /* ── Stage Labels ──────────────────────────────────────── */
121
+
122
+ const STAGE_LABELS: Record<string, string> = {
123
+ incoming: "Incoming",
124
+ email_sent: "Intro Email",
125
+ discovery_call: "Discovery",
126
+ agreements: "Agreements",
127
+ onboarding: "Onboarding",
128
+ active: "Active",
129
+ delivered: "Delivered",
130
+ completed: "Done"
131
+ };
132
+
133
+ const PIPELINES: PipelineOption[] = [
134
+ { id: "main", name: "Main Sales Pipeline", isDefault: true, color: "blue" },
135
+ { id: "partnerships", name: "Partnerships Funnel", isDefault: false, color: "indigo" }
136
+ ];
137
+
138
+ const INITIAL_STAGES: Record<string, Omit<PipelineStage, "count">[]> = {
139
+ main: [
140
+ { stageKey: "incoming", label: "Incoming", shortLabel: "Inbound", color: "blue", icon: "inbox", sortOrder: 1, isTerminal: false, isLostStage: false },
141
+ { stageKey: "email_sent", label: "Intro Email", shortLabel: "Intro", color: "indigo", icon: "mail", sortOrder: 2, isTerminal: false, isLostStage: false },
142
+ { stageKey: "discovery_call", label: "Discovery", shortLabel: "Disco", color: "purple", icon: "phone", sortOrder: 3, isTerminal: false, isLostStage: false },
143
+ { stageKey: "agreements", label: "Agreements", shortLabel: "Contracts", color: "yellow", icon: "file-text", sortOrder: 4, isTerminal: false, isLostStage: false },
144
+ { stageKey: "onboarding", label: "Onboarding", shortLabel: "Onboard", color: "teal", icon: "user-check", sortOrder: 5, isTerminal: false, isLostStage: false },
145
+ { stageKey: "active", label: "Active", shortLabel: "Active", color: "green", icon: "activity", sortOrder: 6, isTerminal: false, isLostStage: false },
146
+ { stageKey: "delivered", label: "Delivered", shortLabel: "Done", color: "orange", icon: "check-circle", sortOrder: 7, isTerminal: true, isLostStage: false },
147
+ { stageKey: "completed", label: "Done", shortLabel: "Closed", color: "red", icon: "archive", sortOrder: 8, isTerminal: true, isLostStage: true }
148
+ ],
149
+ partnerships: [
150
+ { stageKey: "incoming", label: "Outreach", shortLabel: "Outbound", color: "blue", icon: "send", sortOrder: 1, isTerminal: false, isLostStage: false },
151
+ { stageKey: "discovery_call", label: "First Meeting", shortLabel: "Meet", color: "purple", icon: "users", sortOrder: 2, isTerminal: false, isLostStage: false },
152
+ { stageKey: "agreements", label: "Proposal", shortLabel: "Prop", color: "yellow", icon: "file", sortOrder: 3, isTerminal: false, isLostStage: false },
153
+ { stageKey: "active", label: "Partnered", shortLabel: "Live", color: "green", icon: "handshake", sortOrder: 4, isTerminal: true, isLostStage: false }
154
+ ]
155
+ };
156
+
157
+ const INITIAL_CLIENTS: Client[] = [
158
+ { id: "client-1", name: "Alice Johnson", email: "alice@johnson.com", phone: "+1 (555) 0192", stage: "discovery_call" },
159
+ { id: "client-2", name: "Bob Smith", email: "bob@smith.co", phone: "+1 (555) 0143", stage: "email_sent" },
160
+ { id: "client-3", name: "Carol White", email: "carol@whitecorp.com", phone: "+1 (555) 0188", stage: "agreements" },
161
+ { id: "client-4", name: "David Brown", email: "david.b@brown.net", phone: "+1 (555) 0177", stage: "onboarding" },
162
+ { id: "client-5", name: "Eva Green", email: "eva@greenllc.com", phone: "+1 (555) 0165", stage: "active" },
163
+ { id: "client-6", name: "Frank Miller", email: "frank@miller.org", phone: "+1 (555) 0112", stage: "completed" },
164
+ ];
165
+
166
+ const INITIAL_TASKS: Task[] = [
167
+ {
168
+ id: "task-1",
169
+ values: {
170
+ title: "Schedule discovery call",
171
+ status: "pending",
172
+ resolution: null,
173
+ dueDate: new Date(Date.now() + 86400000).toISOString().split('T')[0],
174
+ stageId: "discovery_call",
175
+ clientId: "client-1",
176
+ priority: "high",
177
+ description: "Set up a 30-min Zoom call to discuss requirements and partnership scope.",
178
+ notes: "Alice is in EST timezone, prefers morning slots.",
179
+ createdAt: new Date(Date.now() - 172800000).toISOString()
180
+ }
181
+ },
182
+ {
183
+ id: "task-2",
184
+ values: {
185
+ title: "Draft partnership agreement",
186
+ status: "pending",
187
+ resolution: null,
188
+ dueDate: new Date(Date.now() - 172800000).toISOString().split('T')[0], // overdue
189
+ stageId: "agreements",
190
+ clientId: "client-3",
191
+ priority: "high",
192
+ description: "Draft enterprise contract incorporating customized SLA definitions.",
193
+ notes: "Carol requested special uptime guarantees in Appendix A.",
194
+ createdAt: new Date(Date.now() - 345600000).toISOString()
195
+ }
196
+ },
197
+ {
198
+ id: "task-3",
199
+ values: {
200
+ title: "Send onboarding checklist",
201
+ status: "pending",
202
+ resolution: null,
203
+ dueDate: new Date(Date.now() + 172800000).toISOString().split('T')[0],
204
+ stageId: "onboarding",
205
+ clientId: "client-4",
206
+ priority: "medium",
207
+ description: "Assemble welcome packet, Slack community invite link, and environment setup guidelines.",
208
+ notes: "Must carbon copy David's CTO on all setup emails.",
209
+ createdAt: new Date(Date.now() - 86400000).toISOString()
210
+ }
211
+ },
212
+ {
213
+ id: "task-4",
214
+ values: {
215
+ title: "Follow up on intro email",
216
+ status: "pending",
217
+ resolution: null,
218
+ dueDate: new Date(Date.now() - 86400000).toISOString().split('T')[0], // overdue
219
+ stageId: "email_sent",
220
+ clientId: "client-2",
221
+ priority: "medium",
222
+ description: "Check if Bob had time to review the technical proposal sent last week.",
223
+ notes: "Call if no email response by Friday.",
224
+ createdAt: new Date(Date.now() - 259200000).toISOString()
225
+ }
226
+ },
227
+ {
228
+ id: "task-5",
229
+ values: {
230
+ title: "Review deliverables and request feedback",
231
+ status: "pending",
232
+ resolution: null,
233
+ dueDate: new Date(Date.now() + 432000000).toISOString().split('T')[0],
234
+ stageId: "active",
235
+ clientId: "client-5",
236
+ priority: "low",
237
+ description: "Schedule final demo review, submit deliverables reports, and email feedback questionnaire.",
238
+ notes: "",
239
+ createdAt: new Date(Date.now() - 86400000).toISOString()
240
+ }
241
+ }
242
+ ];
243
+
244
+ const INITIAL_COMPLETED_TASKS: Task[] = [
245
+ {
246
+ id: "task-comp-1",
247
+ values: {
248
+ title: "Review inbound request form",
249
+ status: "completed",
250
+ resolution: "verified",
251
+ dueDate: new Date(Date.now() - 259200000).toISOString().split('T')[0],
252
+ stageId: "incoming",
253
+ clientId: "client-1",
254
+ priority: "medium",
255
+ description: "Analyze initial request details submitted via web form.",
256
+ notes: "",
257
+ createdAt: new Date(Date.now() - 345600000).toISOString()
258
+ }
259
+ },
260
+ {
261
+ id: "task-comp-2",
262
+ values: {
263
+ title: "Send pricing brochure",
264
+ status: "completed",
265
+ resolution: "sent",
266
+ dueDate: new Date(Date.now() - 172800000).toISOString().split('T')[0],
267
+ stageId: "email_sent",
268
+ clientId: "client-2",
269
+ priority: "medium",
270
+ description: "Send standard PDF booklet outlining custom packages and service tiers.",
271
+ notes: "",
272
+ createdAt: new Date(Date.now() - 259200000).toISOString()
273
+ }
274
+ }
275
+ ];
276
+
277
+ const INITIAL_ACTIVITIES: Activity[] = [
278
+ {
279
+ id: "act-1",
280
+ values: {
281
+ type: "email_sent",
282
+ description: "Intro Email sent to Bob Smith",
283
+ createdAt: new Date(Date.now() - 3600000).toISOString(),
284
+ metadata: {
285
+ subject: "Welcome to Rebase — Pricing Tiers & Guidelines",
286
+ body: "Hi Bob,\n\nThanks for your interest in Rebase! I have attached our standard pricing sheet and product documentation for your review.\n\nLet's schedule a brief 15-minute call next week to answer any questions you might have.\n\nBest regards,\nFrancesco",
287
+ to: "bob@smith.co",
288
+ mode: "simulated"
289
+ }
290
+ }
291
+ },
292
+ {
293
+ id: "act-2",
294
+ values: {
295
+ type: "agreements_sent",
296
+ description: "Agreements sent to Carol White",
297
+ createdAt: new Date(Date.now() - 14400000).toISOString(),
298
+ metadata: {
299
+ taskId: "task-2",
300
+ taskTitle: "Draft partnership agreement"
301
+ }
302
+ }
303
+ },
304
+ {
305
+ id: "act-3",
306
+ values: {
307
+ type: "incoming",
308
+ description: "Incoming lead Alice Johnson created",
309
+ createdAt: new Date(Date.now() - 86400000).toISOString()
310
+ }
311
+ },
312
+ {
313
+ id: "act-4",
314
+ values: {
315
+ type: "note",
316
+ description: "Added a note for David Brown: 'Prefers Slack for communications'",
317
+ createdAt: new Date(Date.now() - 172800000).toISOString()
318
+ }
319
+ }
320
+ ];
321
+
322
+ /* ── Helpers ────────────────────────────────────────────── */
323
+
324
+ function getGreeting(): string {
325
+ const hour = new Date().getHours();
326
+ if (hour < 12) return "Good morning";
327
+ if (hour < 18) return "Good afternoon";
328
+ return "Good evening";
329
+ }
330
+
331
+ function getFormattedDate(): string {
332
+ return new Date().toLocaleDateString("en-US", {
333
+ weekday: "long",
334
+ month: "long",
335
+ day: "numeric"
336
+ });
337
+ }
338
+
339
+ /* ── Main Component ─────────────────────────────────────── */
340
+
341
+ export function CrmDashboardDemo() {
342
+ const [loading, setLoading] = useState(true);
343
+ const [activitiesLoading, setActivitiesLoading] = useState(true);
344
+
345
+ // Persisted states for mock DB
346
+ const [clients, setClients] = useState<Client[]>(INITIAL_CLIENTS);
347
+ const [tasks, setTasks] = useState<Task[]>(INITIAL_TASKS);
348
+ const [completedTasks, setCompletedTasks] = useState<Task[]>(INITIAL_COMPLETED_TASKS);
349
+ const [activities, setActivities] = useState<Activity[]>(INITIAL_ACTIVITIES);
350
+
351
+ const [selectedPipelineId, setSelectedPipelineId] = useState<string>("main");
352
+
353
+ // Local Toast/Notifications
354
+ const [toastMessage, setToastMessage] = useState<string | null>(null);
355
+
356
+ // Drawer state (Mock side panel)
357
+ const [activeSideEntity, setActiveSideEntity] = useState<{
358
+ type: "task" | "client" | "new_client";
359
+ id?: string;
360
+ } | null>(null);
361
+
362
+ // Email viewer state
363
+ const [emailViewerOpen, setEmailViewerOpen] = useState(false);
364
+ const [selectedEmail, setSelectedEmail] = useState<{ to: string; subject: string; body: string; html?: string; mode?: string } | null>(null);
365
+
366
+ // Form states for adding new client
367
+ const [newClientName, setNewClientName] = useState("");
368
+ const [newClientEmail, setNewClientEmail] = useState("");
369
+ const [newClientPhone, setNewClientPhone] = useState("");
370
+ const [newClientStage, setNewClientStage] = useState("incoming");
371
+
372
+ // Toggling tasks loading animation state
373
+ const [togglingIds, setTogglingIds] = useState<Set<string>>(new Set());
374
+ const [recentlyToggledIds, setRecentlyToggledIds] = useState<Set<string>>(new Set());
375
+
376
+ // Completed section pagination
377
+ const [completedLoaded, setCompletedLoaded] = useState(false);
378
+ const [loadingCompleted, setLoadingCompleted] = useState(false);
379
+
380
+ // Trigger initial load skeletons
381
+ useEffect(() => {
382
+ const timer1 = setTimeout(() => setLoading(false), 450);
383
+ const timer2 = setTimeout(() => setActivitiesLoading(false), 600);
384
+ return () => {
385
+ clearTimeout(timer1);
386
+ clearTimeout(timer2);
387
+ };
388
+ }, []);
389
+
390
+ // Show custom toast alert
391
+ const showToast = useCallback((msg: string) => {
392
+ setToastMessage(msg);
393
+ const timer = setTimeout(() => setToastMessage(null), 3000);
394
+ return () => clearTimeout(timer);
395
+ }, []);
396
+
397
+ // Load completed tasks simulation
398
+ const handleExpandCompleted = useCallback(() => {
399
+ if (completedLoaded) return;
400
+ setLoadingCompleted(true);
401
+ setTimeout(() => {
402
+ setCompletedLoaded(true);
403
+ setLoadingCompleted(false);
404
+ }, 300);
405
+ }, [completedLoaded]);
406
+
407
+ const handleLoadMoreCompleted = useCallback(() => {
408
+ setLoadingCompleted(true);
409
+ setTimeout(() => {
410
+ setLoadingCompleted(false);
411
+ showToast("Loaded additional completed tasks.");
412
+ }, 300);
413
+ }, [showToast]);
414
+
415
+ // Handle pipeline change
416
+ const handlePipelineChange = useCallback((pipelineId: string) => {
417
+ setSelectedPipelineId(pipelineId);
418
+ setLoading(true);
419
+ setTimeout(() => {
420
+ setLoading(false);
421
+ showToast(`Switched to ${PIPELINES.find(p => p.id === pipelineId)?.name}`);
422
+ }, 200);
423
+ }, [showToast]);
424
+
425
+ // Derived maps
426
+ const clientsMap = useMemo(() => {
427
+ const map = new Map<string, { name: string; picture?: string }>();
428
+ clients.forEach(c => map.set(c.id, { name: c.name }));
429
+ return map;
430
+ }, [clients]);
431
+
432
+ const tasksMap = useMemo(() => {
433
+ const map = new Map<string, any>();
434
+ tasks.forEach(t => map.set(t.id, t));
435
+ completedTasks.forEach(t => map.set(t.id, t));
436
+ return map;
437
+ }, [tasks, completedTasks]);
438
+
439
+ // Task status toggling logic
440
+ const handleToggleTask = useCallback((taskId: string, currentStatus: string) => {
441
+ setTogglingIds(prev => new Set(prev).add(taskId));
442
+
443
+ setTimeout(() => {
444
+ if (currentStatus === "pending") {
445
+ // Complete task
446
+ const task = tasks.find(t => t.id === taskId);
447
+ if (task) {
448
+ setTasks(prev => prev.filter(t => t.id !== taskId));
449
+ const completedTask: Task = {
450
+ ...task,
451
+ values: { ...task.values, status: "completed", resolution: "done" }
452
+ };
453
+ setCompletedTasks(prev => [completedTask, ...prev]);
454
+
455
+ // Add activity
456
+ const newAct: Activity = {
457
+ id: `act-${Date.now()}`,
458
+ values: {
459
+ type: "task_completed",
460
+ description: `Task completed: "${task.values.title}"`,
461
+ createdAt: new Date().toISOString(),
462
+ metadata: {
463
+ taskId: task.id,
464
+ taskTitle: task.values.title
465
+ }
466
+ }
467
+ };
468
+ setActivities(prev => [newAct, ...prev]);
469
+ showToast(`Completed task: "${task.values.title}"`);
470
+ }
471
+ } else {
472
+ // Re-open task
473
+ const task = completedTasks.find(t => t.id === taskId);
474
+ if (task) {
475
+ setCompletedTasks(prev => prev.filter(t => t.id !== taskId));
476
+ const pendingTask: Task = {
477
+ ...task,
478
+ values: { ...task.values, status: "pending", resolution: null }
479
+ };
480
+ setTasks(prev => [pendingTask, ...prev]);
481
+
482
+ // Add activity
483
+ const newAct: Activity = {
484
+ id: `act-${Date.now()}`,
485
+ values: {
486
+ type: "task_created",
487
+ description: `Task re-opened: "${task.values.title}"`,
488
+ createdAt: new Date().toISOString(),
489
+ metadata: {
490
+ taskId: task.id,
491
+ taskTitle: task.values.title
492
+ }
493
+ }
494
+ };
495
+ setActivities(prev => [newAct, ...prev]);
496
+ showToast(`Re-opened task: "${task.values.title}"`);
497
+ }
498
+ }
499
+ setTogglingIds(prev => {
500
+ const next = new Set(prev);
501
+ next.delete(taskId);
502
+ return next;
503
+ });
504
+ }, 200);
505
+ }, [tasks, completedTasks, showToast]);
506
+
507
+ // Activity specific handlers
508
+ const handleCompleteTaskFromActivity = useCallback((activity: any) => {
509
+ const taskId = activity.values?.metadata?.taskId;
510
+ if (taskId) {
511
+ handleToggleTask(taskId, "pending");
512
+ }
513
+ }, [handleToggleTask]);
514
+
515
+ const handleViewEmail = useCallback((activity: any) => {
516
+ if (activity.values?.metadata?.subject) {
517
+ setSelectedEmail({
518
+ to: activity.values.metadata.to || "",
519
+ subject: activity.values.metadata.subject,
520
+ body: activity.values.metadata.body || "",
521
+ html: activity.values.metadata.html || "",
522
+ mode: activity.values.metadata.mode || "simulated"
523
+ });
524
+ setEmailViewerOpen(true);
525
+ }
526
+ }, []);
527
+
528
+ const handleOpenTask = useCallback((activity: any) => {
529
+ const taskId = activity.values?.metadata?.taskId;
530
+ if (taskId) {
531
+ setActiveSideEntity({ type: "task", id: taskId });
532
+ }
533
+ }, []);
534
+
535
+ // Side panel open helpers
536
+ const openTaskDrawer = useCallback((taskId: string) => {
537
+ setActiveSideEntity({ type: "task", id: taskId });
538
+ }, []);
539
+
540
+ const openClientDrawer = useCallback((clientId: string) => {
541
+ setActiveSideEntity({ type: "client", id: clientId });
542
+ }, []);
543
+
544
+ const openNewClientDrawer = useCallback(() => {
545
+ setActiveSideEntity({ type: "new_client" });
546
+ }, []);
547
+
548
+ // Create client simulation
549
+ const handleCreateClient = () => {
550
+ if (!newClientName.trim()) return;
551
+
552
+ const newId = `client-${Date.now()}`;
553
+ const newClient: Client = {
554
+ id: newId,
555
+ name: newClientName,
556
+ email: newClientEmail || `${newClientName.toLowerCase().replace(/\s+/g, ".")}@example.com`,
557
+ phone: newClientPhone || "+1 (555) 0100",
558
+ stage: newClientStage
559
+ };
560
+
561
+ setClients(prev => [...prev, newClient]);
562
+
563
+ // Add auto task
564
+ const newTask: Task = {
565
+ id: `task-${Date.now()}`,
566
+ values: {
567
+ title: `Complete discovery onboarding for ${newClientName}`,
568
+ status: "pending",
569
+ resolution: null,
570
+ dueDate: new Date(Date.now() + 259200000).toISOString().split('T')[0], // 3 days from now
571
+ stageId: newClientStage,
572
+ clientId: newId,
573
+ priority: "medium",
574
+ description: `Initial welcome packet and service review task for ${newClientName}.`,
575
+ notes: "",
576
+ createdAt: new Date().toISOString()
577
+ }
578
+ };
579
+ setTasks(prev => [newTask, ...prev]);
580
+
581
+ // Add activity
582
+ const newAct: Activity = {
583
+ id: `act-${Date.now()}`,
584
+ values: {
585
+ type: "incoming",
586
+ description: `New client "${newClientName}" registered in "${STAGE_LABELS[newClientStage] || newClientStage}"`,
587
+ createdAt: new Date().toISOString()
588
+ }
589
+ };
590
+ setActivities(prev => [newAct, ...prev]);
591
+
592
+ showToast(`Created client "${newClientName}"`);
593
+
594
+ // Reset inputs
595
+ setNewClientName("");
596
+ setNewClientEmail("");
597
+ setNewClientPhone("");
598
+ setNewClientStage("incoming");
599
+ setActiveSideEntity(null);
600
+ };
601
+
602
+ // Simulated navigation / pipeline filtering
603
+ const handleNavigate = useCallback((path: string) => {
604
+ if (path.includes("stage=")) {
605
+ // Simulated stage click filtering
606
+ showToast(`Stage link clicked: simulated filtering.`);
607
+ } else {
608
+ showToast(`Simulated navigation to: ${path}`);
609
+ }
610
+ }, [showToast]);
611
+
612
+ // Derived counts/funnel overview data
613
+ const activeStagesData = useMemo(() => {
614
+ const stagesList = INITIAL_STAGES[selectedPipelineId] || [];
615
+ return stagesList.map(s => {
616
+ const count = clients.filter(c => c.stage === s.stageKey).length;
617
+ return { ...s, count } as PipelineStage;
618
+ });
619
+ }, [clients, selectedPipelineId]);
620
+
621
+ const sortedStages = useMemo(() => {
622
+ return [...activeStagesData].sort((a, b) => a.sortOrder - b.sortOrder);
623
+ }, [activeStagesData]);
624
+
625
+ const activeStagesList = useMemo(() =>
626
+ sortedStages.filter(s => !s.isTerminal && !s.isLostStage),
627
+ [sortedStages]
628
+ );
629
+
630
+ const closedStagesList = useMemo(() =>
631
+ sortedStages.filter(s => s.isTerminal || s.isLostStage),
632
+ [sortedStages]
633
+ );
634
+
635
+ const totalInPipeline = useMemo(() => {
636
+ return clients.filter(c => c.stage !== "completed").length;
637
+ }, [clients]);
638
+
639
+ const insights = useMemo(() => {
640
+ const activeClients = clients.filter(c => c.stage !== "completed" && c.stage !== "delivered").length;
641
+ const overdueTaskCount = tasks.filter(t => {
642
+ if (t.values.status !== "pending") return false;
643
+ if (!t.values.dueDate) return false;
644
+ return new Date(t.values.dueDate) < new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());
645
+ }).length;
646
+
647
+ return {
648
+ pipelines: PIPELINES,
649
+ activeClients,
650
+ potentialRevenue: activeClients * 2500,
651
+ contractedRevenue: clients.filter(c => c.stage === "completed" || c.stage === "active").length * 8000,
652
+ overdueTaskCount
653
+ };
654
+ }, [clients, tasks]);
655
+
656
+ const totalRevenue = insights.contractedRevenue + insights.potentialRevenue;
657
+
658
+ /* ── Render Drawer Sub-panels ──────────────────────────── */
659
+
660
+ const renderDrawerTask = (taskId?: string) => {
661
+ const task = tasksMap.get(taskId || "");
662
+ if (!task) return <Typography variant="body2" color="secondary">Task not found.</Typography>;
663
+
664
+ const isCompleted = task.values.status === "completed";
665
+ const clientInfo = task.values.clientId ? clients.find(c => c.id === task.values.clientId) : null;
666
+ const stageName = task.values.stageId ? STAGE_LABELS[task.values.stageId] : "None";
667
+
668
+ return (
669
+ <div className="flex-1 space-y-4">
670
+ <div>
671
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider font-semibold block mb-1">Task Title</Typography>
672
+ <div className="flex items-start gap-2.5">
673
+ <span className="pt-1">
674
+ <input
675
+ type="checkbox"
676
+ checked={isCompleted}
677
+ onChange={() => handleToggleTask(task.id, task.values.status)}
678
+ className="h-4 w-4 rounded border-surface-300 text-primary focus:ring-primary"
679
+ />
680
+ </span>
681
+ <Typography variant="h6" className={isCompleted ? "line-through text-text-disabled dark:text-text-disabled-dark" : "text-text-primary dark:text-text-primary-dark"}>
682
+ {task.values.title}
683
+ </Typography>
684
+ </div>
685
+ </div>
686
+
687
+ <div className="grid grid-cols-2 gap-4 py-2 border-y border-surface-200 dark:border-surface-700">
688
+ <div>
689
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block">Due Date</Typography>
690
+ <Typography variant="body2" className="font-medium mt-0.5 inline-flex items-center gap-1">
691
+ <Calendar className="h-3.5 w-3.5 text-surface-400" />
692
+ {task.values.dueDate || "No due date"}
693
+ </Typography>
694
+ </div>
695
+ <div>
696
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block">Priority</Typography>
697
+ <Typography variant="body2" className="font-medium mt-0.5">
698
+ {task.values.priority === "high" ? (
699
+ <span className="text-red-500 font-semibold uppercase text-xs">High</span>
700
+ ) : task.values.priority === "medium" ? (
701
+ <span className="text-amber-500 font-semibold uppercase text-xs">Medium</span>
702
+ ) : (
703
+ <span className="text-surface-450 dark:text-surface-500 uppercase text-xs">Low</span>
704
+ )}
705
+ </Typography>
706
+ </div>
707
+ </div>
708
+
709
+ {clientInfo && (
710
+ <div>
711
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider font-semibold block mb-1">Related Client</Typography>
712
+ <Card
713
+ className="p-3 hover:bg-surface-accent-100 dark:hover:bg-surface-800 cursor-pointer transition-colors duration-150"
714
+ onClick={() => openClientDrawer(clientInfo.id)}
715
+ >
716
+ <div className="flex items-center gap-2.5">
717
+ <div className="w-8 h-8 rounded-full bg-surface-200 dark:bg-surface-700 text-surface-500 flex items-center justify-center font-bold">
718
+ {clientInfo.name[0].toUpperCase()}
719
+ </div>
720
+ <div className="flex-1 min-w-0">
721
+ <Typography variant="body2" className="font-medium truncate">{clientInfo.name}</Typography>
722
+ <Typography variant="caption" color="secondary" className="truncate text-[11px] block">{clientInfo.email}</Typography>
723
+ </div>
724
+ </div>
725
+ </Card>
726
+ </div>
727
+ )}
728
+
729
+ <div>
730
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block mb-0.5">Pipeline Stage</Typography>
731
+ <Typography variant="body2" className="font-medium">{stageName}</Typography>
732
+ </div>
733
+
734
+ {task.values.description && (
735
+ <div>
736
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block mb-0.5">Description</Typography>
737
+ <div className="p-3 bg-surface-50 dark:bg-surface-900 rounded-md border border-surface-200 dark:border-surface-700">
738
+ <Typography variant="body2" className="whitespace-pre-wrap text-text-primary dark:text-text-primary-dark">
739
+ {task.values.description}
740
+ </Typography>
741
+ </div>
742
+ </div>
743
+ )}
744
+
745
+ {task.values.notes && (
746
+ <div>
747
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block mb-0.5">Notes</Typography>
748
+ <Typography variant="body2" color="secondary" className="italic whitespace-pre-wrap">
749
+ &ldquo;{task.values.notes}&rdquo;
750
+ </Typography>
751
+ </div>
752
+ )}
753
+ </div>
754
+ );
755
+ };
756
+
757
+ const renderDrawerClient = (clientId?: string) => {
758
+ const client = clients.find(c => c.id === clientId);
759
+ if (!client) return <Typography variant="body2" color="secondary">Client not found.</Typography>;
760
+
761
+ const clientTasks = tasks.filter(t => t.values.clientId === client.id);
762
+ const stageName = STAGE_LABELS[client.stage] || client.stage;
763
+
764
+ return (
765
+ <div className="flex-1 space-y-4">
766
+ <div className="flex items-center gap-3">
767
+ <div className="w-12 h-12 rounded-full bg-primary/10 text-primary dark:text-primary-light flex items-center justify-center text-xl font-bold">
768
+ {client.name[0].toUpperCase()}
769
+ </div>
770
+ <div className="flex-1 min-w-0">
771
+ <Typography variant="h6" className="truncate">{client.name}</Typography>
772
+ <div className="flex items-center gap-1.5 mt-0.5">
773
+ <span className="w-2 h-2 rounded-full bg-green-500" />
774
+ <Typography variant="caption" color="secondary">{stageName}</Typography>
775
+ </div>
776
+ </div>
777
+ </div>
778
+
779
+ <div className="space-y-2.5 py-3 border-y border-surface-200 dark:border-surface-700">
780
+ <div className="flex items-center gap-2 text-surface-600 dark:text-surface-300">
781
+ <Mail className="h-4 w-4 text-surface-400" />
782
+ <Typography variant="body2">{client.email}</Typography>
783
+ </div>
784
+ <div className="flex items-center gap-2 text-surface-600 dark:text-surface-300">
785
+ <Phone className="h-4 w-4 text-surface-400" />
786
+ <Typography variant="body2">{client.phone}</Typography>
787
+ </div>
788
+ </div>
789
+
790
+ <div>
791
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider font-semibold block mb-2">Pending Client Tasks</Typography>
792
+ {clientTasks.length === 0 ? (
793
+ <Typography variant="body2" color="secondary" className="italic">No pending tasks for this client.</Typography>
794
+ ) : (
795
+ <div className="space-y-1.5">
796
+ {clientTasks.map(t => (
797
+ <Card
798
+ key={t.id}
799
+ className="p-2.5 hover:bg-surface-accent-100 dark:hover:bg-surface-800 cursor-pointer transition-colors duration-150"
800
+ onClick={() => openTaskDrawer(t.id)}
801
+ >
802
+ <div className="flex items-start gap-2">
803
+ <input
804
+ type="checkbox"
805
+ checked={t.values.status === "completed"}
806
+ onChange={(e) => {
807
+ e.stopPropagation();
808
+ handleToggleTask(t.id, t.values.status);
809
+ }}
810
+ className="h-3.5 w-3.5 mt-0.5 rounded border-surface-300 text-primary"
811
+ />
812
+ <div className="flex-1 min-w-0">
813
+ <Typography variant="body2" className="truncate font-medium leading-snug">{t.values.title}</Typography>
814
+ {t.values.dueDate && (
815
+ <Typography variant="caption" color="secondary" className="text-[10px] block mt-0.5">Due: {t.values.dueDate}</Typography>
816
+ )}
817
+ </div>
818
+ </div>
819
+ </Card>
820
+ ))}
821
+ </div>
822
+ )}
823
+ </div>
824
+
825
+ <div className="pt-2">
826
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider font-semibold block mb-2">Quick Actions</Typography>
827
+ <div className="grid grid-cols-2 gap-2">
828
+ <Button
829
+ variant="outlined"
830
+ size="small"
831
+ onClick={() => {
832
+ // Add simulated call note
833
+ const newAct: Activity = {
834
+ id: `act-${Date.now()}`,
835
+ values: {
836
+ type: "zoom_setup",
837
+ description: `Scheduled discovery meeting with ${client.name}`,
838
+ createdAt: new Date().toISOString()
839
+ }
840
+ };
841
+ setActivities(prev => [newAct, ...prev]);
842
+ showToast("Simulated action: Call scheduled");
843
+ }}
844
+ >
845
+ Schedule Call
846
+ </Button>
847
+ <Button
848
+ variant="outlined"
849
+ size="small"
850
+ onClick={() => {
851
+ // Add simulated email note
852
+ const newAct: Activity = {
853
+ id: `act-${Date.now()}`,
854
+ values: {
855
+ type: "email_sent",
856
+ description: `Email sent to ${client.name}`,
857
+ createdAt: new Date().toISOString(),
858
+ metadata: {
859
+ subject: "Following up on discovery call",
860
+ body: `Hi ${client.name.split(" ")[0]},\n\nJust wanted to follow up and see if you had any questions on the brochure.\n\nBest,\nFrancesco`,
861
+ to: client.email,
862
+ mode: "simulated"
863
+ }
864
+ }
865
+ };
866
+ setActivities(prev => [newAct, ...prev]);
867
+ showToast("Simulated action: Email sent");
868
+ }}
869
+ >
870
+ Send Email
871
+ </Button>
872
+ </div>
873
+ </div>
874
+ </div>
875
+ );
876
+ };
877
+
878
+ const renderDrawerNewClient = () => {
879
+ return (
880
+ <div className="flex-1 flex flex-col justify-between">
881
+ <div className="space-y-4">
882
+ <Typography variant="subtitle1" className="mb-2">New Lead Details</Typography>
883
+
884
+ <TextField
885
+ label="Client Name"
886
+ required
887
+ value={newClientName}
888
+ onChange={e => setNewClientName(e.target.value)}
889
+ placeholder="e.g. George Clark"
890
+ />
891
+
892
+ <TextField
893
+ label="Email Address"
894
+ value={newClientEmail}
895
+ onChange={e => setNewClientEmail(e.target.value)}
896
+ placeholder="e.g. george@clark.com"
897
+ />
898
+
899
+ <TextField
900
+ label="Phone Number"
901
+ value={newClientPhone}
902
+ onChange={e => setNewClientPhone(e.target.value)}
903
+ placeholder="e.g. +1 (555) 0122"
904
+ />
905
+
906
+ <div>
907
+ <Typography variant="caption" color="secondary" className="uppercase text-[9px] tracking-wider block mb-1">Pipeline Stage</Typography>
908
+ <Select
909
+ value={newClientStage}
910
+ onValueChange={(v) => setNewClientStage(v as string)}
911
+ size="small"
912
+ renderValue={(v) => STAGE_LABELS[v as string] || v as string}
913
+ position="popper"
914
+ >
915
+ {Object.entries(STAGE_LABELS).map(([key, val]) => (
916
+ <SelectItem key={key} value={key}>{val}</SelectItem>
917
+ ))}
918
+ </Select>
919
+ </div>
920
+ </div>
921
+
922
+ <div className="flex items-center gap-2 pt-6 border-t border-surface-200 dark:border-surface-700 mt-6">
923
+ <Button
924
+ variant="outlined"
925
+ color="neutral"
926
+ className="flex-1"
927
+ onClick={() => setActiveSideEntity(null)}
928
+ >
929
+ Cancel
930
+ </Button>
931
+ <Button
932
+ variant="filled"
933
+ color="primary"
934
+ className="flex-1"
935
+ onClick={handleCreateClient}
936
+ disabled={!newClientName.trim()}
937
+ >
938
+ Create
939
+ </Button>
940
+ </div>
941
+ </div>
942
+ );
943
+ };
944
+
945
+ return (
946
+ <div className="relative w-full border rounded-xl bg-surface-50 dark:bg-surface-800 border-surface-200 dark:border-surface-700 shadow-sm flex flex-row">
947
+ {/* Dashboard main workspace */}
948
+ <div className="flex-1 flex flex-col p-4 md:p-6 transition-all duration-300">
949
+ {/* Header */}
950
+ <div className="flex flex-col sm:flex-row sm:items-end justify-between mb-6 gap-4">
951
+ <div>
952
+ <Typography variant="h4" className="tracking-tight">
953
+ {getGreeting()} 👋
954
+ </Typography>
955
+ <Typography variant="body2" color="secondary" className="mt-0.5">
956
+ {getFormattedDate()} — here&apos;s your crm overview.
957
+ </Typography>
958
+ </div>
959
+ <div className="flex items-center gap-2">
960
+ <Button
961
+ variant="text"
962
+ color="primary"
963
+ size="small"
964
+ onClick={() => {
965
+ setLoading(true);
966
+ setActivitiesLoading(true);
967
+ setTimeout(() => {
968
+ setLoading(false);
969
+ setActivitiesLoading(false);
970
+ showToast("Data refreshed.");
971
+ }, 300);
972
+ }}
973
+ disabled={loading}
974
+ startIcon={<RefreshCw className={cls("h-4 w-4", loading && "animate-spin")} />}
975
+ >
976
+ Refresh
977
+ </Button>
978
+ <Button
979
+ variant="filled"
980
+ color="primary"
981
+ size="small"
982
+ onClick={openNewClientDrawer}
983
+ startIcon={<UserPlus className="h-4 w-4" />}
984
+ >
985
+ New Client
986
+ </Button>
987
+ </div>
988
+ </div>
989
+
990
+ {/* KPI metrics row */}
991
+ <DashboardMetrics
992
+ loading={loading}
993
+ insights={insights}
994
+ totalInPipeline={totalInPipeline}
995
+ totalRevenue={totalRevenue}
996
+ />
997
+
998
+ {/* Pipeline visualizer */}
999
+ <PipelineOverview
1000
+ loading={loading}
1001
+ insights={insights}
1002
+ selectedPipelineId={selectedPipelineId}
1003
+ onPipelineChange={handlePipelineChange}
1004
+ onNavigate={handleNavigate}
1005
+ sortedStages={sortedStages}
1006
+ activeStages={activeStagesList}
1007
+ closedStages={closedStagesList}
1008
+ totalInPipeline={totalInPipeline}
1009
+ resolveColor={resolveColor}
1010
+ />
1011
+
1012
+ {/* Combined Tasks, Calendar, and RecentActivity */}
1013
+ <div className="grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6 mt-2">
1014
+ <Card className="p-4 flex flex-col h-[400px]">
1015
+ <TasksView
1016
+ loading={loading}
1017
+ tasks={tasks}
1018
+ togglingIds={togglingIds}
1019
+ onToggleTask={handleToggleTask}
1020
+ onOpenTask={openTaskDrawer}
1021
+ onOpenClient={openClientDrawer}
1022
+ clientsMap={clientsMap}
1023
+ pipelineStages={sortedStages}
1024
+ recentlyToggledIds={recentlyToggledIds}
1025
+ completedTasks={completedTasks}
1026
+ completedLoaded={completedLoaded}
1027
+ loadingCompleted={loadingCompleted}
1028
+ completedHasMore={false}
1029
+ onExpandCompleted={handleExpandCompleted}
1030
+ onLoadMoreCompleted={handleLoadMoreCompleted}
1031
+ />
1032
+ </Card>
1033
+
1034
+ <div className="flex flex-col gap-6">
1035
+ <Card className="p-4 h-auto">
1036
+ <CalendarWidget
1037
+ loading={loading}
1038
+ tasks={tasks.concat(completedTasks)}
1039
+ onOpenTask={openTaskDrawer}
1040
+ />
1041
+ </Card>
1042
+ <RecentActivity
1043
+ activities={activities}
1044
+ activitiesLoading={activitiesLoading}
1045
+ tasksMap={tasksMap}
1046
+ onCompleteTask={handleCompleteTaskFromActivity}
1047
+ onViewEmail={handleViewEmail}
1048
+ onOpenTask={handleOpenTask}
1049
+ emailViewerOpen={emailViewerOpen}
1050
+ onEmailViewerOpenChange={setEmailViewerOpen}
1051
+ selectedEmail={selectedEmail}
1052
+ />
1053
+ </div>
1054
+ </div>
1055
+ </div>
1056
+
1057
+ {/* Custom Mock Side panel sliding in */}
1058
+ <div className={cls(
1059
+ "fixed top-0 right-0 bottom-0 w-full sm:w-[420px] bg-white dark:bg-surface-900 border-l border-surface-200 dark:border-surface-700 shadow-2xl z-40 transition-transform duration-300 ease-in-out flex flex-col",
1060
+ activeSideEntity ? "translate-x-0" : "translate-x-full"
1061
+ )}>
1062
+ <div className="p-4 flex items-center justify-between border-b border-surface-200 dark:border-surface-800">
1063
+ <Typography variant="subtitle1" className="font-semibold uppercase tracking-wider text-[10px] color-secondary">
1064
+ {activeSideEntity?.type === "task" ? "Task Details" : activeSideEntity?.type === "client" ? "Client profile" : "Create Lead"}
1065
+ </Typography>
1066
+ <button
1067
+ onClick={() => setActiveSideEntity(null)}
1068
+ className="p-1 rounded-md text-surface-400 hover:text-surface-700 hover:bg-surface-100 dark:hover:bg-surface-800 focus:outline-none"
1069
+ >
1070
+ <X className="h-4 w-4" />
1071
+ </button>
1072
+ </div>
1073
+ <div className="flex-1 overflow-y-auto p-5 flex flex-col">
1074
+ {activeSideEntity?.type === "task" && renderDrawerTask(activeSideEntity.id)}
1075
+ {activeSideEntity?.type === "client" && renderDrawerClient(activeSideEntity.id)}
1076
+ {activeSideEntity?.type === "new_client" && renderDrawerNewClient()}
1077
+ </div>
1078
+ </div>
1079
+
1080
+ {/* Backdrop click to close drawer */}
1081
+ {activeSideEntity && (
1082
+ <div
1083
+ onClick={() => setActiveSideEntity(null)}
1084
+ className="fixed inset-0 bg-black/20 dark:bg-black/50 z-30 transition-opacity duration-300"
1085
+ />
1086
+ )}
1087
+
1088
+ {/* Custom interactive toast popup */}
1089
+ <div className={cls(
1090
+ "absolute bottom-4 left-4 z-50 transition-all duration-300 transform",
1091
+ toastMessage ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0 pointer-events-none"
1092
+ )}>
1093
+ {toastMessage && (
1094
+ <div className="px-4 py-3 bg-surface-900 dark:bg-surface-50 text-white dark:text-surface-900 text-xs font-medium rounded-lg shadow-lg flex items-center gap-2 border border-surface-800 dark:border-surface-200">
1095
+ <Check className="h-4 w-4 text-green-500" />
1096
+ <span>{toastMessage}</span>
1097
+ </div>
1098
+ )}
1099
+ </div>
1100
+ </div>
1101
+ );
1102
+ }