@tuturuuu/ui 0.2.0 → 0.3.2

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 (129) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/package.json +79 -67
  3. package/src/components/ui/__tests__/avatar.test.tsx +8 -5
  4. package/src/components/ui/calendar-app/components/calendar-connections-compact.tsx +414 -0
  5. package/src/components/ui/calendar-app/components/calendar-connections-manager.tsx +5 -1
  6. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +529 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-unified.tsx +26 -1429
  8. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +711 -0
  9. package/src/components/ui/chart.test.tsx +29 -0
  10. package/src/components/ui/chart.tsx +12 -3
  11. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +396 -2
  12. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +36 -8
  13. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +14 -0
  14. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +5 -0
  15. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +21 -7
  16. package/src/components/ui/chat/chat-agent-details-utils.test.ts +73 -0
  17. package/src/components/ui/chat/chat-agent-details-utils.tsx +100 -26
  18. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +517 -0
  19. package/src/components/ui/chat/chat-workspace.tsx +31 -1
  20. package/src/components/ui/chat/hooks-messages.test.tsx +45 -1
  21. package/src/components/ui/chat/hooks-messages.ts +1 -1
  22. package/src/components/ui/chat/hooks-realtime.ts +13 -16
  23. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +24 -1
  24. package/src/components/ui/custom/__tests__/tuturuuu-logo.test.ts +12 -3
  25. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +39 -0
  26. package/src/components/ui/custom/common-footer.tsx +16 -1
  27. package/src/components/ui/custom/production-indicator.tsx +1 -1
  28. package/src/components/ui/custom/settings/sidebar-settings.tsx +1 -1
  29. package/src/components/ui/custom/settings/task-settings.tsx +18 -0
  30. package/src/components/ui/custom/settings-dialog-shell.tsx +38 -23
  31. package/src/components/ui/custom/sidebar-context-compile-graph.test.ts +60 -0
  32. package/src/components/ui/custom/sidebar-context.tsx +61 -61
  33. package/src/components/ui/custom/sidebar-remote-behavior-bridge.tsx +123 -0
  34. package/src/components/ui/custom/tuturuuu-logo-urls.ts +6 -0
  35. package/src/components/ui/custom/tuturuuu-logo.tsx +25 -7
  36. package/src/components/ui/custom/workspace-select-helpers.ts +20 -0
  37. package/src/components/ui/custom/workspace-select.tsx +33 -12
  38. package/src/components/ui/finance/invoices/components/invoice-checkout-summary.tsx +7 -1
  39. package/src/components/ui/finance/invoices/components/invoice-payment-settings.tsx +3 -0
  40. package/src/components/ui/finance/invoices/components/invoice-products-permission-warning.tsx +58 -0
  41. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +12 -20
  42. package/src/components/ui/finance/invoices/hooks/use-subscription-auto-selection.ts +10 -9
  43. package/src/components/ui/finance/invoices/hooks/use-subscription-invoice-content.ts +10 -5
  44. package/src/components/ui/finance/invoices/hooks.ts +75 -20
  45. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +137 -0
  46. package/src/components/ui/finance/invoices/new-invoice-page.tsx +86 -37
  47. package/src/components/ui/finance/invoices/product-selection.test.tsx +8 -26
  48. package/src/components/ui/finance/invoices/product-selection.tsx +2 -10
  49. package/src/components/ui/finance/invoices/standard-invoice.tsx +88 -26
  50. package/src/components/ui/finance/invoices/subscription-invoice.tsx +154 -46
  51. package/src/components/ui/finance/invoices/utils.test.ts +50 -0
  52. package/src/components/ui/finance/invoices/utils.ts +75 -17
  53. package/src/components/ui/finance/shared/finance-display-amount.tsx +3 -1
  54. package/src/components/ui/finance/shared/finance-permission-warning-dialog.test.tsx +34 -0
  55. package/src/components/ui/finance/shared/finance-permission-warning-dialog.tsx +157 -0
  56. package/src/components/ui/finance/transactions/form-basic-tab.tsx +8 -0
  57. package/src/components/ui/finance/transactions/form-more-tab.tsx +8 -0
  58. package/src/components/ui/finance/transactions/form-types.ts +2 -0
  59. package/src/components/ui/finance/transactions/form.test.tsx +43 -0
  60. package/src/components/ui/finance/transactions/form.tsx +60 -0
  61. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +27 -0
  62. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +13 -1
  63. package/src/components/ui/finance/transactions/transactions-infinite-page.tsx +4 -0
  64. package/src/components/ui/finance/transactions/transactions-page.tsx +23 -1
  65. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  66. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +5 -0
  67. package/src/components/ui/legacy/calendar/calendar-content.tsx +9 -1
  68. package/src/components/ui/legacy/calendar/event-modal.tsx +146 -2
  69. package/src/components/ui/legacy/calendar/event-preview-popover.tsx +200 -0
  70. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +76 -0
  71. package/src/components/ui/legacy/calendar/smart-calendar.tsx +13 -1
  72. package/src/components/ui/legacy/meet/page.test.ts +180 -0
  73. package/src/components/ui/legacy/meet/page.tsx +87 -39
  74. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +79 -25
  75. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/__tests__/bulk-mutations-external-workspaces.test.tsx +392 -0
  76. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.test.tsx +57 -0
  77. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-island.tsx +106 -0
  78. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +106 -161
  79. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-assignees.ts +96 -150
  80. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-labels.ts +63 -79
  81. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-relations-projects.ts +64 -83
  82. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +115 -155
  83. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-utils.ts +319 -2
  84. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operations.ts +8 -1
  85. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +63 -37
  86. package/src/components/ui/tu-do/boards/boardId/kanban/kanban-column-collapse.ts +16 -0
  87. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +46 -0
  88. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +5 -3
  89. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +19 -7
  90. package/src/components/ui/tu-do/boards/boardId/menus/__tests__/task-menus.test.tsx +181 -2
  91. package/src/components/ui/tu-do/boards/boardId/menus/index.ts +1 -0
  92. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-menu.tsx +463 -0
  93. package/src/components/ui/tu-do/boards/boardId/menus/task-scheduling-utils.ts +109 -0
  94. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +4 -0
  95. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardCheckbox.tsx +6 -3
  96. package/src/components/ui/tu-do/boards/boardId/task-card/TaskCardDates.tsx +26 -9
  97. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-checkbox-style.ts +39 -0
  98. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.test.ts +43 -0
  99. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +33 -0
  100. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.test.ts +31 -0
  101. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-completion-checkbox-visibility.ts +9 -0
  102. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.test.tsx +124 -0
  103. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-identifier-row.tsx +88 -0
  104. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +151 -76
  105. package/src/components/ui/tu-do/boards/boardId/task-card/task-scheduling-badge.tsx +174 -0
  106. package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +34 -13
  107. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +54 -1
  108. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +158 -0
  109. package/src/components/ui/tu-do/shared/__tests__/task-dialog-manager.test.tsx +5 -2
  110. package/src/components/ui/tu-do/shared/board-client.tsx +12 -2
  111. package/src/components/ui/tu-do/shared/board-views.tsx +195 -328
  112. package/src/components/ui/tu-do/shared/list-view.tsx +18 -8
  113. package/src/components/ui/tu-do/shared/task-due-date-visibility.test.ts +72 -0
  114. package/src/components/ui/tu-do/shared/task-due-date-visibility.ts +38 -0
  115. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +6 -3
  116. package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-save.ts +2 -2
  117. package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +33 -0
  118. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +74 -3
  119. package/src/hooks/__tests__/use-task-actions.test.tsx +118 -0
  120. package/src/hooks/__tests__/use-user-config.test.tsx +65 -0
  121. package/src/hooks/__tests__/use-workspace-presence.test.tsx +1 -1
  122. package/src/hooks/use-calendar-sync.tsx +22 -277
  123. package/src/hooks/use-calendar.tsx +95 -525
  124. package/src/hooks/use-semantic-task-search.ts +10 -33
  125. package/src/hooks/use-task-actions.ts +43 -117
  126. package/src/hooks/use-user-config.ts +1 -1
  127. package/src/hooks/use-workspace-config.ts +6 -2
  128. package/src/hooks/use-workspace-presence.ts +1 -1
  129. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-actions-bar.tsx +0 -94
@@ -0,0 +1,529 @@
1
+ import {
2
+ Calendar,
3
+ ChevronDown,
4
+ Loader2,
5
+ Lock,
6
+ Plus,
7
+ RotateCcw,
8
+ Sparkles,
9
+ Target,
10
+ Trash2,
11
+ } from '@tuturuuu/icons';
12
+ import Image from 'next/image';
13
+ import type React from 'react';
14
+ import {
15
+ AlertDialog,
16
+ AlertDialogAction,
17
+ AlertDialogCancel,
18
+ AlertDialogContent,
19
+ AlertDialogDescription,
20
+ AlertDialogFooter,
21
+ AlertDialogHeader,
22
+ AlertDialogTitle,
23
+ AlertDialogTrigger,
24
+ } from '../../alert-dialog';
25
+ import { Badge } from '../../badge';
26
+ import { Button } from '../../button';
27
+ import {
28
+ Collapsible,
29
+ CollapsibleContent,
30
+ CollapsibleTrigger,
31
+ } from '../../collapsible';
32
+ import {
33
+ Dialog,
34
+ DialogContent,
35
+ DialogFooter,
36
+ DialogHeader,
37
+ DialogTitle,
38
+ } from '../../dialog';
39
+ import { Input } from '../../input';
40
+ import {
41
+ Select,
42
+ SelectContent,
43
+ SelectItem,
44
+ SelectTrigger,
45
+ SelectValue,
46
+ } from '../../select';
47
+ import { Separator } from '../../separator';
48
+ import { Switch } from '../../switch';
49
+ import type { CalendarConnectionsManagerState } from './use-calendar-connections-manager';
50
+
51
+ export function CalendarConnectionsSettingsContent({
52
+ state,
53
+ className,
54
+ }: {
55
+ state: CalendarConnectionsManagerState;
56
+ className?: string;
57
+ }) {
58
+ const {
59
+ accounts,
60
+ calendarsByAccount,
61
+ createCalendarMutation,
62
+ customCalendars,
63
+ defaultSourceData,
64
+ defaultSourceMutation,
65
+ deleteCalendarMutation,
66
+ disconnectingId,
67
+ disconnectMutation,
68
+ expandedAccounts,
69
+ getCalendarColor,
70
+ googleAuthMutation,
71
+ handleToggle,
72
+ microsoftAuthMutation,
73
+ newCalendarName,
74
+ resetCalendarDataMutation,
75
+ setNewCalendarName,
76
+ setShowCreateCalendarDialog,
77
+ showCreateCalendarDialog,
78
+ systemCalendars,
79
+ t,
80
+ togglingIds,
81
+ togglingTuturuuuIds,
82
+ toggleAccountExpanded,
83
+ toggleWorkspaceCalendarMutation,
84
+ } = state;
85
+
86
+ return (
87
+ <div className={className ? `space-y-4 ${className}` : 'space-y-4'}>
88
+ <div className="space-y-4 py-4">
89
+ <div className="space-y-2 rounded-md border p-3">
90
+ <div>
91
+ <h4 className="font-medium text-sm">
92
+ {t('default_calendar_source') || 'Default calendar source'}
93
+ </h4>
94
+ <p className="text-muted-foreground text-xs">
95
+ {t('default_calendar_source_desc') ||
96
+ 'New events use this calendar unless you choose another source.'}
97
+ </p>
98
+ </div>
99
+ <Select
100
+ value={defaultSourceData?.defaultSource?.id}
101
+ onValueChange={(value) => defaultSourceMutation.mutate(value)}
102
+ disabled={
103
+ !defaultSourceData?.options.length ||
104
+ defaultSourceMutation.isPending
105
+ }
106
+ >
107
+ <SelectTrigger className="w-full">
108
+ <SelectValue placeholder="Choose default calendar" />
109
+ </SelectTrigger>
110
+ <SelectContent>
111
+ {defaultSourceData?.options.map((option) => (
112
+ <SelectItem key={option.id} value={option.id}>
113
+ <div className="flex min-w-0 items-center gap-2">
114
+ <span
115
+ className="h-2.5 w-2.5 shrink-0 rounded-full"
116
+ style={{
117
+ backgroundColor: option.color ?? undefined,
118
+ }}
119
+ />
120
+ <span className="truncate">{option.label}</span>
121
+ <Badge variant="secondary" className="capitalize">
122
+ {option.provider}
123
+ </Badge>
124
+ </div>
125
+ </SelectItem>
126
+ ))}
127
+ </SelectContent>
128
+ </Select>
129
+ </div>
130
+
131
+ {/* Tuturuuu Calendars Section */}
132
+ <div className="space-y-3">
133
+ <div className="flex items-center justify-between">
134
+ <div className="flex items-center gap-2">
135
+ <Image
136
+ src="/icon-512x512.png"
137
+ alt="Tuturuuu"
138
+ width={24}
139
+ height={24}
140
+ />
141
+ <span className="font-medium text-sm">
142
+ {t('tuturuuu_calendars') || 'Tuturuuu Calendars'}
143
+ </span>
144
+ </div>
145
+ <Button
146
+ variant="ghost"
147
+ size="sm"
148
+ className="h-7 gap-1 text-xs"
149
+ onClick={() => setShowCreateCalendarDialog(true)}
150
+ disabled={createCalendarMutation.isPending}
151
+ >
152
+ {createCalendarMutation.isPending ? (
153
+ <Loader2 className="h-3 w-3 animate-spin" />
154
+ ) : (
155
+ <Plus className="h-3 w-3" />
156
+ )}
157
+ {t('new') || 'New'}
158
+ </Button>
159
+ <Dialog
160
+ open={showCreateCalendarDialog}
161
+ onOpenChange={(open) => {
162
+ setShowCreateCalendarDialog(open);
163
+ if (!open) setNewCalendarName('');
164
+ }}
165
+ >
166
+ <DialogContent className="sm:max-w-md">
167
+ <DialogHeader>
168
+ <DialogTitle>{t('create_calendar')}</DialogTitle>
169
+ </DialogHeader>
170
+ <div className="py-4">
171
+ <Input
172
+ value={newCalendarName}
173
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
174
+ setNewCalendarName(e.target.value)
175
+ }
176
+ placeholder={
177
+ t('enter_calendar_name') || 'Enter calendar name'
178
+ }
179
+ autoFocus
180
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
181
+ if (e.key === 'Enter' && newCalendarName.trim()) {
182
+ createCalendarMutation.mutate({
183
+ name: newCalendarName.trim(),
184
+ });
185
+ setShowCreateCalendarDialog(false);
186
+ setNewCalendarName('');
187
+ }
188
+ }}
189
+ />
190
+ </div>
191
+ <DialogFooter>
192
+ <Button
193
+ onClick={() => {
194
+ if (newCalendarName.trim()) {
195
+ createCalendarMutation.mutate({
196
+ name: newCalendarName.trim(),
197
+ });
198
+ setShowCreateCalendarDialog(false);
199
+ setNewCalendarName('');
200
+ }
201
+ }}
202
+ disabled={
203
+ !newCalendarName.trim() ||
204
+ createCalendarMutation.isPending
205
+ }
206
+ >
207
+ {createCalendarMutation.isPending && (
208
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
209
+ )}
210
+ {t('create')}
211
+ </Button>
212
+ </DialogFooter>
213
+ </DialogContent>
214
+ </Dialog>
215
+ </div>
216
+
217
+ {/* System Calendars */}
218
+ <div className="space-y-1">
219
+ {systemCalendars.map((cal) => {
220
+ const Icon =
221
+ cal.calendar_type === 'primary'
222
+ ? Calendar
223
+ : cal.calendar_type === 'tasks'
224
+ ? Target
225
+ : cal.calendar_type === 'habits'
226
+ ? Sparkles
227
+ : Calendar;
228
+ return (
229
+ <div
230
+ key={cal.id}
231
+ className="flex items-center justify-between gap-2 rounded-md px-2 py-1.5 hover:bg-muted/50"
232
+ >
233
+ <div className="flex min-w-0 flex-1 items-center gap-2">
234
+ <div
235
+ className="flex h-5 w-5 shrink-0 items-center justify-center rounded"
236
+ style={{
237
+ backgroundColor: `${getCalendarColor(cal.color || 'BLUE')}20`,
238
+ }}
239
+ >
240
+ <Icon
241
+ className="h-3 w-3"
242
+ style={{
243
+ color: getCalendarColor(cal.color || 'BLUE'),
244
+ }}
245
+ />
246
+ </div>
247
+ <span
248
+ className="line-clamp-1 break-all text-sm"
249
+ title={cal.name}
250
+ >
251
+ {cal.name}
252
+ </span>
253
+ <Lock className="h-3 w-3 shrink-0 text-muted-foreground" />
254
+ </div>
255
+ <Switch
256
+ checked={cal.is_enabled}
257
+ onCheckedChange={() =>
258
+ toggleWorkspaceCalendarMutation.mutate({
259
+ id: cal.id,
260
+ is_enabled: !cal.is_enabled,
261
+ })
262
+ }
263
+ disabled={togglingTuturuuuIds.has(cal.id)}
264
+ />
265
+ </div>
266
+ );
267
+ })}
268
+ </div>
269
+
270
+ {/* Custom Calendars */}
271
+ {customCalendars.length > 0 && (
272
+ <div className="space-y-1">
273
+ <p className="px-2 text-muted-foreground text-xs">
274
+ {t('custom_calendars') || 'Custom'}
275
+ </p>
276
+ {customCalendars.map((cal) => (
277
+ <div
278
+ key={cal.id}
279
+ className="flex items-center justify-between gap-2 rounded-md px-2 py-1.5 hover:bg-muted/50"
280
+ >
281
+ <div className="flex min-w-0 flex-1 items-center gap-2">
282
+ <div
283
+ className="h-3 w-3 shrink-0 rounded-full"
284
+ style={{
285
+ backgroundColor: getCalendarColor(cal.color || 'BLUE'),
286
+ }}
287
+ />
288
+ <span
289
+ className="line-clamp-1 break-all text-sm"
290
+ title={cal.name}
291
+ >
292
+ {cal.name}
293
+ </span>
294
+ </div>
295
+ <div className="flex items-center gap-1">
296
+ <Button
297
+ variant="ghost"
298
+ size="icon"
299
+ className="h-6 w-6 text-destructive hover:bg-destructive/10"
300
+ onClick={() => deleteCalendarMutation.mutate(cal.id)}
301
+ disabled={deleteCalendarMutation.isPending}
302
+ >
303
+ <Trash2 className="h-3 w-3" />
304
+ </Button>
305
+ <Switch
306
+ checked={cal.is_enabled}
307
+ onCheckedChange={() =>
308
+ toggleWorkspaceCalendarMutation.mutate({
309
+ id: cal.id,
310
+ is_enabled: !cal.is_enabled,
311
+ })
312
+ }
313
+ disabled={togglingTuturuuuIds.has(cal.id)}
314
+ />
315
+ </div>
316
+ </div>
317
+ ))}
318
+ </div>
319
+ )}
320
+ </div>
321
+
322
+ <Separator />
323
+
324
+ {/* Connected accounts with calendars */}
325
+ {accounts.map((account) => (
326
+ <Collapsible
327
+ key={account.id}
328
+ open={expandedAccounts.has(account.id)}
329
+ onOpenChange={() => toggleAccountExpanded(account.id)}
330
+ >
331
+ <div className="rounded-lg border">
332
+ <CollapsibleTrigger className="flex w-full items-center justify-between p-3 transition-colors hover:bg-muted/50">
333
+ <div className="flex items-center gap-3">
334
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted">
335
+ <Image
336
+ src={
337
+ account.provider === 'google'
338
+ ? '/media/logos/google.svg'
339
+ : '/media/logos/microsoft.svg'
340
+ }
341
+ alt={account.provider}
342
+ width={18}
343
+ height={18}
344
+ />
345
+ </div>
346
+ <div className="text-left">
347
+ <p className="font-medium text-sm">
348
+ {account.account_name ||
349
+ account.account_email ||
350
+ t('unknown_account')}
351
+ </p>
352
+ {account.account_email && account.account_name && (
353
+ <p className="text-muted-foreground text-xs">
354
+ {account.account_email}
355
+ </p>
356
+ )}
357
+ </div>
358
+ </div>
359
+ <div className="flex items-center gap-2">
360
+ <Badge variant="outline" className="text-xs capitalize">
361
+ {account.provider}
362
+ </Badge>
363
+ <ChevronDown
364
+ className={`h-4 w-4 transition-transform ${expandedAccounts.has(account.id) ? 'rotate-180' : ''}`}
365
+ />
366
+ </div>
367
+ </CollapsibleTrigger>
368
+
369
+ <CollapsibleContent>
370
+ <Separator />
371
+ <div className="space-y-2 p-3">
372
+ {(calendarsByAccount[account.id] || []).length > 0 ? (
373
+ (calendarsByAccount[account.id] || []).map((cal) => (
374
+ <div
375
+ key={cal.id}
376
+ className="flex items-center justify-between gap-2 rounded-md px-2 py-1.5 hover:bg-muted/50"
377
+ >
378
+ <div className="flex min-w-0 flex-1 items-center gap-2">
379
+ <div
380
+ className="h-3 w-3 shrink-0 rounded-full"
381
+ style={{
382
+ backgroundColor: cal.color || '#4285f4',
383
+ }}
384
+ />
385
+ <span
386
+ className="line-clamp-1 break-all text-sm"
387
+ title={cal.calendar_name}
388
+ >
389
+ {cal.calendar_name}
390
+ </span>
391
+ </div>
392
+ <Switch
393
+ checked={cal.is_enabled}
394
+ onCheckedChange={() =>
395
+ handleToggle(cal.id, cal.is_enabled, {
396
+ calendar_id: cal.calendar_id,
397
+ calendar_name: cal.calendar_name,
398
+ color: cal.color,
399
+ connectionExists: cal.connectionExists,
400
+ accountId: cal.accountId,
401
+ })
402
+ }
403
+ disabled={togglingIds.has(cal.id)}
404
+ />
405
+ </div>
406
+ ))
407
+ ) : (
408
+ <p className="py-2 text-center text-muted-foreground text-xs">
409
+ {t('no_calendars_found')}
410
+ </p>
411
+ )}
412
+
413
+ <Separator className="my-2" />
414
+ <Button
415
+ variant="ghost"
416
+ size="sm"
417
+ className="w-full text-destructive hover:bg-destructive/10 hover:text-destructive"
418
+ onClick={() => disconnectMutation.mutate(account.id)}
419
+ disabled={disconnectingId === account.id}
420
+ >
421
+ {disconnectingId === account.id ? (
422
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
423
+ ) : (
424
+ <Trash2 className="mr-2 h-4 w-4" />
425
+ )}
426
+ {t('disconnect')}
427
+ </Button>
428
+ </div>
429
+ </CollapsibleContent>
430
+ </div>
431
+ </Collapsible>
432
+ ))}
433
+
434
+ {/* Add new account buttons */}
435
+ <div className="space-y-2 pt-2">
436
+ <p className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
437
+ {t('add_account')}
438
+ </p>
439
+ <div className="grid grid-cols-2 gap-2">
440
+ <Button
441
+ variant="outline"
442
+ size="sm"
443
+ className="gap-2 text-center"
444
+ onClick={() => googleAuthMutation.mutate()}
445
+ disabled={googleAuthMutation.isPending}
446
+ >
447
+ {googleAuthMutation.isPending ? (
448
+ <Loader2 className="h-4 w-4 animate-spin" />
449
+ ) : (
450
+ <Image
451
+ src="/media/logos/google.svg"
452
+ alt="Google"
453
+ width={16}
454
+ height={16}
455
+ />
456
+ )}
457
+ {t('google')}
458
+ </Button>
459
+ <Button
460
+ variant="outline"
461
+ size="sm"
462
+ className="gap-2 text-center"
463
+ onClick={() => microsoftAuthMutation.mutate()}
464
+ disabled={microsoftAuthMutation.isPending}
465
+ >
466
+ {microsoftAuthMutation.isPending ? (
467
+ <Loader2 className="h-4 w-4 animate-spin" />
468
+ ) : (
469
+ <Image
470
+ src="/media/logos/microsoft.svg"
471
+ alt="Microsoft"
472
+ width={16}
473
+ height={16}
474
+ />
475
+ )}
476
+ {t('outlook')}
477
+ </Button>
478
+ </div>
479
+ </div>
480
+
481
+ {/* Danger Zone */}
482
+ <Separator />
483
+ <div className="space-y-2">
484
+ <p className="font-medium text-destructive text-xs uppercase tracking-wider">
485
+ {t('danger_zone') || 'Danger Zone'}
486
+ </p>
487
+ <AlertDialog>
488
+ <AlertDialogTrigger asChild>
489
+ <Button
490
+ variant="destructive"
491
+ size="sm"
492
+ className="w-full gap-2"
493
+ disabled={resetCalendarDataMutation.isPending}
494
+ >
495
+ {resetCalendarDataMutation.isPending ? (
496
+ <Loader2 className="h-4 w-4 animate-spin" />
497
+ ) : (
498
+ <RotateCcw className="h-4 w-4" />
499
+ )}
500
+ {t('reset_calendar_data') || 'Reset Calendar Data'}
501
+ </Button>
502
+ </AlertDialogTrigger>
503
+ <AlertDialogContent>
504
+ <AlertDialogHeader>
505
+ <AlertDialogTitle>
506
+ {t('reset_calendar_confirm_title') ||
507
+ 'Reset all calendar data?'}
508
+ </AlertDialogTitle>
509
+ <AlertDialogDescription>
510
+ {t('reset_calendar_confirm_desc') ||
511
+ 'This will permanently delete all calendar events, disconnect all linked accounts (Google, Microsoft), and remove custom calendars. System calendars will be preserved but emptied. This action cannot be undone.'}
512
+ </AlertDialogDescription>
513
+ </AlertDialogHeader>
514
+ <AlertDialogFooter>
515
+ <AlertDialogCancel>{t('cancel') || 'Cancel'}</AlertDialogCancel>
516
+ <AlertDialogAction
517
+ onClick={() => resetCalendarDataMutation.mutate()}
518
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
519
+ >
520
+ {t('reset_all_data') || 'Reset All Data'}
521
+ </AlertDialogAction>
522
+ </AlertDialogFooter>
523
+ </AlertDialogContent>
524
+ </AlertDialog>
525
+ </div>
526
+ </div>
527
+ </div>
528
+ );
529
+ }