@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.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/auth/api.d.ts +66 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/types.d.ts +127 -0
- package/dist/auth/useRebaseAuthController.d.ts +13 -0
- package/dist/components/AIIcon.d.ts +17 -0
- package/dist/components/AuthSimulationMenu.d.ts +0 -0
- package/dist/components/ConfirmationDialog.d.ts +9 -0
- package/dist/components/Debug/UIReferenceView.d.ts +13 -0
- package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
- package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/index.d.ts +3 -0
- package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
- package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
- package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
- package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
- package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
- package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
- package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
- package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
- package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
- package/dist/components/ErrorTooltip.d.ts +3 -0
- package/dist/components/ErrorView.d.ts +21 -0
- package/dist/components/LanguageToggle.d.ts +2 -0
- package/dist/components/LoginView/LoginView.d.ts +113 -0
- package/dist/components/LoginView/index.d.ts +2 -0
- package/dist/components/NotFoundPage.d.ts +2 -0
- package/dist/components/RebaseAuth.d.ts +10 -0
- package/dist/components/RebaseLogo.d.ts +7 -0
- package/dist/components/SchemaDriftBanner.d.ts +27 -0
- package/dist/components/UnsavedChangesDialog.d.ts +9 -0
- package/dist/components/UserDisplay.d.ts +7 -0
- package/dist/components/UserSelectPopover.d.ts +63 -0
- package/dist/components/UserSettingsView.d.ts +2 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/table_height.d.ts +5 -0
- package/dist/components/common/types.d.ts +66 -0
- package/dist/components/common/useColumnsIds.d.ts +9 -0
- package/dist/components/common/useDataTableController.d.ts +45 -0
- package/dist/components/common/useDebouncedData.d.ts +9 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/contexts/AdminModeController.d.ts +4 -0
- package/dist/contexts/AnalyticsContext.d.ts +3 -0
- package/dist/contexts/AuthControllerContext.d.ts +3 -0
- package/dist/contexts/CollectionScopeContext.d.ts +50 -0
- package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
- package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
- package/dist/contexts/DataDriverContext.d.ts +3 -0
- package/dist/contexts/DataSourcesContext.d.ts +31 -0
- package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
- package/dist/contexts/DialogsProvider.d.ts +4 -0
- package/dist/contexts/EffectiveRoleController.d.ts +4 -0
- package/dist/contexts/ModeController.d.ts +4 -0
- package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
- package/dist/contexts/RebaseDataContext.d.ts +3 -0
- package/dist/contexts/SnackbarProvider.d.ts +2 -0
- package/dist/contexts/StorageSourceContext.d.ts +3 -0
- package/dist/contexts/StorageSourcesContext.d.ts +25 -0
- package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
- package/dist/contexts/index.d.ts +17 -0
- package/dist/core/PluginLifecycleManager.d.ts +17 -0
- package/dist/core/PluginProviderStack.d.ts +21 -0
- package/dist/core/Rebase.d.ts +15 -0
- package/dist/core/RebaseProps.d.ts +291 -0
- package/dist/core/RebaseRouter.d.ts +4 -0
- package/dist/core/RebaseRoutes.d.ts +17 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/hooks/ApiConfigContext.d.ts +24 -0
- package/dist/hooks/data/delete.d.ts +33 -0
- package/dist/hooks/data/save.d.ts +40 -0
- package/dist/hooks/data/useCollection.d.ts +62 -0
- package/dist/hooks/data/useData.d.ts +13 -0
- package/dist/hooks/data/useDataOrder.d.ts +12 -0
- package/dist/hooks/data/useFetch.d.ts +43 -0
- package/dist/hooks/data/useRelationSelector.d.ts +52 -0
- package/dist/hooks/index.d.ts +36 -0
- package/dist/hooks/useAdminModeController.d.ts +19 -0
- package/dist/hooks/useAnalyticsController.d.ts +5 -0
- package/dist/hooks/useAuthController.d.ts +11 -0
- package/dist/hooks/useAuthSubscription.d.ts +2 -0
- package/dist/hooks/useBackendStorageSource.d.ts +30 -0
- package/dist/hooks/useBridgeRegistration.d.ts +18 -0
- package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
- package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
- package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
- package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
- package/dist/hooks/useBuildModeController.d.ts +6 -0
- package/dist/hooks/useClipboard.d.ts +57 -0
- package/dist/hooks/useCollapsedGroups.d.ts +27 -0
- package/dist/hooks/useComponentOverride.d.ts +32 -0
- package/dist/hooks/useCustomizationController.d.ts +11 -0
- package/dist/hooks/useDialogsController.d.ts +11 -0
- package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
- package/dist/hooks/useLargeLayout.d.ts +1 -0
- package/dist/hooks/useModeController.d.ts +19 -0
- package/dist/hooks/usePermissions.d.ts +12 -0
- package/dist/hooks/useRebaseClient.d.ts +5 -0
- package/dist/hooks/useRebaseContext.d.ts +11 -0
- package/dist/hooks/useRebaseRegistry.d.ts +34 -0
- package/dist/hooks/useResolvedComponent.d.ts +47 -0
- package/dist/hooks/useSlot.d.ts +18 -0
- package/dist/hooks/useSnackbarController.d.ts +20 -0
- package/dist/hooks/useStorageSource.d.ts +6 -0
- package/dist/hooks/useStudioBridge.d.ts +79 -0
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
- package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
- package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.es.js +16843 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +17021 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/internal/common.d.ts +14 -0
- package/dist/internal/useRestoreScroll.d.ts +15 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- package/dist/util/constants.d.ts +1 -0
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +22 -0
- package/dist/util/enums.d.ts +5 -0
- package/dist/util/icon_list.d.ts +5 -0
- package/dist/util/icons.d.ts +20 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/previews.d.ts +4 -0
- package/dist/util/useStorageUploadController.d.ts +44 -0
- package/dist/vitePlugin.d.ts +60 -0
- package/dist/vitePlugin.js +113 -0
- package/package.json +145 -0
- package/src/auth/api.ts +148 -0
- package/src/auth/index.ts +25 -0
- package/src/auth/types.ts +115 -0
- package/src/auth/useRebaseAuthController.ts +328 -0
- package/src/components/AIIcon.tsx +47 -0
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +48 -0
- package/src/components/Debug/UIReferenceView.tsx +958 -0
- package/src/components/Debug/UIStyleGuide.tsx +156 -0
- package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
- package/src/components/Debug/collection-views/index.ts +3 -0
- package/src/components/Debug/collection-views/sample_data.ts +42 -0
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
- package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
- package/src/components/ErrorTooltip.tsx +12 -0
- package/src/components/ErrorView.tsx +94 -0
- package/src/components/LanguageToggle.tsx +59 -0
- package/src/components/LoginView/LoginView.tsx +932 -0
- package/src/components/LoginView/index.ts +2 -0
- package/src/components/NotFoundPage.tsx +27 -0
- package/src/components/RebaseAuth.tsx +27 -0
- package/src/components/RebaseLogo.tsx +29 -0
- package/src/components/SchemaDriftBanner.tsx +102 -0
- package/src/components/UnsavedChangesDialog.tsx +46 -0
- package/src/components/UserDisplay.tsx +54 -0
- package/src/components/UserSelectPopover.tsx +399 -0
- package/src/components/UserSettingsView.tsx +318 -0
- package/src/components/common/index.ts +6 -0
- package/src/components/common/table_height.tsx +21 -0
- package/src/components/common/types.tsx +66 -0
- package/src/components/common/useColumnsIds.tsx +222 -0
- package/src/components/common/useDataTableController.tsx +514 -0
- package/src/components/common/useDebouncedData.ts +49 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/components/index.tsx +26 -0
- package/src/contexts/AdminModeController.tsx +11 -0
- package/src/contexts/AnalyticsContext.tsx +4 -0
- package/src/contexts/AuthControllerContext.tsx +4 -0
- package/src/contexts/CollectionScopeContext.tsx +67 -0
- package/src/contexts/ComponentOverrideContext.tsx +81 -0
- package/src/contexts/CustomizationControllerContext.tsx +4 -0
- package/src/contexts/DataDriverContext.tsx +4 -0
- package/src/contexts/DataSourcesContext.tsx +36 -0
- package/src/contexts/DatabaseAdminContext.tsx +4 -0
- package/src/contexts/DialogsProvider.tsx +56 -0
- package/src/contexts/EffectiveRoleController.tsx +12 -0
- package/src/contexts/ModeController.tsx +11 -0
- package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
- package/src/contexts/RebaseDataContext.tsx +4 -0
- package/src/contexts/SnackbarProvider.tsx +15 -0
- package/src/contexts/StorageSourceContext.tsx +4 -0
- package/src/contexts/StorageSourcesContext.tsx +30 -0
- package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
- package/src/contexts/index.ts +17 -0
- package/src/core/PluginLifecycleManager.tsx +95 -0
- package/src/core/PluginProviderStack.tsx +40 -0
- package/src/core/Rebase.tsx +439 -0
- package/src/core/RebaseProps.tsx +320 -0
- package/src/core/RebaseRouter.tsx +17 -0
- package/src/core/RebaseRoutes.tsx +24 -0
- package/src/core/index.tsx +5 -0
- package/src/hooks/ApiConfigContext.tsx +41 -0
- package/src/hooks/data/delete.ts +55 -0
- package/src/hooks/data/save.ts +77 -0
- package/src/hooks/data/useCollection.tsx +176 -0
- package/src/hooks/data/useData.tsx +18 -0
- package/src/hooks/data/useDataOrder.ts +26 -0
- package/src/hooks/data/useFetch.tsx +140 -0
- package/src/hooks/data/useRelationSelector.tsx +234 -0
- package/src/hooks/index.tsx +45 -0
- package/src/hooks/useAdminModeController.tsx +23 -0
- package/src/hooks/useAnalyticsController.tsx +8 -0
- package/src/hooks/useAuthController.tsx +14 -0
- package/src/hooks/useAuthSubscription.ts +87 -0
- package/src/hooks/useBackendStorageSource.ts +288 -0
- package/src/hooks/useBridgeRegistration.tsx +32 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
- package/src/hooks/useBuildAdminModeController.tsx +24 -0
- package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
- package/src/hooks/useBuildModeController.tsx +71 -0
- package/src/hooks/useClipboard.tsx +158 -0
- package/src/hooks/useCollapsedGroups.ts +116 -0
- package/src/hooks/useComponentOverride.tsx +59 -0
- package/src/hooks/useCustomizationController.tsx +14 -0
- package/src/hooks/useDialogsController.tsx +15 -0
- package/src/hooks/useEffectiveRoleController.tsx +10 -0
- package/src/hooks/useLargeLayout.tsx +65 -0
- package/src/hooks/useModeController.tsx +23 -0
- package/src/hooks/usePermissions.ts +44 -0
- package/src/hooks/useRebaseClient.tsx +10 -0
- package/src/hooks/useRebaseContext.tsx +83 -0
- package/src/hooks/useRebaseRegistry.tsx +91 -0
- package/src/hooks/useResolvedComponent.tsx +159 -0
- package/src/hooks/useSlot.tsx +64 -0
- package/src/hooks/useSnackbarController.tsx +52 -0
- package/src/hooks/useStorageSource.tsx +11 -0
- package/src/hooks/useStudioBridge.tsx +209 -0
- package/src/hooks/useTranslation.ts +33 -0
- package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
- package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
- package/src/i18n/RebaseI18nProvider.tsx +160 -0
- package/src/index.ts +25 -0
- package/src/internal/common.tsx +16 -0
- package/src/internal/useRestoreScroll.tsx +69 -0
- package/src/locales/de.ts +902 -0
- package/src/locales/en.ts +980 -0
- package/src/locales/es.ts +922 -0
- package/src/locales/fr.ts +901 -0
- package/src/locales/hi.ts +901 -0
- package/src/locales/it.ts +901 -0
- package/src/locales/pt.ts +909 -0
- package/src/util/constants.ts +7 -0
- package/src/util/createFormexStub.tsx +66 -0
- package/src/util/entity_cache.ts +239 -0
- package/src/util/enums.ts +32 -0
- package/src/util/icon_list.ts +22 -0
- package/src/util/icons.tsx +149 -0
- package/src/util/index.ts +12 -0
- package/src/util/previews.ts +105 -0
- package/src/util/useStorageUploadController.tsx +350 -0
- 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
|
+
“{task.values.notes}”
|
|
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'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
|
+
}
|