@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,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
+ }