@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,133 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Typography,
|
|
4
|
+
cls,
|
|
5
|
+
Card,
|
|
6
|
+
Skeleton
|
|
7
|
+
} from "@rebasepro/ui";
|
|
8
|
+
import {
|
|
9
|
+
Users as UsersIcon,
|
|
10
|
+
DollarSign as DollarSignIcon,
|
|
11
|
+
AlertTriangle,
|
|
12
|
+
TrendingUp
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
|
|
15
|
+
/* ── Types ────────────────────────────────────────────── */
|
|
16
|
+
|
|
17
|
+
interface InsightsData {
|
|
18
|
+
activeClients: number;
|
|
19
|
+
potentialRevenue: number;
|
|
20
|
+
contractedRevenue: number;
|
|
21
|
+
overdueTaskCount: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface DashboardMetricsProps {
|
|
25
|
+
loading: boolean;
|
|
26
|
+
insights: InsightsData | null;
|
|
27
|
+
totalInPipeline: number;
|
|
28
|
+
totalRevenue: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ── Component ────────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
export function DashboardMetrics({
|
|
34
|
+
loading,
|
|
35
|
+
insights,
|
|
36
|
+
totalInPipeline,
|
|
37
|
+
totalRevenue
|
|
38
|
+
}: DashboardMetricsProps) {
|
|
39
|
+
return (
|
|
40
|
+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
|
41
|
+
{/* In Pipeline */}
|
|
42
|
+
<Card className="p-4">
|
|
43
|
+
<div className="flex items-center gap-3">
|
|
44
|
+
<div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500">
|
|
45
|
+
<UsersIcon className="h-4 w-4" />
|
|
46
|
+
</div>
|
|
47
|
+
<div className="flex-1 min-w-0">
|
|
48
|
+
<Typography variant="caption" color="secondary" className="uppercase tracking-[0.05em] text-[10px]">In Pipeline</Typography>
|
|
49
|
+
{loading ? (
|
|
50
|
+
<Skeleton className="h-7 w-12 mt-0.5" />
|
|
51
|
+
) : (
|
|
52
|
+
<Typography variant="h5" className="tabular-nums mt-0.5">
|
|
53
|
+
{totalInPipeline}
|
|
54
|
+
</Typography>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</Card>
|
|
59
|
+
|
|
60
|
+
{/* Active Clients */}
|
|
61
|
+
<Card className="p-4">
|
|
62
|
+
<div className="flex items-center gap-3">
|
|
63
|
+
<div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500">
|
|
64
|
+
<TrendingUp className="h-4 w-4" />
|
|
65
|
+
</div>
|
|
66
|
+
<div className="flex-1 min-w-0">
|
|
67
|
+
<Typography variant="caption" color="secondary" className="uppercase tracking-[0.05em] text-[10px]">Active Clients</Typography>
|
|
68
|
+
{loading ? (
|
|
69
|
+
<Skeleton className="h-7 w-12 mt-0.5" />
|
|
70
|
+
) : (
|
|
71
|
+
<Typography variant="h5" className="tabular-nums mt-0.5">
|
|
72
|
+
{insights?.activeClients ?? 0}
|
|
73
|
+
</Typography>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</Card>
|
|
78
|
+
|
|
79
|
+
{/* Total Revenue */}
|
|
80
|
+
<Card className="p-4">
|
|
81
|
+
<div className="flex items-center gap-3">
|
|
82
|
+
<div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500">
|
|
83
|
+
<DollarSignIcon className="h-4 w-4" />
|
|
84
|
+
</div>
|
|
85
|
+
<div className="flex-1 min-w-0">
|
|
86
|
+
<Typography variant="caption" color="secondary" className="uppercase tracking-[0.05em] text-[10px]">Revenue</Typography>
|
|
87
|
+
{loading ? (
|
|
88
|
+
<Skeleton className="h-7 w-20 mt-0.5" />
|
|
89
|
+
) : (
|
|
90
|
+
<div className="flex flex-col sm:flex-row sm:items-baseline gap-1 sm:gap-2 mt-0.5">
|
|
91
|
+
<Typography variant="h5" className="tabular-nums">
|
|
92
|
+
${totalRevenue.toLocaleString()}
|
|
93
|
+
</Typography>
|
|
94
|
+
{insights && insights.contractedRevenue > 0 && (
|
|
95
|
+
<Typography variant="caption" color="secondary" className="text-[10px]">
|
|
96
|
+
(${insights.contractedRevenue.toLocaleString()} confirmed)
|
|
97
|
+
</Typography>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</Card>
|
|
104
|
+
|
|
105
|
+
{/* Overdue Tasks */}
|
|
106
|
+
<Card className="p-4">
|
|
107
|
+
<div className="flex items-center gap-3">
|
|
108
|
+
<div className={cls(
|
|
109
|
+
"flex items-center justify-center w-5 h-5",
|
|
110
|
+
insights && insights.overdueTaskCount > 0
|
|
111
|
+
? "text-red-500"
|
|
112
|
+
: "text-surface-400 dark:text-surface-500"
|
|
113
|
+
)}>
|
|
114
|
+
<AlertTriangle className="h-4 w-4" />
|
|
115
|
+
</div>
|
|
116
|
+
<div className="flex-1 min-w-0">
|
|
117
|
+
<Typography variant="caption" color="secondary" className="uppercase tracking-[0.05em] text-[10px]">Overdue Tasks</Typography>
|
|
118
|
+
{loading ? (
|
|
119
|
+
<Skeleton className="h-7 w-8 mt-0.5" />
|
|
120
|
+
) : (
|
|
121
|
+
<Typography variant="h5" className={cls(
|
|
122
|
+
"tabular-nums mt-0.5",
|
|
123
|
+
insights && insights.overdueTaskCount > 0 && "text-red-600 dark:text-red-400"
|
|
124
|
+
)}>
|
|
125
|
+
{insights?.overdueTaskCount ?? 0}
|
|
126
|
+
</Typography>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</Card>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Typography,
|
|
4
|
+
cls,
|
|
5
|
+
Card,
|
|
6
|
+
Button,
|
|
7
|
+
Skeleton,
|
|
8
|
+
Tooltip,
|
|
9
|
+
Select,
|
|
10
|
+
SelectItem
|
|
11
|
+
} from "@rebasepro/ui";
|
|
12
|
+
import {
|
|
13
|
+
Users as UsersIcon,
|
|
14
|
+
ChevronRight,
|
|
15
|
+
Settings
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
|
|
18
|
+
/* ── Types ────────────────────────────────────────────── */
|
|
19
|
+
|
|
20
|
+
interface PipelineStage {
|
|
21
|
+
stageKey: string;
|
|
22
|
+
label: string;
|
|
23
|
+
shortLabel: string;
|
|
24
|
+
color: string;
|
|
25
|
+
icon: string;
|
|
26
|
+
sortOrder: number;
|
|
27
|
+
isTerminal: boolean;
|
|
28
|
+
isLostStage: boolean;
|
|
29
|
+
count: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PipelineOption {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
isDefault: boolean;
|
|
36
|
+
color: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PipelineOverviewProps {
|
|
40
|
+
loading: boolean;
|
|
41
|
+
insights: {
|
|
42
|
+
pipelines: PipelineOption[];
|
|
43
|
+
} | null;
|
|
44
|
+
selectedPipelineId: string | null;
|
|
45
|
+
onPipelineChange: (newPipelineId: string) => void;
|
|
46
|
+
onNavigate: (path: string) => void;
|
|
47
|
+
sortedStages: PipelineStage[];
|
|
48
|
+
activeStages: PipelineStage[];
|
|
49
|
+
closedStages: PipelineStage[];
|
|
50
|
+
totalInPipeline: number;
|
|
51
|
+
resolveColor: (colorName: string) => string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ── Component ────────────────────────────────────────── */
|
|
55
|
+
|
|
56
|
+
export function PipelineOverview({
|
|
57
|
+
loading,
|
|
58
|
+
insights,
|
|
59
|
+
selectedPipelineId,
|
|
60
|
+
onPipelineChange,
|
|
61
|
+
onNavigate,
|
|
62
|
+
sortedStages,
|
|
63
|
+
activeStages,
|
|
64
|
+
closedStages,
|
|
65
|
+
totalInPipeline,
|
|
66
|
+
resolveColor
|
|
67
|
+
}: PipelineOverviewProps) {
|
|
68
|
+
const useShortLabels = sortedStages.length > 6;
|
|
69
|
+
const needsScroll = sortedStages.length > 10;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Card className="p-4 mb-6">
|
|
73
|
+
<div className="flex items-center justify-between mb-4">
|
|
74
|
+
<div className="flex items-center gap-3">
|
|
75
|
+
<Typography variant="subtitle1">
|
|
76
|
+
Pipeline{!loading && totalInPipeline > 0 ? ` (${totalInPipeline} clients)` : ""}
|
|
77
|
+
</Typography>
|
|
78
|
+
|
|
79
|
+
{/* Pipeline selector — using @rebasepro/ui Select */}
|
|
80
|
+
{insights && insights.pipelines.length > 1 && (
|
|
81
|
+
<Select
|
|
82
|
+
value={selectedPipelineId ?? ""}
|
|
83
|
+
onValueChange={(v) => onPipelineChange(v as string)}
|
|
84
|
+
size="smallest"
|
|
85
|
+
renderValue={(v) => {
|
|
86
|
+
const selected = insights.pipelines.find(p => p.id === v);
|
|
87
|
+
return selected ? selected.name : "Pipeline";
|
|
88
|
+
}}
|
|
89
|
+
position="popper"
|
|
90
|
+
>
|
|
91
|
+
{insights.pipelines.map(p => (
|
|
92
|
+
<SelectItem key={p.id} value={p.id}>
|
|
93
|
+
{p.name}
|
|
94
|
+
</SelectItem>
|
|
95
|
+
))}
|
|
96
|
+
</Select>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
<Button
|
|
100
|
+
variant="text"
|
|
101
|
+
size="small"
|
|
102
|
+
onClick={() => { onNavigate('/c/engagements'); }}
|
|
103
|
+
endIcon={<ChevronRight className="h-4 w-4" />}
|
|
104
|
+
>
|
|
105
|
+
View All
|
|
106
|
+
</Button>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{loading ? (
|
|
110
|
+
<Skeleton className="h-20 w-full rounded-lg" />
|
|
111
|
+
) : insights && sortedStages.length === 0 ? (
|
|
112
|
+
/* No stages configured */
|
|
113
|
+
<div className="flex flex-col items-center justify-center py-8">
|
|
114
|
+
<div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 mb-3">
|
|
115
|
+
<Settings className="h-4 w-4" />
|
|
116
|
+
</div>
|
|
117
|
+
<Typography variant="body2" color="secondary" className="mb-1">
|
|
118
|
+
No pipeline stages configured
|
|
119
|
+
</Typography>
|
|
120
|
+
<Typography variant="caption" color="disabled" className="mb-4 text-center">
|
|
121
|
+
Set up your pipeline stages in Settings → Pipeline Stages
|
|
122
|
+
</Typography>
|
|
123
|
+
<Button
|
|
124
|
+
variant="outlined"
|
|
125
|
+
size="small"
|
|
126
|
+
onClick={() => onNavigate('/pipeline-builder')}
|
|
127
|
+
>
|
|
128
|
+
Configure Stages
|
|
129
|
+
</Button>
|
|
130
|
+
</div>
|
|
131
|
+
) : insights && totalInPipeline === 0 ? (
|
|
132
|
+
/* No clients in pipeline */
|
|
133
|
+
<div className="flex flex-col items-center justify-center py-8">
|
|
134
|
+
<div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 mb-3">
|
|
135
|
+
<UsersIcon className="h-4 w-4" />
|
|
136
|
+
</div>
|
|
137
|
+
<Typography variant="body2" color="secondary" className="mb-1">
|
|
138
|
+
No clients in pipeline yet
|
|
139
|
+
</Typography>
|
|
140
|
+
<Typography variant="caption" color="disabled" className="mb-4">
|
|
141
|
+
Create your first client to get started.
|
|
142
|
+
</Typography>
|
|
143
|
+
</div>
|
|
144
|
+
) : insights ? (
|
|
145
|
+
/* Pipeline bar */
|
|
146
|
+
<div className="relative">
|
|
147
|
+
{needsScroll && (
|
|
148
|
+
<>
|
|
149
|
+
<div className="absolute left-0 top-0 bottom-0 w-6 bg-gradient-to-r from-white dark:from-surface-900 to-transparent z-10 pointer-events-none rounded-l-lg" />
|
|
150
|
+
<div className="absolute right-0 top-0 bottom-0 w-6 bg-gradient-to-l from-white dark:from-surface-900 to-transparent z-10 pointer-events-none rounded-r-lg" />
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
<div className={cls(
|
|
154
|
+
"flex items-stretch gap-0",
|
|
155
|
+
needsScroll && "overflow-x-auto"
|
|
156
|
+
)}>
|
|
157
|
+
{/* Active stages */}
|
|
158
|
+
{activeStages.map((stage, idx) => {
|
|
159
|
+
const isEmpty = stage.count === 0;
|
|
160
|
+
const widthPercent = totalInPipeline > 0
|
|
161
|
+
? Math.max(8, (stage.count / totalInPipeline) * 100)
|
|
162
|
+
: 100 / Math.max(activeStages.length, 1);
|
|
163
|
+
const stageColor = resolveColor(stage.color);
|
|
164
|
+
const displayLabel = useShortLabels ? stage.shortLabel : stage.label;
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<React.Fragment key={stage.stageKey}>
|
|
168
|
+
{idx > 0 && (
|
|
169
|
+
<div className="flex items-center shrink-0 text-surface-300 dark:text-surface-600 -mx-0.5">
|
|
170
|
+
<ChevronRight className="h-4 w-4" />
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
<Tooltip title={`${stage.label} — ${stage.count} client${stage.count !== 1 ? "s" : ""}`}>
|
|
174
|
+
<div
|
|
175
|
+
className={cls(
|
|
176
|
+
"flex flex-col items-center justify-center rounded-lg px-3 py-3 min-w-[72px] transition-colors duration-150 cursor-pointer",
|
|
177
|
+
"hover:bg-surface-accent-100 dark:hover:bg-surface-800",
|
|
178
|
+
isEmpty
|
|
179
|
+
? "bg-surface-50 dark:bg-surface-900/20"
|
|
180
|
+
: "bg-surface-100/50 dark:bg-surface-900/60"
|
|
181
|
+
)}
|
|
182
|
+
style={{ flex: `${widthPercent} 1 0%` }}
|
|
183
|
+
onClick={() => onNavigate(`/c/engagements?stage=${stage.stageKey}`)}
|
|
184
|
+
>
|
|
185
|
+
<Typography
|
|
186
|
+
variant="h6"
|
|
187
|
+
className={cls(
|
|
188
|
+
"tabular-nums leading-none font-semibold",
|
|
189
|
+
isEmpty && "opacity-25"
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
{stage.count}
|
|
193
|
+
</Typography>
|
|
194
|
+
<div className="flex items-center gap-1.5 mt-1.5">
|
|
195
|
+
<span
|
|
196
|
+
className="w-1.5 h-1.5 rounded-full shrink-0"
|
|
197
|
+
style={{
|
|
198
|
+
backgroundColor: stageColor,
|
|
199
|
+
opacity: isEmpty ? 0.25 : 1
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
<Typography
|
|
203
|
+
variant="caption"
|
|
204
|
+
color="secondary"
|
|
205
|
+
className={cls(
|
|
206
|
+
"whitespace-nowrap text-[11px]",
|
|
207
|
+
isEmpty && "opacity-35"
|
|
208
|
+
)}
|
|
209
|
+
>
|
|
210
|
+
{displayLabel}
|
|
211
|
+
</Typography>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</Tooltip>
|
|
215
|
+
</React.Fragment>
|
|
216
|
+
);
|
|
217
|
+
})}
|
|
218
|
+
|
|
219
|
+
{/* Separator between active and closed stages */}
|
|
220
|
+
{activeStages.length > 0 && closedStages.length > 0 && (
|
|
221
|
+
<div className="flex items-center shrink-0 mx-2">
|
|
222
|
+
<div className="w-px h-8 bg-surface-200 dark:bg-surface-700" />
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{/* Closed / terminal stages — muted styling */}
|
|
227
|
+
{closedStages.map((stage, idx) => {
|
|
228
|
+
const isEmpty = stage.count === 0;
|
|
229
|
+
const stageColor = resolveColor(stage.color);
|
|
230
|
+
const displayLabel = useShortLabels ? stage.shortLabel : stage.label;
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<React.Fragment key={stage.stageKey}>
|
|
234
|
+
{idx > 0 && (
|
|
235
|
+
<div className="flex items-center shrink-0 text-surface-300 dark:text-surface-600 -mx-0.5">
|
|
236
|
+
<ChevronRight className="h-4 w-4" />
|
|
237
|
+
</div>
|
|
238
|
+
)}
|
|
239
|
+
<Tooltip title={`${stage.label} — ${stage.count} client${stage.count !== 1 ? "s" : ""}`}>
|
|
240
|
+
<div
|
|
241
|
+
className={cls(
|
|
242
|
+
"flex flex-col items-center justify-center rounded-lg px-3 py-3 min-w-[72px] transition-colors duration-150 cursor-pointer",
|
|
243
|
+
"hover:bg-surface-accent-100 dark:hover:bg-surface-800",
|
|
244
|
+
"opacity-60",
|
|
245
|
+
isEmpty
|
|
246
|
+
? "bg-surface-50 dark:bg-surface-900/10"
|
|
247
|
+
: "bg-surface-100/30 dark:bg-surface-900/30"
|
|
248
|
+
)}
|
|
249
|
+
style={{ flex: "0 0 auto" }}
|
|
250
|
+
onClick={() => onNavigate(`/c/engagements?stage=${stage.stageKey}`)}
|
|
251
|
+
>
|
|
252
|
+
<Typography
|
|
253
|
+
variant="h6"
|
|
254
|
+
className={cls(
|
|
255
|
+
"tabular-nums leading-none font-semibold",
|
|
256
|
+
isEmpty && "opacity-25"
|
|
257
|
+
)}
|
|
258
|
+
>
|
|
259
|
+
{stage.count}
|
|
260
|
+
</Typography>
|
|
261
|
+
<div className="flex items-center gap-1.5 mt-1.5">
|
|
262
|
+
<span
|
|
263
|
+
className="w-1.5 h-1.5 rounded-full shrink-0"
|
|
264
|
+
style={{
|
|
265
|
+
backgroundColor: stageColor,
|
|
266
|
+
opacity: isEmpty ? 0.25 : 1
|
|
267
|
+
}}
|
|
268
|
+
/>
|
|
269
|
+
<Typography
|
|
270
|
+
variant="caption"
|
|
271
|
+
color="secondary"
|
|
272
|
+
className={cls(
|
|
273
|
+
"whitespace-nowrap text-[11px]",
|
|
274
|
+
isEmpty && "opacity-35"
|
|
275
|
+
)}
|
|
276
|
+
>
|
|
277
|
+
{displayLabel}
|
|
278
|
+
</Typography>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
</Tooltip>
|
|
282
|
+
</React.Fragment>
|
|
283
|
+
);
|
|
284
|
+
})}
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
) : null}
|
|
288
|
+
</Card>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Typography,
|
|
4
|
+
Card,
|
|
5
|
+
Skeleton,
|
|
6
|
+
Separator,
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogTitle,
|
|
9
|
+
DialogContent,
|
|
10
|
+
DialogActions,
|
|
11
|
+
Button
|
|
12
|
+
} from "@rebasepro/ui";
|
|
13
|
+
import {
|
|
14
|
+
CircleDot,
|
|
15
|
+
ArrowRightLeft,
|
|
16
|
+
Mail,
|
|
17
|
+
FileText,
|
|
18
|
+
PenLine,
|
|
19
|
+
Video,
|
|
20
|
+
MessageCircle,
|
|
21
|
+
StickyNote,
|
|
22
|
+
ListPlus,
|
|
23
|
+
CheckCircle2,
|
|
24
|
+
Wrench,
|
|
25
|
+
Pin
|
|
26
|
+
} from "lucide-react";
|
|
27
|
+
|
|
28
|
+
/* ── Activity type → icon mapping ─────────────────── */
|
|
29
|
+
const ACTIVITY_ICONS: Record<string, React.ReactNode> = {
|
|
30
|
+
incoming: <CircleDot className="h-4 w-4 text-blue-500" />,
|
|
31
|
+
status_changed: <ArrowRightLeft className="h-4 w-4 text-violet-500" />,
|
|
32
|
+
email_sent: <Mail className="h-4 w-4 text-sky-500" />,
|
|
33
|
+
agreements_sent: <FileText className="h-4 w-4 text-amber-500" />,
|
|
34
|
+
agreements_signed: <PenLine className="h-4 w-4 text-emerald-500" />,
|
|
35
|
+
zoom_setup: <Video className="h-4 w-4 text-blue-600" />,
|
|
36
|
+
whatsapp_setup: <MessageCircle className="h-4 w-4 text-green-500" />,
|
|
37
|
+
note: <StickyNote className="h-4 w-4 text-yellow-500" />,
|
|
38
|
+
task_created: <ListPlus className="h-4 w-4 text-teal-500" />,
|
|
39
|
+
task_completed: <CheckCircle2 className="h-4 w-4 text-emerald-500" />,
|
|
40
|
+
manual_action: <Wrench className="h-4 w-4 text-orange-500" />
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/* ── Relative time helper ──────────────────────────── */
|
|
44
|
+
function relativeTime(dateStr: string): string {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const then = new Date(dateStr).getTime();
|
|
47
|
+
const diffSec = Math.round((now - then) / 1000);
|
|
48
|
+
if (diffSec < 60) return 'just now';
|
|
49
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
50
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
51
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
52
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
53
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
54
|
+
if (diffDay < 7) return `${diffDay}d ago`;
|
|
55
|
+
return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ── Types ────────────────────────────────────────────── */
|
|
59
|
+
|
|
60
|
+
export interface RecentActivityProps {
|
|
61
|
+
activities: any[];
|
|
62
|
+
activitiesLoading: boolean;
|
|
63
|
+
tasksMap: Map<string, any>;
|
|
64
|
+
onCompleteTask: (activity: any) => void;
|
|
65
|
+
onViewEmail: (activity: any) => void;
|
|
66
|
+
onOpenTask: (activity: any) => void;
|
|
67
|
+
emailViewerOpen: boolean;
|
|
68
|
+
onEmailViewerOpenChange: (open: boolean) => void;
|
|
69
|
+
selectedEmail: { to: string; subject: string; body: string; html?: string; mode?: string } | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ── Component ────────────────────────────────────────── */
|
|
73
|
+
|
|
74
|
+
export function RecentActivity({
|
|
75
|
+
activities,
|
|
76
|
+
activitiesLoading,
|
|
77
|
+
tasksMap,
|
|
78
|
+
onCompleteTask,
|
|
79
|
+
onViewEmail,
|
|
80
|
+
onOpenTask,
|
|
81
|
+
emailViewerOpen,
|
|
82
|
+
onEmailViewerOpenChange,
|
|
83
|
+
selectedEmail
|
|
84
|
+
}: RecentActivityProps) {
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Card className="p-4 flex-1">
|
|
88
|
+
<Typography variant="subtitle1" className="mb-3">Recent Activity</Typography>
|
|
89
|
+
|
|
90
|
+
{activitiesLoading ? (
|
|
91
|
+
<div className="flex flex-col gap-3">
|
|
92
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
93
|
+
<div key={i} className="flex items-center gap-3">
|
|
94
|
+
<Skeleton className="h-5 w-5 rounded-full shrink-0" />
|
|
95
|
+
<Skeleton className="h-4 flex-1" />
|
|
96
|
+
</div>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
) : activities.length === 0 ? (
|
|
100
|
+
<div className="flex flex-col items-center py-6">
|
|
101
|
+
<Typography variant="body2" color="secondary">
|
|
102
|
+
No recent activity
|
|
103
|
+
</Typography>
|
|
104
|
+
</div>
|
|
105
|
+
) : (
|
|
106
|
+
<div className="flex flex-col">
|
|
107
|
+
{activities.map((activity: any, idx: number) => {
|
|
108
|
+
const actType = activity.values?.type;
|
|
109
|
+
const taskId = activity.values?.metadata?.taskId;
|
|
110
|
+
const relatedTask = taskId ? tasksMap.get(taskId) : null;
|
|
111
|
+
const isTaskActivity = actType === "task_created" || actType === "task_completed";
|
|
112
|
+
const isEmailActivity = actType === "email_sent";
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<React.Fragment key={activity.id}>
|
|
116
|
+
{idx > 0 && <Separator orientation="horizontal" className="my-0" />}
|
|
117
|
+
<div className="flex items-start gap-2.5 py-2.5">
|
|
118
|
+
<span className="shrink-0 mt-0.5 flex items-center">
|
|
119
|
+
{ACTIVITY_ICONS[actType] ?? <Pin className="h-4 w-4 text-surface-400 dark:text-surface-500" />}
|
|
120
|
+
</span>
|
|
121
|
+
<div className="flex-1 min-w-0">
|
|
122
|
+
<Typography variant="body2" className="truncate leading-snug">
|
|
123
|
+
{activity.values?.description || actType || "Activity"}
|
|
124
|
+
</Typography>
|
|
125
|
+
{activity.values?.createdAt && (
|
|
126
|
+
<Typography variant="caption" color="secondary" className="text-[10px]">
|
|
127
|
+
{relativeTime(activity.values.createdAt)}
|
|
128
|
+
</Typography>
|
|
129
|
+
)}
|
|
130
|
+
{/* Action buttons using @rebasepro/ui Buttons */}
|
|
131
|
+
<div className="flex items-center gap-3 mt-1 flex-wrap">
|
|
132
|
+
{/* Complete Task — only for task_created with a pending task */}
|
|
133
|
+
{isTaskActivity && relatedTask && relatedTask.values?.status !== "completed" && (
|
|
134
|
+
<Button
|
|
135
|
+
variant="text"
|
|
136
|
+
size="small"
|
|
137
|
+
color="primary"
|
|
138
|
+
className="text-emerald-600 dark:text-emerald-400 font-medium hover:underline p-0 h-auto min-w-0"
|
|
139
|
+
onClick={() => onCompleteTask(activity)}
|
|
140
|
+
>
|
|
141
|
+
Complete Task
|
|
142
|
+
</Button>
|
|
143
|
+
)}
|
|
144
|
+
{/* View Sent Email */}
|
|
145
|
+
{isEmailActivity && (
|
|
146
|
+
<Button
|
|
147
|
+
variant="text"
|
|
148
|
+
size="small"
|
|
149
|
+
color="primary"
|
|
150
|
+
className="font-medium hover:underline p-0 h-auto min-w-0"
|
|
151
|
+
onClick={() => onViewEmail(activity)}
|
|
152
|
+
>
|
|
153
|
+
View Sent Email
|
|
154
|
+
</Button>
|
|
155
|
+
)}
|
|
156
|
+
{/* Open task in side panel */}
|
|
157
|
+
{isTaskActivity && taskId && (
|
|
158
|
+
<Button
|
|
159
|
+
variant="text"
|
|
160
|
+
size="small"
|
|
161
|
+
color="neutral"
|
|
162
|
+
className="font-medium hover:underline p-0 h-auto min-w-0 text-surface-500 hover:text-surface-700 dark:text-surface-400 dark:hover:text-surface-300"
|
|
163
|
+
onClick={() => onOpenTask(activity)}
|
|
164
|
+
>
|
|
165
|
+
Open Task
|
|
166
|
+
</Button>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</React.Fragment>
|
|
172
|
+
);
|
|
173
|
+
})}
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</Card>
|
|
177
|
+
|
|
178
|
+
{/* ── Email Viewer Dialog ── */}
|
|
179
|
+
<Dialog
|
|
180
|
+
open={emailViewerOpen}
|
|
181
|
+
onOpenChange={onEmailViewerOpenChange}
|
|
182
|
+
maxWidth="xl"
|
|
183
|
+
>
|
|
184
|
+
<DialogTitle>{selectedEmail?.subject || "Email"}</DialogTitle>
|
|
185
|
+
<DialogContent>
|
|
186
|
+
{selectedEmail && (
|
|
187
|
+
<div className="space-y-3">
|
|
188
|
+
<Typography variant="caption" color="secondary">
|
|
189
|
+
To: {selectedEmail.to}
|
|
190
|
+
</Typography>
|
|
191
|
+
{selectedEmail.html ? (
|
|
192
|
+
<div
|
|
193
|
+
className="prose prose-sm dark:prose-invert max-w-none text-text-primary dark:text-text-primary-dark"
|
|
194
|
+
dangerouslySetInnerHTML={{ __html: selectedEmail.html }}
|
|
195
|
+
/>
|
|
196
|
+
) : (
|
|
197
|
+
<Typography variant="body2" className="whitespace-pre-wrap">
|
|
198
|
+
{selectedEmail.body}
|
|
199
|
+
</Typography>
|
|
200
|
+
)}
|
|
201
|
+
{selectedEmail.mode === "simulated" && (
|
|
202
|
+
<Typography variant="caption" color="secondary" className="italic mt-2 block">
|
|
203
|
+
Simulated — email was not actually sent.
|
|
204
|
+
</Typography>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
</DialogContent>
|
|
209
|
+
<DialogActions>
|
|
210
|
+
<Button variant="text" onClick={() => onEmailViewerOpenChange(false)}>Close</Button>
|
|
211
|
+
</DialogActions>
|
|
212
|
+
</Dialog>
|
|
213
|
+
</>
|
|
214
|
+
);
|
|
215
|
+
}
|