@rebasepro/app 0.17.3 → 0.18.1
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/README.md +4 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/contexts/AuthControllerContext.d.ts +2 -1
- package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
- package/dist/contexts/DialogsProvider.d.ts +2 -1
- package/dist/contexts/RebaseDataContext.d.ts +10 -1
- package/dist/contexts/StorageSourceContext.d.ts +11 -1
- package/dist/core/RebaseProps.d.ts +2 -12
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug.js +3631 -0
- package/dist/hooks/data/delete.d.ts +16 -7
- package/dist/hooks/data/save.d.ts +18 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAdminModeController.d.ts +5 -4
- package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
- package/dist/hooks/useStudioBridge.d.ts +33 -0
- package/dist/index.es.js +2599 -6980
- package/dist/index.es.js.map +1 -1
- package/dist/vitePlugin.d.ts +22 -0
- package/dist/vitePlugin.js +40 -1
- package/package.json +42 -31
- package/src/auth/api.ts +0 -134
- package/src/auth/index.ts +0 -26
- package/src/auth/types.ts +0 -115
- package/src/auth/useRebaseAuthController.ts +0 -345
- package/src/collections/collection_view_config.ts +0 -76
- package/src/collections/entity-display-cache.ts +0 -195
- package/src/collections/entity-display.ts +0 -77
- package/src/collections/entity_image_preview.ts +0 -61
- package/src/collections/filter-operator-resolution.ts +0 -119
- package/src/collections/form-layout.ts +0 -394
- package/src/collections/index.ts +0 -13
- package/src/collections/navigation_from_path.ts +0 -123
- package/src/collections/navigation_utils.ts +0 -171
- package/src/collections/parent_references_from_path.ts +0 -59
- package/src/collections/property-path.ts +0 -30
- package/src/collections/property_presentation.ts +0 -264
- package/src/collections/summary-property.ts +0 -114
- package/src/collections/title-property.ts +0 -312
- package/src/components/AIIcon.tsx +0 -47
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/Debug/UIReferenceView.tsx +0 -1053
- package/src/components/Debug/UIStyleGuide.tsx +0 -159
- package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
- package/src/components/Debug/collection-views/index.ts +0 -3
- package/src/components/Debug/collection-views/sample_data.ts +0 -44
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
- package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
- package/src/components/ErrorTooltip.tsx +0 -12
- package/src/components/ErrorView.tsx +0 -94
- package/src/components/LanguageToggle.tsx +0 -59
- package/src/components/LoginView/LoginView.tsx +0 -1235
- package/src/components/LoginView/index.ts +0 -2
- package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
- package/src/components/NotFoundPage.tsx +0 -27
- package/src/components/RebaseAuth.tsx +0 -27
- package/src/components/RebaseLogo.tsx +0 -88
- package/src/components/SchemaDriftBanner.tsx +0 -102
- package/src/components/UnsavedChangesDialog.tsx +0 -46
- package/src/components/UserDisplay.tsx +0 -54
- package/src/components/UserSelectPopover.tsx +0 -399
- package/src/components/UserSettingsView.tsx +0 -318
- package/src/components/common/index.ts +0 -6
- package/src/components/common/table_height.tsx +0 -21
- package/src/components/common/types.tsx +0 -66
- package/src/components/common/useColumnsIds.tsx +0 -261
- package/src/components/common/useDataTableController.tsx +0 -571
- package/src/components/common/useDebouncedData.ts +0 -49
- package/src/components/common/useScrollRestoration.tsx +0 -68
- package/src/components/index.tsx +0 -26
- package/src/contexts/AdminModeController.tsx +0 -11
- package/src/contexts/AnalyticsContext.tsx +0 -4
- package/src/contexts/AuthControllerContext.tsx +0 -4
- package/src/contexts/CollectionResolverContext.tsx +0 -24
- package/src/contexts/CollectionScopeContext.tsx +0 -68
- package/src/contexts/ComponentOverrideContext.tsx +0 -81
- package/src/contexts/CustomizationControllerContext.tsx +0 -4
- package/src/contexts/DataDriverContext.tsx +0 -4
- package/src/contexts/DataSourcesContext.tsx +0 -36
- package/src/contexts/DatabaseAdminContext.tsx +0 -4
- package/src/contexts/DialogsProvider.tsx +0 -56
- package/src/contexts/EffectiveRoleController.tsx +0 -12
- package/src/contexts/ModeController.tsx +0 -11
- package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
- package/src/contexts/RebaseDataContext.tsx +0 -4
- package/src/contexts/SnackbarProvider.tsx +0 -15
- package/src/contexts/StorageSourceContext.tsx +0 -4
- package/src/contexts/StorageSourcesContext.tsx +0 -30
- package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
- package/src/contexts/index.ts +0 -18
- package/src/core/PluginLifecycleManager.tsx +0 -96
- package/src/core/PluginProviderStack.tsx +0 -40
- package/src/core/Rebase.tsx +0 -488
- package/src/core/RebaseProps.tsx +0 -333
- package/src/core/RebaseRouter.tsx +0 -18
- package/src/core/RebaseRoutes.tsx +0 -24
- package/src/core/index.tsx +0 -5
- package/src/hooks/ApiConfigContext.tsx +0 -83
- package/src/hooks/data/collectionQuery.ts +0 -55
- package/src/hooks/data/delete.ts +0 -56
- package/src/hooks/data/save.ts +0 -78
- package/src/hooks/data/useCollection.tsx +0 -183
- package/src/hooks/data/useData.tsx +0 -18
- package/src/hooks/data/useDataOrder.ts +0 -26
- package/src/hooks/data/useFetch.tsx +0 -156
- package/src/hooks/data/useRelationSelector.tsx +0 -280
- package/src/hooks/index.tsx +0 -45
- package/src/hooks/useAdminModeController.tsx +0 -23
- package/src/hooks/useAnalyticsController.tsx +0 -8
- package/src/hooks/useAuthController.tsx +0 -15
- package/src/hooks/useAuthSubscription.ts +0 -130
- package/src/hooks/useBackendStorageSource.ts +0 -295
- package/src/hooks/useBridgeRegistration.tsx +0 -32
- package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
- package/src/hooks/useBuildAdminModeController.tsx +0 -29
- package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
- package/src/hooks/useBuildModeController.tsx +0 -73
- package/src/hooks/useClipboard.tsx +0 -158
- package/src/hooks/useCollapsedGroups.ts +0 -116
- package/src/hooks/useComponentOverride.tsx +0 -59
- package/src/hooks/useCustomizationController.tsx +0 -14
- package/src/hooks/useDialogsController.tsx +0 -15
- package/src/hooks/useEffectiveRoleController.tsx +0 -10
- package/src/hooks/useLargeLayout.tsx +0 -65
- package/src/hooks/useModeController.tsx +0 -23
- package/src/hooks/useNavigationBlocker.tsx +0 -108
- package/src/hooks/usePermissions.ts +0 -45
- package/src/hooks/useRebaseClient.tsx +0 -10
- package/src/hooks/useRebaseContext.tsx +0 -84
- package/src/hooks/useRebaseRegistry.tsx +0 -91
- package/src/hooks/useResolvedComponent.tsx +0 -195
- package/src/hooks/useSlot.tsx +0 -64
- package/src/hooks/useSnackbarController.tsx +0 -80
- package/src/hooks/useStorageSource.tsx +0 -11
- package/src/hooks/useStudioBridge.tsx +0 -258
- package/src/hooks/useTranslation.ts +0 -38
- package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
- package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
- package/src/i18n/RebaseI18nProvider.tsx +0 -159
- package/src/index.ts +0 -32
- package/src/internal/common.tsx +0 -28
- package/src/internal/useRestoreScroll.tsx +0 -69
- package/src/locales/de.ts +0 -941
- package/src/locales/en.ts +0 -1021
- package/src/locales/es.ts +0 -962
- package/src/locales/fr.ts +0 -941
- package/src/locales/hi.ts +0 -941
- package/src/locales/it.ts +0 -941
- package/src/locales/pt.ts +0 -949
- package/src/util/constants.ts +0 -7
- package/src/util/createFormexStub.tsx +0 -66
- package/src/util/entity_cache.ts +0 -242
- package/src/util/enums.ts +0 -34
- package/src/util/icon_list.ts +0 -22
- package/src/util/icons.tsx +0 -129
- package/src/util/index.ts +0 -13
- package/src/util/local_storage.ts +0 -66
- package/src/util/previews.ts +0 -127
- package/src/util/useStorageUploadController.tsx +0 -354
- package/src/vitePlugin.ts +0 -215
- /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
- /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
- /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
|
-
}
|