@rebasepro/app 0.0.1-canary.4829d6e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/auth/api.d.ts +66 -0
  4. package/dist/auth/index.d.ts +13 -0
  5. package/dist/auth/types.d.ts +127 -0
  6. package/dist/auth/useRebaseAuthController.d.ts +13 -0
  7. package/dist/components/AIIcon.d.ts +17 -0
  8. package/dist/components/AuthSimulationMenu.d.ts +0 -0
  9. package/dist/components/ConfirmationDialog.d.ts +9 -0
  10. package/dist/components/Debug/UIReferenceView.d.ts +13 -0
  11. package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
  12. package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
  13. package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
  14. package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
  15. package/dist/components/Debug/collection-views/index.d.ts +3 -0
  16. package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
  17. package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
  18. package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
  19. package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
  20. package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
  21. package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
  22. package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
  23. package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
  24. package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
  25. package/dist/components/ErrorTooltip.d.ts +3 -0
  26. package/dist/components/ErrorView.d.ts +21 -0
  27. package/dist/components/LanguageToggle.d.ts +2 -0
  28. package/dist/components/LoginView/LoginView.d.ts +113 -0
  29. package/dist/components/LoginView/index.d.ts +2 -0
  30. package/dist/components/NotFoundPage.d.ts +2 -0
  31. package/dist/components/RebaseAuth.d.ts +10 -0
  32. package/dist/components/RebaseLogo.d.ts +7 -0
  33. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  34. package/dist/components/UnsavedChangesDialog.d.ts +9 -0
  35. package/dist/components/UserDisplay.d.ts +7 -0
  36. package/dist/components/UserSelectPopover.d.ts +63 -0
  37. package/dist/components/UserSettingsView.d.ts +2 -0
  38. package/dist/components/common/index.d.ts +6 -0
  39. package/dist/components/common/table_height.d.ts +5 -0
  40. package/dist/components/common/types.d.ts +66 -0
  41. package/dist/components/common/useColumnsIds.d.ts +9 -0
  42. package/dist/components/common/useDataTableController.d.ts +45 -0
  43. package/dist/components/common/useDebouncedData.d.ts +9 -0
  44. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  45. package/dist/components/index.d.ts +18 -0
  46. package/dist/contexts/AdminModeController.d.ts +4 -0
  47. package/dist/contexts/AnalyticsContext.d.ts +3 -0
  48. package/dist/contexts/AuthControllerContext.d.ts +3 -0
  49. package/dist/contexts/CollectionScopeContext.d.ts +50 -0
  50. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  51. package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
  52. package/dist/contexts/DataDriverContext.d.ts +3 -0
  53. package/dist/contexts/DataSourcesContext.d.ts +31 -0
  54. package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
  55. package/dist/contexts/DialogsProvider.d.ts +4 -0
  56. package/dist/contexts/EffectiveRoleController.d.ts +4 -0
  57. package/dist/contexts/ModeController.d.ts +4 -0
  58. package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
  59. package/dist/contexts/RebaseDataContext.d.ts +3 -0
  60. package/dist/contexts/SnackbarProvider.d.ts +2 -0
  61. package/dist/contexts/StorageSourceContext.d.ts +3 -0
  62. package/dist/contexts/StorageSourcesContext.d.ts +25 -0
  63. package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  64. package/dist/contexts/index.d.ts +17 -0
  65. package/dist/core/PluginLifecycleManager.d.ts +17 -0
  66. package/dist/core/PluginProviderStack.d.ts +21 -0
  67. package/dist/core/Rebase.d.ts +15 -0
  68. package/dist/core/RebaseProps.d.ts +291 -0
  69. package/dist/core/RebaseRouter.d.ts +4 -0
  70. package/dist/core/RebaseRoutes.d.ts +17 -0
  71. package/dist/core/index.d.ts +4 -0
  72. package/dist/hooks/ApiConfigContext.d.ts +24 -0
  73. package/dist/hooks/data/delete.d.ts +33 -0
  74. package/dist/hooks/data/save.d.ts +40 -0
  75. package/dist/hooks/data/useCollection.d.ts +62 -0
  76. package/dist/hooks/data/useData.d.ts +13 -0
  77. package/dist/hooks/data/useDataOrder.d.ts +12 -0
  78. package/dist/hooks/data/useFetch.d.ts +43 -0
  79. package/dist/hooks/data/useRelationSelector.d.ts +52 -0
  80. package/dist/hooks/index.d.ts +36 -0
  81. package/dist/hooks/useAdminModeController.d.ts +19 -0
  82. package/dist/hooks/useAnalyticsController.d.ts +5 -0
  83. package/dist/hooks/useAuthController.d.ts +11 -0
  84. package/dist/hooks/useAuthSubscription.d.ts +2 -0
  85. package/dist/hooks/useBackendStorageSource.d.ts +30 -0
  86. package/dist/hooks/useBridgeRegistration.d.ts +18 -0
  87. package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  88. package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
  89. package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  90. package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  91. package/dist/hooks/useBuildModeController.d.ts +6 -0
  92. package/dist/hooks/useClipboard.d.ts +57 -0
  93. package/dist/hooks/useCollapsedGroups.d.ts +27 -0
  94. package/dist/hooks/useComponentOverride.d.ts +32 -0
  95. package/dist/hooks/useCustomizationController.d.ts +11 -0
  96. package/dist/hooks/useDialogsController.d.ts +11 -0
  97. package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
  98. package/dist/hooks/useLargeLayout.d.ts +1 -0
  99. package/dist/hooks/useModeController.d.ts +19 -0
  100. package/dist/hooks/usePermissions.d.ts +12 -0
  101. package/dist/hooks/useRebaseClient.d.ts +5 -0
  102. package/dist/hooks/useRebaseContext.d.ts +11 -0
  103. package/dist/hooks/useRebaseRegistry.d.ts +34 -0
  104. package/dist/hooks/useResolvedComponent.d.ts +47 -0
  105. package/dist/hooks/useSlot.d.ts +18 -0
  106. package/dist/hooks/useSnackbarController.d.ts +20 -0
  107. package/dist/hooks/useStorageSource.d.ts +6 -0
  108. package/dist/hooks/useStudioBridge.d.ts +79 -0
  109. package/dist/hooks/useTranslation.d.ts +17 -0
  110. package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
  111. package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
  112. package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
  113. package/dist/index.d.ts +16 -0
  114. package/dist/index.es.js +16843 -0
  115. package/dist/index.es.js.map +1 -0
  116. package/dist/index.umd.js +17021 -0
  117. package/dist/index.umd.js.map +1 -0
  118. package/dist/internal/common.d.ts +14 -0
  119. package/dist/internal/useRestoreScroll.d.ts +15 -0
  120. package/dist/locales/de.d.ts +2 -0
  121. package/dist/locales/en.d.ts +10 -0
  122. package/dist/locales/es.d.ts +10 -0
  123. package/dist/locales/fr.d.ts +2 -0
  124. package/dist/locales/hi.d.ts +2 -0
  125. package/dist/locales/it.d.ts +2 -0
  126. package/dist/locales/pt.d.ts +7 -0
  127. package/dist/util/constants.d.ts +1 -0
  128. package/dist/util/createFormexStub.d.ts +2 -0
  129. package/dist/util/entity_cache.d.ts +22 -0
  130. package/dist/util/enums.d.ts +5 -0
  131. package/dist/util/icon_list.d.ts +5 -0
  132. package/dist/util/icons.d.ts +20 -0
  133. package/dist/util/index.d.ts +8 -0
  134. package/dist/util/previews.d.ts +4 -0
  135. package/dist/util/useStorageUploadController.d.ts +44 -0
  136. package/dist/vitePlugin.d.ts +60 -0
  137. package/dist/vitePlugin.js +113 -0
  138. package/package.json +145 -0
  139. package/src/auth/api.ts +148 -0
  140. package/src/auth/index.ts +25 -0
  141. package/src/auth/types.ts +115 -0
  142. package/src/auth/useRebaseAuthController.ts +328 -0
  143. package/src/components/AIIcon.tsx +47 -0
  144. package/src/components/AuthSimulationMenu.tsx +0 -0
  145. package/src/components/ConfirmationDialog.tsx +48 -0
  146. package/src/components/Debug/UIReferenceView.tsx +958 -0
  147. package/src/components/Debug/UIStyleGuide.tsx +156 -0
  148. package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
  149. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
  150. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
  151. package/src/components/Debug/collection-views/index.ts +3 -0
  152. package/src/components/Debug/collection-views/sample_data.ts +42 -0
  153. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
  154. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
  155. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
  156. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
  157. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
  158. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
  159. package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
  160. package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
  161. package/src/components/ErrorTooltip.tsx +12 -0
  162. package/src/components/ErrorView.tsx +94 -0
  163. package/src/components/LanguageToggle.tsx +59 -0
  164. package/src/components/LoginView/LoginView.tsx +932 -0
  165. package/src/components/LoginView/index.ts +2 -0
  166. package/src/components/NotFoundPage.tsx +27 -0
  167. package/src/components/RebaseAuth.tsx +27 -0
  168. package/src/components/RebaseLogo.tsx +29 -0
  169. package/src/components/SchemaDriftBanner.tsx +102 -0
  170. package/src/components/UnsavedChangesDialog.tsx +46 -0
  171. package/src/components/UserDisplay.tsx +54 -0
  172. package/src/components/UserSelectPopover.tsx +399 -0
  173. package/src/components/UserSettingsView.tsx +318 -0
  174. package/src/components/common/index.ts +6 -0
  175. package/src/components/common/table_height.tsx +21 -0
  176. package/src/components/common/types.tsx +66 -0
  177. package/src/components/common/useColumnsIds.tsx +222 -0
  178. package/src/components/common/useDataTableController.tsx +514 -0
  179. package/src/components/common/useDebouncedData.ts +49 -0
  180. package/src/components/common/useScrollRestoration.tsx +68 -0
  181. package/src/components/index.tsx +26 -0
  182. package/src/contexts/AdminModeController.tsx +11 -0
  183. package/src/contexts/AnalyticsContext.tsx +4 -0
  184. package/src/contexts/AuthControllerContext.tsx +4 -0
  185. package/src/contexts/CollectionScopeContext.tsx +67 -0
  186. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  187. package/src/contexts/CustomizationControllerContext.tsx +4 -0
  188. package/src/contexts/DataDriverContext.tsx +4 -0
  189. package/src/contexts/DataSourcesContext.tsx +36 -0
  190. package/src/contexts/DatabaseAdminContext.tsx +4 -0
  191. package/src/contexts/DialogsProvider.tsx +56 -0
  192. package/src/contexts/EffectiveRoleController.tsx +12 -0
  193. package/src/contexts/ModeController.tsx +11 -0
  194. package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
  195. package/src/contexts/RebaseDataContext.tsx +4 -0
  196. package/src/contexts/SnackbarProvider.tsx +15 -0
  197. package/src/contexts/StorageSourceContext.tsx +4 -0
  198. package/src/contexts/StorageSourcesContext.tsx +30 -0
  199. package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
  200. package/src/contexts/index.ts +17 -0
  201. package/src/core/PluginLifecycleManager.tsx +95 -0
  202. package/src/core/PluginProviderStack.tsx +40 -0
  203. package/src/core/Rebase.tsx +439 -0
  204. package/src/core/RebaseProps.tsx +320 -0
  205. package/src/core/RebaseRouter.tsx +17 -0
  206. package/src/core/RebaseRoutes.tsx +24 -0
  207. package/src/core/index.tsx +5 -0
  208. package/src/hooks/ApiConfigContext.tsx +41 -0
  209. package/src/hooks/data/delete.ts +55 -0
  210. package/src/hooks/data/save.ts +77 -0
  211. package/src/hooks/data/useCollection.tsx +176 -0
  212. package/src/hooks/data/useData.tsx +18 -0
  213. package/src/hooks/data/useDataOrder.ts +26 -0
  214. package/src/hooks/data/useFetch.tsx +140 -0
  215. package/src/hooks/data/useRelationSelector.tsx +234 -0
  216. package/src/hooks/index.tsx +45 -0
  217. package/src/hooks/useAdminModeController.tsx +23 -0
  218. package/src/hooks/useAnalyticsController.tsx +8 -0
  219. package/src/hooks/useAuthController.tsx +14 -0
  220. package/src/hooks/useAuthSubscription.ts +87 -0
  221. package/src/hooks/useBackendStorageSource.ts +288 -0
  222. package/src/hooks/useBridgeRegistration.tsx +32 -0
  223. package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
  224. package/src/hooks/useBuildAdminModeController.tsx +24 -0
  225. package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
  226. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
  227. package/src/hooks/useBuildModeController.tsx +71 -0
  228. package/src/hooks/useClipboard.tsx +158 -0
  229. package/src/hooks/useCollapsedGroups.ts +116 -0
  230. package/src/hooks/useComponentOverride.tsx +59 -0
  231. package/src/hooks/useCustomizationController.tsx +14 -0
  232. package/src/hooks/useDialogsController.tsx +15 -0
  233. package/src/hooks/useEffectiveRoleController.tsx +10 -0
  234. package/src/hooks/useLargeLayout.tsx +65 -0
  235. package/src/hooks/useModeController.tsx +23 -0
  236. package/src/hooks/usePermissions.ts +44 -0
  237. package/src/hooks/useRebaseClient.tsx +10 -0
  238. package/src/hooks/useRebaseContext.tsx +83 -0
  239. package/src/hooks/useRebaseRegistry.tsx +91 -0
  240. package/src/hooks/useResolvedComponent.tsx +159 -0
  241. package/src/hooks/useSlot.tsx +64 -0
  242. package/src/hooks/useSnackbarController.tsx +52 -0
  243. package/src/hooks/useStorageSource.tsx +11 -0
  244. package/src/hooks/useStudioBridge.tsx +209 -0
  245. package/src/hooks/useTranslation.ts +33 -0
  246. package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
  247. package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
  248. package/src/i18n/RebaseI18nProvider.tsx +160 -0
  249. package/src/index.ts +25 -0
  250. package/src/internal/common.tsx +16 -0
  251. package/src/internal/useRestoreScroll.tsx +69 -0
  252. package/src/locales/de.ts +902 -0
  253. package/src/locales/en.ts +980 -0
  254. package/src/locales/es.ts +922 -0
  255. package/src/locales/fr.ts +901 -0
  256. package/src/locales/hi.ts +901 -0
  257. package/src/locales/it.ts +901 -0
  258. package/src/locales/pt.ts +909 -0
  259. package/src/util/constants.ts +7 -0
  260. package/src/util/createFormexStub.tsx +66 -0
  261. package/src/util/entity_cache.ts +239 -0
  262. package/src/util/enums.ts +32 -0
  263. package/src/util/icon_list.ts +22 -0
  264. package/src/util/icons.tsx +149 -0
  265. package/src/util/index.ts +12 -0
  266. package/src/util/previews.ts +105 -0
  267. package/src/util/useStorageUploadController.tsx +350 -0
  268. package/src/vitePlugin.ts +196 -0
@@ -0,0 +1,184 @@
1
+ import React from "react";
2
+ import {
3
+ Chip,
4
+ FilterChip,
5
+ SearchBar,
6
+ Select,
7
+ SelectItem,
8
+ Typography,
9
+ Button
10
+ } from "@rebasepro/ui";
11
+ import {
12
+ AlertTriangle,
13
+ ListTodo,
14
+ X
15
+ } from "lucide-react";
16
+
17
+ /* ── Types ──────────────────────────────────────────────────── */
18
+
19
+ type QuickFilter = "all" | "overdue";
20
+
21
+ export interface TaskFiltersProps {
22
+ quickFilter: QuickFilter;
23
+ onQuickFilterChange: (filter: QuickFilter) => void;
24
+ stageFilter: string | null;
25
+ onStageFilterChange: (stage: string | null) => void;
26
+ clientFilter: string | null;
27
+ onClientFilterChange: (clientId: string | null) => void;
28
+ searchText: string;
29
+ onSearchTextChange: (text: string) => void;
30
+ hasActiveFilter: boolean;
31
+ onClearAllFilters: () => void;
32
+ overdueTasks: { length: number };
33
+ pendingTasks: { length: number };
34
+ stageLabels: Record<string, string>;
35
+ stageFilterOrder: string[];
36
+ stageCounts: Record<string, number>;
37
+ activeClientIds: string[];
38
+ clientsMap: Map<string, { name: string; picture?: string }>;
39
+ }
40
+
41
+ /* ── Component ──────────────────────────────────────────────── */
42
+
43
+ export function TaskFilters({
44
+ quickFilter,
45
+ onQuickFilterChange,
46
+ stageFilter,
47
+ onStageFilterChange,
48
+ clientFilter,
49
+ onClientFilterChange,
50
+ searchText,
51
+ onSearchTextChange,
52
+ hasActiveFilter,
53
+ onClearAllFilters,
54
+ overdueTasks,
55
+ pendingTasks,
56
+ stageLabels,
57
+ stageFilterOrder,
58
+ stageCounts,
59
+ activeClientIds,
60
+ clientsMap
61
+ }: TaskFiltersProps) {
62
+ return (
63
+ <>
64
+ {/* Header */}
65
+ <div className="flex items-center justify-between mb-2">
66
+ <div className="flex items-center gap-2">
67
+ <div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500">
68
+ <ListTodo className="h-4 w-4" />
69
+ </div>
70
+ <Typography variant="subtitle1">Tasks</Typography>
71
+ {pendingTasks.length > 0 && (
72
+ <Chip size="smallest" colorScheme="blue">
73
+ {pendingTasks.length}{hasActiveFilter ? " shown" : " pending"}
74
+ </Chip>
75
+ )}
76
+ </div>
77
+ {overdueTasks.length > 0 && (
78
+ <Chip size="smallest" colorScheme="red">
79
+ {overdueTasks.length} overdue
80
+ </Chip>
81
+ )}
82
+ </div>
83
+
84
+ {/* ── Filter bar ── */}
85
+ <div className="flex flex-col gap-2 mb-3">
86
+ {/* Quick filter chips row */}
87
+ <div className="flex items-center gap-1.5">
88
+ <FilterChip
89
+ active={quickFilter === "all" && !stageFilter && !clientFilter}
90
+ onClick={() => { onQuickFilterChange("all"); onStageFilterChange(null); onClientFilterChange(null); }}
91
+ size="small"
92
+ >
93
+ All
94
+ </FilterChip>
95
+ <FilterChip
96
+ active={quickFilter === "overdue"}
97
+ onClick={() => { onQuickFilterChange(quickFilter === "overdue" ? "all" : "overdue"); }}
98
+ size="small"
99
+ icon={<AlertTriangle className="h-3 w-3" />}
100
+ >
101
+ Overdue{overdueTasks.length > 0 ? ` (${overdueTasks.length})` : ""}
102
+ </FilterChip>
103
+
104
+ {/* Clear all filters using @rebasepro/ui Button */}
105
+ {hasActiveFilter && (
106
+ <Button
107
+ variant="text"
108
+ size="small"
109
+ color="neutral"
110
+ className="p-0 h-auto min-w-0 text-xs text-text-secondary dark:text-text-secondary-dark hover:text-primary dark:hover:text-primary-light"
111
+ onClick={onClearAllFilters}
112
+ startIcon={<X className="h-3 w-3" />}
113
+ >
114
+ Clear
115
+ </Button>
116
+ )}
117
+ </div>
118
+
119
+ {/* Stage select + Client select + Search row */}
120
+ <div className="flex items-center gap-2">
121
+ <div className="flex-1">
122
+ <SearchBar
123
+ onTextSearch={(v) => { onSearchTextChange(v || ""); }}
124
+ placeholder="Search tasks..."
125
+ size="smallest"
126
+ className="w-full"
127
+ />
128
+ </div>
129
+
130
+ {/* Stage filter dropdown */}
131
+ <Select
132
+ value={stageFilter || "__all__"}
133
+ onValueChange={(v) => { onStageFilterChange(v === "__all__" ? null : v as string); }}
134
+ placeholder="Stage"
135
+ size="smallest"
136
+ renderValue={(v) => {
137
+ if (!v || v === "__all__") return "Stage";
138
+ return stageLabels[v as string] || "Stage";
139
+ }}
140
+ position="popper"
141
+ >
142
+ <SelectItem value={"__all__"}>All stages</SelectItem>
143
+ {stageFilterOrder
144
+ .filter(stageId => stageCounts[stageId] > 0)
145
+ .map(stageId => (
146
+ <SelectItem key={stageId} value={stageId}>
147
+ {stageLabels[stageId] || stageId}
148
+ {stageCounts[stageId] > 0 ? ` (${stageCounts[stageId]})` : ""}
149
+ </SelectItem>
150
+ ))
151
+ }
152
+ </Select>
153
+
154
+ {/* Client filter dropdown */}
155
+ {activeClientIds.length > 1 && (
156
+ <Select
157
+ value={clientFilter || "__all__"}
158
+ onValueChange={(v) => { onClientFilterChange(v === "__all__" ? null : v as string); }}
159
+ placeholder="All clients"
160
+ size="smallest"
161
+ renderValue={(v) => {
162
+ if (!v || v === "__all__") return "Client";
163
+ const info = clientsMap.get(v as string);
164
+ return info?.name || "Client";
165
+ }}
166
+ position="popper"
167
+ >
168
+ <SelectItem value={"__all__"}>All clients</SelectItem>
169
+ {activeClientIds.map(cId => {
170
+ const info = clientsMap.get(cId);
171
+ if (!info) return null;
172
+ return (
173
+ <SelectItem key={cId} value={cId}>
174
+ {info.name}
175
+ </SelectItem>
176
+ );
177
+ })}
178
+ </Select>
179
+ )}
180
+ </div>
181
+ </div>
182
+ </>
183
+ );
184
+ }
@@ -0,0 +1,427 @@
1
+ import React, { useState, useCallback } from "react";
2
+ import {
3
+ Typography,
4
+ cls,
5
+ Card,
6
+ Chip,
7
+ Skeleton,
8
+ Checkbox,
9
+ Button
10
+ } from "@rebasepro/ui";
11
+ import {
12
+ Calendar,
13
+ AlertTriangle,
14
+ ListTodo,
15
+ CheckCircle2
16
+ } from "lucide-react";
17
+
18
+ /* ── Types ──────────────────────────────────────────────────── */
19
+
20
+ interface TaskEntity {
21
+ id: string;
22
+ values: {
23
+ title: string;
24
+ status: string;
25
+ resolution: string | null;
26
+ dueDate: string | null;
27
+ stageId: string | null;
28
+ description: string | null;
29
+ notes: string | null;
30
+ clientId: string | null;
31
+ client?: { name?: string } | null;
32
+ priority?: string | null;
33
+ createdAt: string | null;
34
+ };
35
+ }
36
+
37
+ export interface TaskTableProps {
38
+ tasks: TaskEntity[];
39
+ loading: boolean;
40
+ pendingTasks: TaskEntity[];
41
+ visiblePendingTasks: TaskEntity[];
42
+ hasMorePending: boolean;
43
+ onLoadMorePending: () => void;
44
+ pendingCount: number;
45
+ togglingIds: Set<string>;
46
+ onToggleTask: (taskId: string, currentStatus: string) => void;
47
+ onOpenTask: (taskId: string) => void;
48
+ onOpenClient: (clientId: string) => void;
49
+ clientsMap: Map<string, { name: string; picture?: string }>;
50
+ stageLabels: Record<string, string>;
51
+ recentlyToggledIds: Set<string>;
52
+ /* Completed section */
53
+ completedTasks: TaskEntity[];
54
+ completedLoaded: boolean;
55
+ loadingCompleted: boolean;
56
+ completedHasMore: boolean;
57
+ onExpandCompleted: () => void;
58
+ onLoadMoreCompleted: () => void;
59
+ }
60
+
61
+ export const resolutionDisplay: Record<string, { label: string; colorScheme: string }> = {
62
+ verified: { label: "Verified", colorScheme: "green" },
63
+ needs_followup: { label: "Needs Follow-up", colorScheme: "amber" },
64
+ suitable: { label: "Suitable", colorScheme: "green" },
65
+ not_a_fit: { label: "Not a Fit", colorScheme: "red" },
66
+ response_received: { label: "Response Received", colorScheme: "green" },
67
+ followup_sent: { label: "Follow-up Sent", colorScheme: "blue" },
68
+ prepared: { label: "Prepared", colorScheme: "green" },
69
+ invite_sent: { label: "Invite Sent", colorScheme: "green" },
70
+ agreement_ready: { label: "Agreement Ready", colorScheme: "green" },
71
+ signed: { label: "Signed", colorScheme: "green" },
72
+ reminder_sent: { label: "Reminder Sent", colorScheme: "blue" },
73
+ payment_confirmed: { label: "Payment Confirmed", colorScheme: "green" },
74
+ not_yet_received: { label: "Not Yet Received", colorScheme: "amber" },
75
+ sent: { label: "Sent", colorScheme: "green" },
76
+ confirmed: { label: "Confirmed", colorScheme: "green" },
77
+ not_yet: { label: "Not Yet", colorScheme: "amber" },
78
+ scheduled: { label: "Scheduled", colorScheme: "green" },
79
+ feedback_received: { label: "Feedback Received", colorScheme: "green" },
80
+ awaiting_response: { label: "Awaiting Response", colorScheme: "amber" },
81
+ archived: { label: "Archived", colorScheme: "green" },
82
+ done: { label: "Done", colorScheme: "green" },
83
+ };
84
+
85
+ /* ── Helpers ────────────────────────────────────────────────── */
86
+
87
+ function formatDueDate(dateStr: string | null): string | null {
88
+ if (!dateStr) return null;
89
+ const d = new Date(dateStr);
90
+ if (isNaN(d.getTime())) return null;
91
+ const now = new Date();
92
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
93
+ const target = new Date(d.getFullYear(), d.getMonth(), d.getDate());
94
+ const diffMs = target.getTime() - today.getTime();
95
+ const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
96
+
97
+ if (diffDays === 0) return "Today";
98
+ if (diffDays === 1) return "Tomorrow";
99
+ if (diffDays === -1) return "Yesterday";
100
+ if (diffDays < 0) return `${Math.abs(diffDays)}d overdue`;
101
+ if (diffDays <= 7) return `In ${diffDays}d`;
102
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
103
+ }
104
+
105
+ function isDueDateOverdue(dateStr: string | null): boolean {
106
+ if (!dateStr) return false;
107
+ const d = new Date(dateStr);
108
+ if (isNaN(d.getTime())) return false;
109
+ const now = new Date();
110
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
111
+ return d < today;
112
+ }
113
+
114
+ function isDueDateSoon(dateStr: string | null): boolean {
115
+ if (!dateStr) return false;
116
+ const d = new Date(dateStr);
117
+ if (isNaN(d.getTime())) return false;
118
+ const now = new Date();
119
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
120
+ const diffMs = d.getTime() - today.getTime();
121
+ const diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24));
122
+ return diffDays >= 0 && diffDays <= 2;
123
+ }
124
+
125
+ /* ── Component ──────────────────────────────────────────────── */
126
+
127
+ export function TaskTable({
128
+ tasks,
129
+ loading,
130
+ pendingTasks,
131
+ visiblePendingTasks,
132
+ hasMorePending,
133
+ onLoadMorePending,
134
+ pendingCount,
135
+ togglingIds,
136
+ onToggleTask,
137
+ onOpenTask,
138
+ onOpenClient,
139
+ clientsMap,
140
+ stageLabels,
141
+ recentlyToggledIds,
142
+ completedTasks,
143
+ completedLoaded,
144
+ loadingCompleted,
145
+ completedHasMore,
146
+ onExpandCompleted,
147
+ onLoadMoreCompleted
148
+ }: TaskTableProps) {
149
+
150
+ /* ── Task Row Renderer ── */
151
+ const renderTaskRow = useCallback((task: TaskEntity) => {
152
+ const isCompleted = task.values?.status === "completed";
153
+ const isToggling = togglingIds.has(task.id);
154
+ const dueLabel = formatDueDate(task.values?.dueDate);
155
+ const overdue = !isCompleted && isDueDateOverdue(task.values?.dueDate);
156
+ const soon = !isCompleted && isDueDateSoon(task.values?.dueDate);
157
+ const stageName = task.values?.stageId ? stageLabels[task.values.stageId] : null;
158
+ const clientInfo = task.values?.clientId ? clientsMap.get(task.values.clientId) : null;
159
+ const clientName = clientInfo?.name || null;
160
+
161
+ return (
162
+ <div
163
+ key={task.id}
164
+ className={cls(
165
+ "group flex items-start gap-3 py-2.5 px-3 rounded-md transition-colors duration-150 cursor-pointer",
166
+ "hover:bg-surface-accent-100 dark:hover:bg-surface-800",
167
+ isCompleted && "opacity-60",
168
+ overdue && "border-l-2 border-l-red-500 rounded-l-none",
169
+ soon && !overdue && "border-l-2 border-l-amber-450 rounded-l-none"
170
+ )}
171
+ onClick={() => onOpenTask(task.id)}
172
+ >
173
+ {/* Checkbox */}
174
+ <div
175
+ className="pt-0.5 shrink-0"
176
+ onClick={(e) => e.stopPropagation()}
177
+ >
178
+ <Checkbox
179
+ checked={isCompleted}
180
+ size="small"
181
+ onCheckedChange={() => {
182
+ if (!isToggling) onToggleTask(task.id, task.values?.status);
183
+ }}
184
+ />
185
+ </div>
186
+
187
+ {/* Content */}
188
+ <div className="flex-1 min-w-0">
189
+ <div className="flex items-center gap-1.5 flex-wrap">
190
+ <Typography
191
+ variant="body2"
192
+ className={cls(
193
+ "leading-snug",
194
+ isCompleted && "line-through text-text-disabled dark:text-text-disabled-dark"
195
+ )}
196
+ >
197
+ {task.values?.title || "Untitled Task"}
198
+ </Typography>
199
+ {task.values?.priority === "high" && (
200
+ <Chip colorScheme="red" size="smallest">High</Chip>
201
+ )}
202
+ </div>
203
+
204
+ {/* Meta row */}
205
+ <div className="flex items-center gap-2 mt-1 flex-wrap">
206
+ {dueLabel && (
207
+ <span className={cls(
208
+ "inline-flex items-center gap-1 text-[11px]",
209
+ overdue && "text-red-600 dark:text-red-400 font-medium",
210
+ soon && !overdue && "text-amber-600 dark:text-amber-400 font-medium",
211
+ !overdue && !soon && "text-text-secondary dark:text-text-secondary-dark"
212
+ )}>
213
+ {overdue ? (
214
+ <AlertTriangle className="h-3 w-3" />
215
+ ) : (
216
+ <Calendar className="h-3 w-3" />
217
+ )}
218
+ {dueLabel}
219
+ </span>
220
+ )}
221
+
222
+ {clientName && task.values?.clientId && (
223
+ <Typography
224
+ variant="body2"
225
+ className={cls(
226
+ "inline-flex items-center gap-1 text-primary hover:text-primary-dark",
227
+ "dark:text-primary-light dark:hover:text-primary",
228
+ "hover:underline cursor-pointer text-[11px]"
229
+ )}
230
+ onClick={(e) => {
231
+ e.stopPropagation();
232
+ onOpenClient(task.values.clientId!);
233
+ }}
234
+ >
235
+ {(() => {
236
+ const clientPic = clientInfo?.picture;
237
+ const clientInitial = (clientName || "?")[0].toUpperCase();
238
+ return clientPic ? (
239
+ <img src={clientPic} alt="" className="w-4 h-4 rounded-full object-cover shrink-0" />
240
+ ) : (
241
+ <div className="w-4 h-4 rounded-full bg-surface-200 dark:bg-surface-700 text-surface-500 dark:text-surface-400 flex items-center justify-center shrink-0">
242
+ <span className="text-[9px] font-semibold">{clientInitial}</span>
243
+ </div>
244
+ );
245
+ })()}
246
+ {clientName}
247
+ </Typography>
248
+ )}
249
+
250
+ {stageName && (
251
+ <Chip size="smallest" colorScheme="cyan" outlined>
252
+ {stageName}
253
+ </Chip>
254
+ )}
255
+
256
+ {/* Resolution chip for completed tasks */}
257
+ {isCompleted && task.values?.resolution && resolutionDisplay[task.values.resolution] && (
258
+ <Chip
259
+ size="smallest"
260
+ colorScheme={resolutionDisplay[task.values.resolution].colorScheme as any}
261
+ >
262
+ {resolutionDisplay[task.values.resolution].label}
263
+ </Chip>
264
+ )}
265
+ </div>
266
+ </div>
267
+ </div>
268
+ );
269
+ }, [togglingIds, onToggleTask, onOpenTask, onOpenClient, clientsMap, recentlyToggledIds, stageLabels]);
270
+
271
+ /* ── Skeleton loader ── */
272
+ if (loading) {
273
+ return (
274
+ <div className="flex flex-col gap-3">
275
+ {[1, 2, 3, 4].map(i => (
276
+ <div key={i} className="flex items-center gap-3 py-2 px-3">
277
+ <Skeleton width={18} height={18} />
278
+ <div className="flex-1 space-y-1">
279
+ <Skeleton width={200} height={16} />
280
+ <Skeleton width={120} height={12} />
281
+ </div>
282
+ </div>
283
+ ))}
284
+ </div>
285
+ );
286
+ }
287
+
288
+ return (
289
+ <div className="flex-1 overflow-y-auto">
290
+ {tasks.length === 0 ? (
291
+ <Card className="flex flex-col items-center justify-center py-10 px-6 text-center mt-2 border-dashed">
292
+ <div className="flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 mb-3">
293
+ <CheckCircle2 className="h-4 w-4" />
294
+ </div>
295
+ <Typography variant="subtitle2" className="mb-1">
296
+ All clear!
297
+ </Typography>
298
+ <Typography variant="body2" color="secondary">
299
+ No tasks at the moment. New tasks will show up here automatically.
300
+ </Typography>
301
+ </Card>
302
+ ) : (
303
+ <div className="space-y-4">
304
+ {/* Pending tasks */}
305
+ {pendingTasks.length > 0 && (
306
+ <div className="space-y-0.5">
307
+ {visiblePendingTasks.map(renderTaskRow)}
308
+ {hasMorePending && (
309
+ <div className="pt-2">
310
+ <Button
311
+ variant="text"
312
+ size="small"
313
+ color="primary"
314
+ className="w-full justify-center"
315
+ onClick={onLoadMorePending}
316
+ >
317
+ Load more ({pendingCount - visiblePendingTasks.length} remaining)
318
+ </Button>
319
+ </div>
320
+ )}
321
+ </div>
322
+ )}
323
+
324
+ {/* Completed tasks - lazy loaded collapsed section */}
325
+ <CompletedSection
326
+ tasks={completedTasks}
327
+ renderTaskRow={renderTaskRow}
328
+ onExpand={onExpandCompleted}
329
+ onLoadMore={onLoadMoreCompleted}
330
+ loading={loadingCompleted}
331
+ hasMore={completedHasMore}
332
+ />
333
+ </div>
334
+ )}
335
+ </div>
336
+ );
337
+ }
338
+
339
+ /* ── Completed Section (collapsible, lazy-loaded) ─────────── */
340
+
341
+ function CompletedSection({
342
+ tasks,
343
+ renderTaskRow,
344
+ onExpand,
345
+ onLoadMore,
346
+ loading,
347
+ hasMore
348
+ }: {
349
+ tasks: TaskEntity[];
350
+ renderTaskRow: (task: TaskEntity) => React.ReactNode;
351
+ onExpand: () => void;
352
+ onLoadMore: () => void;
353
+ loading: boolean;
354
+ hasMore: boolean;
355
+ }) {
356
+ const [expanded, setExpanded] = useState(false);
357
+
358
+ const handleToggle = () => {
359
+ const willExpand = !expanded;
360
+ setExpanded(willExpand);
361
+ if (willExpand) onExpand();
362
+ };
363
+
364
+ return (
365
+ <div>
366
+ <Button
367
+ variant="text"
368
+ size="small"
369
+ color="neutral"
370
+ className="w-full justify-between hover:bg-surface-accent-100 dark:hover:bg-surface-800 px-3 py-1.5 h-auto text-left"
371
+ onClick={handleToggle}
372
+ endIcon={
373
+ <svg
374
+ className={cls(
375
+ "h-3 w-3 text-text-secondary dark:text-text-secondary-dark transition-transform duration-150",
376
+ expanded && "rotate-90"
377
+ )}
378
+ viewBox="0 0 24 24"
379
+ fill="none"
380
+ stroke="currentColor"
381
+ strokeWidth={2}
382
+ strokeLinecap="round"
383
+ strokeLinejoin="round"
384
+ >
385
+ <polyline points="9 18 15 12 9 6" />
386
+ </svg>
387
+ }
388
+ >
389
+ <Typography
390
+ variant="caption"
391
+ color="secondary"
392
+ className="font-semibold uppercase tracking-wider text-[10px]"
393
+ >
394
+ Completed{tasks.length > 0 ? ` (${tasks.length}${hasMore ? "+" : ""})` : ""}
395
+ </Typography>
396
+ </Button>
397
+ {expanded && (
398
+ <div className="mt-1 space-y-0.5">
399
+ {loading && tasks.length === 0 ? (
400
+ <div className="flex items-center gap-2 px-3 py-3">
401
+ <Skeleton width={18} height={18} />
402
+ <Skeleton width={200} height={16} />
403
+ </div>
404
+ ) : (
405
+ <>
406
+ {tasks.map(renderTaskRow)}
407
+ {hasMore && (
408
+ <div className="pt-2">
409
+ <Button
410
+ variant="text"
411
+ size="small"
412
+ color="primary"
413
+ className="w-full justify-center"
414
+ onClick={onLoadMore}
415
+ disabled={loading}
416
+ >
417
+ {loading ? "Loading..." : "Load more completed"}
418
+ </Button>
419
+ </div>
420
+ )}
421
+ </>
422
+ )}
423
+ </div>
424
+ )}
425
+ </div>
426
+ );
427
+ }