@rebasepro/app 0.17.3 → 0.18.0

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