@rebasepro/app 0.0.1-canary.4829d6e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/auth/api.d.ts +66 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/types.d.ts +127 -0
- package/dist/auth/useRebaseAuthController.d.ts +13 -0
- package/dist/components/AIIcon.d.ts +17 -0
- package/dist/components/AuthSimulationMenu.d.ts +0 -0
- package/dist/components/ConfirmationDialog.d.ts +9 -0
- package/dist/components/Debug/UIReferenceView.d.ts +13 -0
- package/dist/components/Debug/UIStyleGuide.d.ts +2 -0
- package/dist/components/Debug/collection-views/CardViewDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/CollectionTableDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/KanbanBoardDemo.d.ts +2 -0
- package/dist/components/Debug/collection-views/index.d.ts +3 -0
- package/dist/components/Debug/collection-views/sample_data.d.ts +23 -0
- package/dist/components/Debug/crm-dashboard/CalendarWidget.d.ts +22 -0
- package/dist/components/Debug/crm-dashboard/CrmDashboardDemo.d.ts +2 -0
- package/dist/components/Debug/crm-dashboard/DashboardMetrics.d.ts +15 -0
- package/dist/components/Debug/crm-dashboard/PipelineOverview.d.ts +34 -0
- package/dist/components/Debug/crm-dashboard/RecentActivity.d.ts +19 -0
- package/dist/components/Debug/crm-dashboard/TaskFilters.d.ts +30 -0
- package/dist/components/Debug/crm-dashboard/TaskTable.d.ts +50 -0
- package/dist/components/Debug/crm-dashboard/TasksView.d.ts +41 -0
- package/dist/components/ErrorTooltip.d.ts +3 -0
- package/dist/components/ErrorView.d.ts +21 -0
- package/dist/components/LanguageToggle.d.ts +2 -0
- package/dist/components/LoginView/LoginView.d.ts +113 -0
- package/dist/components/LoginView/index.d.ts +2 -0
- package/dist/components/NotFoundPage.d.ts +2 -0
- package/dist/components/RebaseAuth.d.ts +10 -0
- package/dist/components/RebaseLogo.d.ts +7 -0
- package/dist/components/SchemaDriftBanner.d.ts +27 -0
- package/dist/components/UnsavedChangesDialog.d.ts +9 -0
- package/dist/components/UserDisplay.d.ts +7 -0
- package/dist/components/UserSelectPopover.d.ts +63 -0
- package/dist/components/UserSettingsView.d.ts +2 -0
- package/dist/components/common/index.d.ts +6 -0
- package/dist/components/common/table_height.d.ts +5 -0
- package/dist/components/common/types.d.ts +66 -0
- package/dist/components/common/useColumnsIds.d.ts +9 -0
- package/dist/components/common/useDataTableController.d.ts +45 -0
- package/dist/components/common/useDebouncedData.d.ts +9 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/components/index.d.ts +18 -0
- package/dist/contexts/AdminModeController.d.ts +4 -0
- package/dist/contexts/AnalyticsContext.d.ts +3 -0
- package/dist/contexts/AuthControllerContext.d.ts +3 -0
- package/dist/contexts/CollectionScopeContext.d.ts +50 -0
- package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
- package/dist/contexts/CustomizationControllerContext.d.ts +3 -0
- package/dist/contexts/DataDriverContext.d.ts +3 -0
- package/dist/contexts/DataSourcesContext.d.ts +31 -0
- package/dist/contexts/DatabaseAdminContext.d.ts +3 -0
- package/dist/contexts/DialogsProvider.d.ts +4 -0
- package/dist/contexts/EffectiveRoleController.d.ts +4 -0
- package/dist/contexts/ModeController.d.ts +4 -0
- package/dist/contexts/RebaseClientInstanceContext.d.ts +6 -0
- package/dist/contexts/RebaseDataContext.d.ts +3 -0
- package/dist/contexts/SnackbarProvider.d.ts +2 -0
- package/dist/contexts/StorageSourceContext.d.ts +3 -0
- package/dist/contexts/StorageSourcesContext.d.ts +25 -0
- package/dist/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
- package/dist/contexts/index.d.ts +17 -0
- package/dist/core/PluginLifecycleManager.d.ts +17 -0
- package/dist/core/PluginProviderStack.d.ts +21 -0
- package/dist/core/Rebase.d.ts +15 -0
- package/dist/core/RebaseProps.d.ts +291 -0
- package/dist/core/RebaseRouter.d.ts +4 -0
- package/dist/core/RebaseRoutes.d.ts +17 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/hooks/ApiConfigContext.d.ts +24 -0
- package/dist/hooks/data/delete.d.ts +33 -0
- package/dist/hooks/data/save.d.ts +40 -0
- package/dist/hooks/data/useCollection.d.ts +62 -0
- package/dist/hooks/data/useData.d.ts +13 -0
- package/dist/hooks/data/useDataOrder.d.ts +12 -0
- package/dist/hooks/data/useFetch.d.ts +43 -0
- package/dist/hooks/data/useRelationSelector.d.ts +52 -0
- package/dist/hooks/index.d.ts +36 -0
- package/dist/hooks/useAdminModeController.d.ts +19 -0
- package/dist/hooks/useAnalyticsController.d.ts +5 -0
- package/dist/hooks/useAuthController.d.ts +11 -0
- package/dist/hooks/useAuthSubscription.d.ts +2 -0
- package/dist/hooks/useBackendStorageSource.d.ts +30 -0
- package/dist/hooks/useBridgeRegistration.d.ts +18 -0
- package/dist/hooks/useBrowserTitleAndIcon.d.ts +6 -0
- package/dist/hooks/useBuildAdminModeController.d.ts +6 -0
- package/dist/hooks/useBuildEffectiveRoleController.d.ts +8 -0
- package/dist/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
- package/dist/hooks/useBuildModeController.d.ts +6 -0
- package/dist/hooks/useClipboard.d.ts +57 -0
- package/dist/hooks/useCollapsedGroups.d.ts +27 -0
- package/dist/hooks/useComponentOverride.d.ts +32 -0
- package/dist/hooks/useCustomizationController.d.ts +11 -0
- package/dist/hooks/useDialogsController.d.ts +11 -0
- package/dist/hooks/useEffectiveRoleController.d.ts +7 -0
- package/dist/hooks/useLargeLayout.d.ts +1 -0
- package/dist/hooks/useModeController.d.ts +19 -0
- package/dist/hooks/usePermissions.d.ts +12 -0
- package/dist/hooks/useRebaseClient.d.ts +5 -0
- package/dist/hooks/useRebaseContext.d.ts +11 -0
- package/dist/hooks/useRebaseRegistry.d.ts +34 -0
- package/dist/hooks/useResolvedComponent.d.ts +47 -0
- package/dist/hooks/useSlot.d.ts +18 -0
- package/dist/hooks/useSnackbarController.d.ts +20 -0
- package/dist/hooks/useStorageSource.d.ts +6 -0
- package/dist/hooks/useStudioBridge.d.ts +79 -0
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/hooks/useUnsavedChangesDialog.d.ts +12 -0
- package/dist/hooks/useUserConfigurationPersistence.d.ts +8 -0
- package/dist/i18n/RebaseI18nProvider.d.ts +33 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.es.js +16843 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +17021 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/internal/common.d.ts +14 -0
- package/dist/internal/useRestoreScroll.d.ts +15 -0
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- package/dist/util/constants.d.ts +1 -0
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +22 -0
- package/dist/util/enums.d.ts +5 -0
- package/dist/util/icon_list.d.ts +5 -0
- package/dist/util/icons.d.ts +20 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/previews.d.ts +4 -0
- package/dist/util/useStorageUploadController.d.ts +44 -0
- package/dist/vitePlugin.d.ts +60 -0
- package/dist/vitePlugin.js +113 -0
- package/package.json +145 -0
- package/src/auth/api.ts +148 -0
- package/src/auth/index.ts +25 -0
- package/src/auth/types.ts +115 -0
- package/src/auth/useRebaseAuthController.ts +328 -0
- package/src/components/AIIcon.tsx +47 -0
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +48 -0
- package/src/components/Debug/UIReferenceView.tsx +958 -0
- package/src/components/Debug/UIStyleGuide.tsx +156 -0
- package/src/components/Debug/collection-views/CardViewDemo.tsx +85 -0
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +107 -0
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +90 -0
- package/src/components/Debug/collection-views/index.ts +3 -0
- package/src/components/Debug/collection-views/sample_data.ts +42 -0
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +389 -0
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +1102 -0
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +133 -0
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +290 -0
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +215 -0
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +184 -0
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +427 -0
- package/src/components/Debug/crm-dashboard/TasksView.tsx +234 -0
- package/src/components/ErrorTooltip.tsx +12 -0
- package/src/components/ErrorView.tsx +94 -0
- package/src/components/LanguageToggle.tsx +59 -0
- package/src/components/LoginView/LoginView.tsx +932 -0
- package/src/components/LoginView/index.ts +2 -0
- package/src/components/NotFoundPage.tsx +27 -0
- package/src/components/RebaseAuth.tsx +27 -0
- package/src/components/RebaseLogo.tsx +29 -0
- package/src/components/SchemaDriftBanner.tsx +102 -0
- package/src/components/UnsavedChangesDialog.tsx +46 -0
- package/src/components/UserDisplay.tsx +54 -0
- package/src/components/UserSelectPopover.tsx +399 -0
- package/src/components/UserSettingsView.tsx +318 -0
- package/src/components/common/index.ts +6 -0
- package/src/components/common/table_height.tsx +21 -0
- package/src/components/common/types.tsx +66 -0
- package/src/components/common/useColumnsIds.tsx +222 -0
- package/src/components/common/useDataTableController.tsx +514 -0
- package/src/components/common/useDebouncedData.ts +49 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/components/index.tsx +26 -0
- package/src/contexts/AdminModeController.tsx +11 -0
- package/src/contexts/AnalyticsContext.tsx +4 -0
- package/src/contexts/AuthControllerContext.tsx +4 -0
- package/src/contexts/CollectionScopeContext.tsx +67 -0
- package/src/contexts/ComponentOverrideContext.tsx +81 -0
- package/src/contexts/CustomizationControllerContext.tsx +4 -0
- package/src/contexts/DataDriverContext.tsx +4 -0
- package/src/contexts/DataSourcesContext.tsx +36 -0
- package/src/contexts/DatabaseAdminContext.tsx +4 -0
- package/src/contexts/DialogsProvider.tsx +56 -0
- package/src/contexts/EffectiveRoleController.tsx +12 -0
- package/src/contexts/ModeController.tsx +11 -0
- package/src/contexts/RebaseClientInstanceContext.tsx +7 -0
- package/src/contexts/RebaseDataContext.tsx +4 -0
- package/src/contexts/SnackbarProvider.tsx +15 -0
- package/src/contexts/StorageSourceContext.tsx +4 -0
- package/src/contexts/StorageSourcesContext.tsx +30 -0
- package/src/contexts/UserConfigurationPersistenceContext.tsx +4 -0
- package/src/contexts/index.ts +17 -0
- package/src/core/PluginLifecycleManager.tsx +95 -0
- package/src/core/PluginProviderStack.tsx +40 -0
- package/src/core/Rebase.tsx +439 -0
- package/src/core/RebaseProps.tsx +320 -0
- package/src/core/RebaseRouter.tsx +17 -0
- package/src/core/RebaseRoutes.tsx +24 -0
- package/src/core/index.tsx +5 -0
- package/src/hooks/ApiConfigContext.tsx +41 -0
- package/src/hooks/data/delete.ts +55 -0
- package/src/hooks/data/save.ts +77 -0
- package/src/hooks/data/useCollection.tsx +176 -0
- package/src/hooks/data/useData.tsx +18 -0
- package/src/hooks/data/useDataOrder.ts +26 -0
- package/src/hooks/data/useFetch.tsx +140 -0
- package/src/hooks/data/useRelationSelector.tsx +234 -0
- package/src/hooks/index.tsx +45 -0
- package/src/hooks/useAdminModeController.tsx +23 -0
- package/src/hooks/useAnalyticsController.tsx +8 -0
- package/src/hooks/useAuthController.tsx +14 -0
- package/src/hooks/useAuthSubscription.ts +87 -0
- package/src/hooks/useBackendStorageSource.ts +288 -0
- package/src/hooks/useBridgeRegistration.tsx +32 -0
- package/src/hooks/useBrowserTitleAndIcon.tsx +23 -0
- package/src/hooks/useBuildAdminModeController.tsx +24 -0
- package/src/hooks/useBuildEffectiveRoleController.tsx +31 -0
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +75 -0
- package/src/hooks/useBuildModeController.tsx +71 -0
- package/src/hooks/useClipboard.tsx +158 -0
- package/src/hooks/useCollapsedGroups.ts +116 -0
- package/src/hooks/useComponentOverride.tsx +59 -0
- package/src/hooks/useCustomizationController.tsx +14 -0
- package/src/hooks/useDialogsController.tsx +15 -0
- package/src/hooks/useEffectiveRoleController.tsx +10 -0
- package/src/hooks/useLargeLayout.tsx +65 -0
- package/src/hooks/useModeController.tsx +23 -0
- package/src/hooks/usePermissions.ts +44 -0
- package/src/hooks/useRebaseClient.tsx +10 -0
- package/src/hooks/useRebaseContext.tsx +83 -0
- package/src/hooks/useRebaseRegistry.tsx +91 -0
- package/src/hooks/useResolvedComponent.tsx +159 -0
- package/src/hooks/useSlot.tsx +64 -0
- package/src/hooks/useSnackbarController.tsx +52 -0
- package/src/hooks/useStorageSource.tsx +11 -0
- package/src/hooks/useStudioBridge.tsx +209 -0
- package/src/hooks/useTranslation.ts +33 -0
- package/src/hooks/useUnsavedChangesDialog.tsx +64 -0
- package/src/hooks/useUserConfigurationPersistence.tsx +11 -0
- package/src/i18n/RebaseI18nProvider.tsx +160 -0
- package/src/index.ts +25 -0
- package/src/internal/common.tsx +16 -0
- package/src/internal/useRestoreScroll.tsx +69 -0
- package/src/locales/de.ts +902 -0
- package/src/locales/en.ts +980 -0
- package/src/locales/es.ts +922 -0
- package/src/locales/fr.ts +901 -0
- package/src/locales/hi.ts +901 -0
- package/src/locales/it.ts +901 -0
- package/src/locales/pt.ts +909 -0
- package/src/util/constants.ts +7 -0
- package/src/util/createFormexStub.tsx +66 -0
- package/src/util/entity_cache.ts +239 -0
- package/src/util/enums.ts +32 -0
- package/src/util/icon_list.ts +22 -0
- package/src/util/icons.tsx +149 -0
- package/src/util/index.ts +12 -0
- package/src/util/previews.ts +105 -0
- package/src/util/useStorageUploadController.tsx +350 -0
- package/src/vitePlugin.ts +196 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import React, { useState, useMemo, useCallback } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Typography,
|
|
4
|
+
cls,
|
|
5
|
+
Chip,
|
|
6
|
+
Skeleton,
|
|
7
|
+
IconButton,
|
|
8
|
+
Separator,
|
|
9
|
+
Button
|
|
10
|
+
} from "@rebasepro/ui";
|
|
11
|
+
import {
|
|
12
|
+
ChevronLeft,
|
|
13
|
+
ChevronRight
|
|
14
|
+
} from "lucide-react";
|
|
15
|
+
|
|
16
|
+
/* ── Types ─────────────────────────────────────────────── */
|
|
17
|
+
|
|
18
|
+
interface TaskEntity {
|
|
19
|
+
id: string;
|
|
20
|
+
values: {
|
|
21
|
+
title: string;
|
|
22
|
+
status: string;
|
|
23
|
+
dueDate: string | null;
|
|
24
|
+
stageId: string | null;
|
|
25
|
+
priority?: string | null;
|
|
26
|
+
clientId: string | null;
|
|
27
|
+
client?: { name?: string } | null;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CalendarWidgetProps {
|
|
32
|
+
loading: boolean;
|
|
33
|
+
tasks: TaskEntity[];
|
|
34
|
+
onOpenTask: (taskId: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── Date helpers ──────────────────────────────────────── */
|
|
38
|
+
|
|
39
|
+
function startOfMonth(date: Date): Date {
|
|
40
|
+
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function endOfMonth(date: Date): Date {
|
|
44
|
+
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isSameDay(a: Date, b: Date): boolean {
|
|
48
|
+
return (
|
|
49
|
+
a.getFullYear() === b.getFullYear() &&
|
|
50
|
+
a.getMonth() === b.getMonth() &&
|
|
51
|
+
a.getDate() === b.getDate()
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toDateKey(date: Date): string {
|
|
56
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isBeforeToday(date: Date): boolean {
|
|
60
|
+
const today = new Date();
|
|
61
|
+
today.setHours(0, 0, 0, 0);
|
|
62
|
+
const d = new Date(date);
|
|
63
|
+
d.setHours(0, 0, 0, 0);
|
|
64
|
+
return d < today;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ── Stage label map ───────────────────────────────────── */
|
|
68
|
+
|
|
69
|
+
const stageLabels: Record<string, string> = {
|
|
70
|
+
incoming: "Incoming",
|
|
71
|
+
email_sent: "Intro Email",
|
|
72
|
+
discovery_call: "Discovery",
|
|
73
|
+
agreements: "Agreements",
|
|
74
|
+
onboarding: "Onboarding",
|
|
75
|
+
active: "Active",
|
|
76
|
+
delivered: "Delivered",
|
|
77
|
+
completed: "Done"
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/* ── Calendar grid builder ─────────────────────────────── */
|
|
81
|
+
|
|
82
|
+
interface CalendarDay {
|
|
83
|
+
date: Date;
|
|
84
|
+
isCurrentMonth: boolean;
|
|
85
|
+
isToday: boolean;
|
|
86
|
+
dateKey: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildCalendarGrid(viewDate: Date): CalendarDay[] {
|
|
90
|
+
const today = new Date();
|
|
91
|
+
const first = startOfMonth(viewDate);
|
|
92
|
+
const last = endOfMonth(viewDate);
|
|
93
|
+
|
|
94
|
+
// Monday = 0 ... Sunday = 6
|
|
95
|
+
let startDay = first.getDay() - 1;
|
|
96
|
+
if (startDay < 0) startDay = 6;
|
|
97
|
+
|
|
98
|
+
const days: CalendarDay[] = [];
|
|
99
|
+
|
|
100
|
+
// Fill leading days from previous month
|
|
101
|
+
for (let i = startDay - 1; i >= 0; i--) {
|
|
102
|
+
const d = new Date(first);
|
|
103
|
+
d.setDate(d.getDate() - (i + 1));
|
|
104
|
+
days.push({
|
|
105
|
+
date: d,
|
|
106
|
+
isCurrentMonth: false,
|
|
107
|
+
isToday: isSameDay(d, today),
|
|
108
|
+
dateKey: toDateKey(d)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Current month days
|
|
113
|
+
for (let d = 1; d <= last.getDate(); d++) {
|
|
114
|
+
const date = new Date(viewDate.getFullYear(), viewDate.getMonth(), d);
|
|
115
|
+
days.push({
|
|
116
|
+
date,
|
|
117
|
+
isCurrentMonth: true,
|
|
118
|
+
isToday: isSameDay(date, today),
|
|
119
|
+
dateKey: toDateKey(date)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fill trailing days to complete the last week
|
|
124
|
+
while (days.length % 7 !== 0) {
|
|
125
|
+
const d = new Date(last);
|
|
126
|
+
d.setDate(d.getDate() + (days.length - (startDay + last.getDate())) + 1);
|
|
127
|
+
days.push({
|
|
128
|
+
date: d,
|
|
129
|
+
isCurrentMonth: false,
|
|
130
|
+
isToday: isSameDay(d, today),
|
|
131
|
+
dateKey: toDateKey(d)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return days;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ── Component ─────────────────────────────────────────── */
|
|
139
|
+
|
|
140
|
+
const DAY_HEADERS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
|
|
141
|
+
|
|
142
|
+
const MONTH_NAMES = [
|
|
143
|
+
"January", "February", "March", "April", "May", "June",
|
|
144
|
+
"July", "August", "September", "October", "November", "December"
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
export function CalendarWidget({ loading, tasks, onOpenTask }: CalendarWidgetProps) {
|
|
148
|
+
const [viewDate, setViewDate] = useState(() => new Date());
|
|
149
|
+
const [selectedDay, setSelectedDay] = useState<string | null>(null);
|
|
150
|
+
|
|
151
|
+
/* ── Group tasks by date key ── */
|
|
152
|
+
const tasksByDate = useMemo(() => {
|
|
153
|
+
const map = new Map<string, TaskEntity[]>();
|
|
154
|
+
for (const task of tasks) {
|
|
155
|
+
if (!task.values?.dueDate) continue;
|
|
156
|
+
const d = new Date(task.values.dueDate);
|
|
157
|
+
const key = toDateKey(d);
|
|
158
|
+
if (!map.has(key)) map.set(key, []);
|
|
159
|
+
map.get(key)!.push(task);
|
|
160
|
+
}
|
|
161
|
+
return map;
|
|
162
|
+
}, [tasks]);
|
|
163
|
+
|
|
164
|
+
/* ── Calendar grid ── */
|
|
165
|
+
const calendarDays = useMemo(() => buildCalendarGrid(viewDate), [viewDate]);
|
|
166
|
+
|
|
167
|
+
/* ── Month navigation ── */
|
|
168
|
+
const goToPrevMonth = useCallback(() => {
|
|
169
|
+
setViewDate(prev => new Date(prev.getFullYear(), prev.getMonth() - 1, 1));
|
|
170
|
+
setSelectedDay(null);
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
const goToNextMonth = useCallback(() => {
|
|
174
|
+
setViewDate(prev => new Date(prev.getFullYear(), prev.getMonth() + 1, 1));
|
|
175
|
+
setSelectedDay(null);
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const goToToday = useCallback(() => {
|
|
179
|
+
const today = new Date();
|
|
180
|
+
setViewDate(new Date(today.getFullYear(), today.getMonth(), 1));
|
|
181
|
+
setSelectedDay(toDateKey(today));
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
/* ── Selected day tasks ── */
|
|
185
|
+
const selectedDayTasks = useMemo(() => {
|
|
186
|
+
if (!selectedDay) return [];
|
|
187
|
+
return tasksByDate.get(selectedDay) || [];
|
|
188
|
+
}, [selectedDay, tasksByDate]);
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div>
|
|
192
|
+
{/* Header */}
|
|
193
|
+
<div className="flex items-center justify-between mb-3">
|
|
194
|
+
<Typography variant="subtitle1">Calendar</Typography>
|
|
195
|
+
<Button
|
|
196
|
+
variant="text"
|
|
197
|
+
size="small"
|
|
198
|
+
color="primary"
|
|
199
|
+
className="p-0 h-auto min-w-0"
|
|
200
|
+
onClick={goToToday}
|
|
201
|
+
>
|
|
202
|
+
Today
|
|
203
|
+
</Button>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
{/* Month nav */}
|
|
207
|
+
<div className="flex items-center justify-between mb-2">
|
|
208
|
+
<IconButton
|
|
209
|
+
size="smallest"
|
|
210
|
+
aria-label="Previous month"
|
|
211
|
+
onClick={goToPrevMonth}
|
|
212
|
+
>
|
|
213
|
+
<ChevronLeft className="h-4 w-4" />
|
|
214
|
+
</IconButton>
|
|
215
|
+
<Typography variant="body2" className="font-semibold">
|
|
216
|
+
{MONTH_NAMES[viewDate.getMonth()]} {viewDate.getFullYear()}
|
|
217
|
+
</Typography>
|
|
218
|
+
<IconButton
|
|
219
|
+
size="smallest"
|
|
220
|
+
aria-label="Next month"
|
|
221
|
+
onClick={goToNextMonth}
|
|
222
|
+
>
|
|
223
|
+
<ChevronRight className="h-4 w-4" />
|
|
224
|
+
</IconButton>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{loading ? (
|
|
228
|
+
<Skeleton className="h-48 w-full rounded-lg" />
|
|
229
|
+
) : (
|
|
230
|
+
<>
|
|
231
|
+
{/* Day headers */}
|
|
232
|
+
<div className="grid grid-cols-7 mb-1">
|
|
233
|
+
{DAY_HEADERS.map(d => (
|
|
234
|
+
<div key={d} className="text-center py-1">
|
|
235
|
+
<Typography variant="caption" color="secondary" className="text-[10px] font-semibold uppercase tracking-wider">
|
|
236
|
+
{d}
|
|
237
|
+
</Typography>
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
{/* Calendar grid */}
|
|
243
|
+
<div className="grid grid-cols-7 gap-0.5">
|
|
244
|
+
{calendarDays.map((day) => {
|
|
245
|
+
const dayTasks = tasksByDate.get(day.dateKey) || [];
|
|
246
|
+
const hasTasks = dayTasks.length > 0;
|
|
247
|
+
const isSelected = selectedDay === day.dateKey;
|
|
248
|
+
|
|
249
|
+
// Count tasks by status
|
|
250
|
+
const pendingCount = dayTasks.filter(t => t.values.status === "pending").length;
|
|
251
|
+
const completedCount = dayTasks.filter(t => t.values.status === "completed").length;
|
|
252
|
+
const hasOverdue = dayTasks.some(
|
|
253
|
+
t => t.values.status === "pending" && isBeforeToday(new Date(t.values.dueDate!))
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<button
|
|
258
|
+
key={day.dateKey}
|
|
259
|
+
onClick={() => setSelectedDay(isSelected ? null : day.dateKey)}
|
|
260
|
+
className={cls(
|
|
261
|
+
"flex flex-col items-center justify-center py-1.5 rounded-md transition-colors duration-100 focus:outline-none",
|
|
262
|
+
day.isCurrentMonth
|
|
263
|
+
? "text-text-primary dark:text-text-primary-dark"
|
|
264
|
+
: "text-surface-300 dark:text-surface-650",
|
|
265
|
+
day.isToday && !isSelected && "ring-1 ring-primary/50",
|
|
266
|
+
isSelected
|
|
267
|
+
? "bg-primary/10 dark:bg-primary/15 ring-1 ring-primary/60"
|
|
268
|
+
: "hover:bg-surface-100 dark:hover:bg-surface-800",
|
|
269
|
+
hasTasks && "cursor-pointer"
|
|
270
|
+
)}
|
|
271
|
+
>
|
|
272
|
+
<Typography
|
|
273
|
+
variant="caption"
|
|
274
|
+
color="inherit"
|
|
275
|
+
className={cls(
|
|
276
|
+
"leading-none tabular-nums text-[11px]",
|
|
277
|
+
day.isToday && "font-semibold text-primary dark:text-primary-light"
|
|
278
|
+
)}
|
|
279
|
+
>
|
|
280
|
+
{day.date.getDate()}
|
|
281
|
+
</Typography>
|
|
282
|
+
|
|
283
|
+
{/* Task dots */}
|
|
284
|
+
{hasTasks ? (
|
|
285
|
+
<div className="flex items-center gap-0.5 mt-1 h-[5px]">
|
|
286
|
+
{hasOverdue && (
|
|
287
|
+
<span className="w-[5px] h-[5px] rounded-full bg-red-500" />
|
|
288
|
+
)}
|
|
289
|
+
{pendingCount > 0 && !hasOverdue && (
|
|
290
|
+
<span className="w-[5px] h-[5px] rounded-full bg-primary" />
|
|
291
|
+
)}
|
|
292
|
+
{completedCount > 0 && (
|
|
293
|
+
<span className="w-[5px] h-[5px] rounded-full bg-emerald-500" />
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
) : (
|
|
297
|
+
<div className="h-[5px] mt-1" />
|
|
298
|
+
)}
|
|
299
|
+
</button>
|
|
300
|
+
);
|
|
301
|
+
})}
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
{/* Dot legend */}
|
|
305
|
+
<div className="flex items-center gap-3 mt-3 mb-1 px-1">
|
|
306
|
+
<div className="flex items-center gap-1">
|
|
307
|
+
<span className="w-[5px] h-[5px] rounded-full bg-primary" />
|
|
308
|
+
<Typography variant="caption" color="secondary" className="text-[10px]">Pending</Typography>
|
|
309
|
+
</div>
|
|
310
|
+
<div className="flex items-center gap-1">
|
|
311
|
+
<span className="w-[5px] h-[5px] rounded-full bg-red-500" />
|
|
312
|
+
<Typography variant="caption" color="secondary" className="text-[10px]">Overdue</Typography>
|
|
313
|
+
</div>
|
|
314
|
+
<div className="flex items-center gap-1">
|
|
315
|
+
<span className="w-[5px] h-[5px] rounded-full bg-emerald-500" />
|
|
316
|
+
<Typography variant="caption" color="secondary" className="text-[10px]">Done</Typography>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Selected day task list */}
|
|
321
|
+
{selectedDay && (
|
|
322
|
+
<div className="mt-2">
|
|
323
|
+
<Separator orientation="horizontal" className="mb-2" />
|
|
324
|
+
{selectedDayTasks.length === 0 ? (
|
|
325
|
+
<div className="py-3 text-center">
|
|
326
|
+
<Typography variant="caption" color="secondary">
|
|
327
|
+
No tasks on this day
|
|
328
|
+
</Typography>
|
|
329
|
+
</div>
|
|
330
|
+
) : (
|
|
331
|
+
<div className="flex flex-col gap-0.5">
|
|
332
|
+
<Typography variant="caption" color="secondary" className="mb-1 px-1">
|
|
333
|
+
{selectedDayTasks.length} task{selectedDayTasks.length !== 1 ? "s" : ""} — {new Date(selectedDay + "T00:00:00").toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" })}
|
|
334
|
+
</Typography>
|
|
335
|
+
{selectedDayTasks.map((task) => {
|
|
336
|
+
const isPending = task.values.status === "pending";
|
|
337
|
+
const isOverdue = isPending && isBeforeToday(new Date(task.values.dueDate!));
|
|
338
|
+
return (
|
|
339
|
+
<button
|
|
340
|
+
key={task.id}
|
|
341
|
+
onClick={() => onOpenTask(task.id)}
|
|
342
|
+
className={cls(
|
|
343
|
+
"flex items-start gap-2 px-2 py-1.5 rounded-md text-left w-full",
|
|
344
|
+
"hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors duration-100 focus:outline-none"
|
|
345
|
+
)}
|
|
346
|
+
>
|
|
347
|
+
{/* Status dot */}
|
|
348
|
+
<span
|
|
349
|
+
className={cls(
|
|
350
|
+
"w-1.5 h-1.5 rounded-full mt-1.5 shrink-0",
|
|
351
|
+
isOverdue
|
|
352
|
+
? "bg-red-500"
|
|
353
|
+
: isPending
|
|
354
|
+
? "bg-primary"
|
|
355
|
+
: "bg-emerald-500"
|
|
356
|
+
)}
|
|
357
|
+
/>
|
|
358
|
+
<div className="flex-1 min-w-0">
|
|
359
|
+
<Typography variant="body2" className="truncate leading-snug">
|
|
360
|
+
{task.values.title}
|
|
361
|
+
</Typography>
|
|
362
|
+
<div className="flex items-center gap-2">
|
|
363
|
+
{task.values.client?.name && (
|
|
364
|
+
<Typography variant="caption" color="secondary" className="truncate text-[10px]">
|
|
365
|
+
{task.values.client.name}
|
|
366
|
+
</Typography>
|
|
367
|
+
)}
|
|
368
|
+
{task.values.stageId && (
|
|
369
|
+
<Typography variant="caption" color="disabled" className="truncate text-[10px]">
|
|
370
|
+
{stageLabels[task.values.stageId] || task.values.stageId}
|
|
371
|
+
</Typography>
|
|
372
|
+
)}
|
|
373
|
+
</div>
|
|
374
|
+
</div>
|
|
375
|
+
{task.values.priority === "high" && (
|
|
376
|
+
<Chip colorScheme="red" size="smallest">High</Chip>
|
|
377
|
+
)}
|
|
378
|
+
</button>
|
|
379
|
+
);
|
|
380
|
+
})}
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
)}
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
387
|
+
</div>
|
|
388
|
+
);
|
|
389
|
+
}
|