@rebasepro/app 0.17.3 → 0.18.0

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