@tuturuuu/ui 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/biome.json +1 -1
  3. package/package.json +73 -71
  4. package/src/components/ui/accordion.tsx +1 -1
  5. package/src/components/ui/breadcrumb.tsx +1 -1
  6. package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
  7. package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
  8. package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
  9. package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
  10. package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
  11. package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
  12. package/src/components/ui/calendar.tsx +1 -1
  13. package/src/components/ui/carousel.tsx +1 -1
  14. package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
  15. package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
  16. package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
  17. package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
  18. package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
  19. package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
  20. package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
  21. package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
  22. package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
  23. package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
  24. package/src/components/ui/checkbox.tsx +1 -1
  25. package/src/components/ui/color-picker.tsx +1 -1
  26. package/src/components/ui/command.tsx +1 -1
  27. package/src/components/ui/context-menu.tsx +5 -1
  28. package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
  29. package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +19 -0
  30. package/src/components/ui/custom/combobox.test.tsx +195 -0
  31. package/src/components/ui/custom/combobox.tsx +273 -156
  32. package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
  33. package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
  34. package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
  35. package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
  36. package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
  37. package/src/components/ui/custom/settings-dialog-shell.tsx +2 -1
  38. package/src/components/ui/custom/theme-toggle.tsx +1 -1
  39. package/src/components/ui/custom/workspace-select.tsx +8 -3
  40. package/src/components/ui/dialog.test.tsx +52 -0
  41. package/src/components/ui/dialog.tsx +6 -2
  42. package/src/components/ui/dropdown-menu.tsx +5 -1
  43. package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
  44. package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
  45. package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
  46. package/src/components/ui/finance/debts/debts-page.tsx +15 -2
  47. package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
  48. package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
  49. package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
  50. package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
  51. package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
  52. package/src/components/ui/finance/invoices/utils.ts +3 -1
  53. package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
  54. package/src/components/ui/finance/transactions/form-types.ts +1 -0
  55. package/src/components/ui/finance/transactions/form.tsx +2 -0
  56. package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
  57. package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
  58. package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
  59. package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
  60. package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
  61. package/src/components/ui/finance/wallets/form.test.tsx +51 -3
  62. package/src/components/ui/finance/wallets/form.tsx +15 -4
  63. package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
  64. package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
  65. package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
  66. package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
  67. package/src/components/ui/input-otp.tsx +1 -1
  68. package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
  69. package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
  70. package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
  71. package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
  72. package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
  73. package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
  74. package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
  75. package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
  76. package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
  77. package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
  78. package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
  79. package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
  80. package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
  81. package/src/components/ui/navigation-menu.tsx +1 -1
  82. package/src/components/ui/pagination.tsx +1 -1
  83. package/src/components/ui/radio-group.tsx +1 -1
  84. package/src/components/ui/select.tsx +5 -1
  85. package/src/components/ui/sheet.tsx +1 -1
  86. package/src/components/ui/sidebar.tsx +1 -1
  87. package/src/components/ui/storefront/cart-popover.tsx +61 -0
  88. package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
  89. package/src/components/ui/storefront/cart-summary.tsx +93 -154
  90. package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
  91. package/src/components/ui/storefront/listing-card.tsx +1 -1
  92. package/src/components/ui/storefront/merch-sections.tsx +70 -0
  93. package/src/components/ui/storefront/product-detail.tsx +1 -1
  94. package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
  95. package/src/components/ui/storefront/storefront-surface.tsx +101 -166
  96. package/src/components/ui/storefront/types.ts +4 -0
  97. package/src/components/ui/storefront/utils.ts +6 -0
  98. package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
  99. package/src/components/ui/text-editor/background-color-extension.ts +62 -0
  100. package/src/components/ui/text-editor/color-controls.tsx +284 -0
  101. package/src/components/ui/text-editor/editor.tsx +69 -14
  102. package/src/components/ui/text-editor/extensions.ts +8 -2
  103. package/src/components/ui/text-editor/highlight-extension.ts +22 -0
  104. package/src/components/ui/text-editor/tool-bar.tsx +9 -16
  105. package/src/components/ui/toast.tsx +1 -1
  106. package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +270 -0
  107. package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
  108. package/src/components/ui/tu-do/boards/board-share-dialog.tsx +222 -109
  109. package/src/components/ui/tu-do/boards/boardId/board-column.tsx +112 -43
  110. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
  111. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
  112. package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
  113. package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
  114. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
  115. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
  116. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
  117. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
  118. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
  119. package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
  120. package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
  121. package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
  122. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
  123. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
  124. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
  125. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
  126. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
  127. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
  128. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
  129. package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
  130. package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
  131. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +397 -2
  132. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +103 -13
  133. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +443 -19
  134. package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +94 -32
  135. package/src/components/ui/tu-do/boards/boardId/kanban.tsx +213 -106
  136. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +26 -4
  137. package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +5 -2
  138. package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +3 -0
  139. package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
  140. package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +191 -28
  141. package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
  142. package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
  143. package/src/components/ui/tu-do/boards/boardId/task-list.tsx +7 -0
  144. package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
  145. package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
  146. package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
  147. package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
  148. package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
  149. package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
  150. package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
  151. package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +25 -0
  152. package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +341 -38
  153. package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +253 -0
  154. package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +203 -2
  155. package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +17 -0
  156. package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
  157. package/src/components/ui/tu-do/shared/board-client.tsx +2 -7
  158. package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
  159. package/src/components/ui/tu-do/shared/board-header.tsx +464 -975
  160. package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
  161. package/src/components/ui/tu-do/shared/board-switcher.tsx +209 -217
  162. package/src/components/ui/tu-do/shared/board-views.tsx +587 -75
  163. package/src/components/ui/tu-do/shared/list-view.tsx +227 -1
  164. package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
  165. package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
  166. package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
  167. package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
  168. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
  169. package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
  170. package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
  171. package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
  172. package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +2 -1
  173. package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
  174. package/src/declarations.d.ts +1 -0
  175. package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
  176. package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
  177. package/src/hooks/use-calendar-sync.tsx +247 -243
  178. package/src/hooks/use-calendar.tsx +323 -138
  179. package/src/hooks/use-task-actions.ts +24 -0
  180. package/src/hooks/use-user-workspace-config.ts +75 -0
  181. package/src/hooks/use-workspace-currency.ts +8 -3
  182. package/src/hooks/useBoardRealtimeEventHandler.ts +11 -0
@@ -43,6 +43,7 @@ interface VirtualizedTaskListProps {
43
43
  onLoadMore?: () => void;
44
44
  hasMore?: boolean;
45
45
  isLoadingMore?: boolean;
46
+ readOnly?: boolean;
46
47
  }
47
48
 
48
49
  interface TaskListContentProps {
@@ -64,6 +65,7 @@ interface TaskListContentProps {
64
65
  bulkUpdateCustomDueDate?: (date: Date | null) => Promise<void>;
65
66
  startIndex?: number;
66
67
  taskOrder?: Pick<Task, 'id'>[];
68
+ readOnly?: boolean;
67
69
  }
68
70
 
69
71
  export function getTaskDragPreviewSlotIndex({
@@ -132,6 +134,7 @@ function TaskListContent({
132
134
  bulkUpdateCustomDueDate,
133
135
  startIndex = 0,
134
136
  taskOrder = tasks,
137
+ readOnly = false,
135
138
  }: TaskListContentProps) {
136
139
  const slotIndex = getTaskDragPreviewSlotIndex({
137
140
  columnId: column.id,
@@ -170,6 +173,7 @@ function TaskListContent({
170
173
  optimisticUpdateInProgress={optimisticUpdateInProgress}
171
174
  selectedTasks={selectedTasks}
172
175
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
176
+ readOnly={readOnly}
173
177
  />
174
178
  {slotIndex === globalIndex + 1 && (
175
179
  <DragPreviewSlot
@@ -238,6 +242,7 @@ function VirtualizedTaskListInner({
238
242
  onLoadMore,
239
243
  hasMore,
240
244
  isLoadingMore,
245
+ readOnly = false,
241
246
  }: VirtualizedTaskListProps) {
242
247
  const t = useTranslations('common');
243
248
  const tTasks = useTranslations('ws-tasks');
@@ -474,6 +479,7 @@ function VirtualizedTaskListInner({
474
479
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
475
480
  startIndex={startIndex}
476
481
  taskOrder={tasks}
482
+ readOnly={readOnly}
477
483
  />
478
484
  </div>
479
485
  </div>
@@ -502,6 +508,7 @@ function VirtualizedTaskListInner({
502
508
  optimisticUpdateInProgress={optimisticUpdateInProgress}
503
509
  bulkUpdateCustomDueDate={bulkUpdateCustomDueDate}
504
510
  taskOrder={tasks}
511
+ readOnly={readOnly}
505
512
  />
506
513
  {loadMoreSentinel}
507
514
  </SortableContext>
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ import { ChevronDown, Info } from '@tuturuuu/icons';
4
+ import {
5
+ Collapsible,
6
+ CollapsibleContent,
7
+ CollapsibleTrigger,
8
+ } from '@tuturuuu/ui/collapsible';
9
+ import {
10
+ Tooltip,
11
+ TooltipContent,
12
+ TooltipProvider,
13
+ TooltipTrigger,
14
+ } from '@tuturuuu/ui/tooltip';
15
+ import { cn } from '@tuturuuu/utils/format';
16
+ import { useTranslations } from 'next-intl';
17
+ import type { ReactNode } from 'react';
18
+
19
+ function ShareInfoTooltip({
20
+ content,
21
+ label,
22
+ }: {
23
+ content: string;
24
+ label: string;
25
+ }) {
26
+ return (
27
+ <TooltipProvider delayDuration={0} skipDelayDuration={0}>
28
+ <Tooltip>
29
+ <TooltipTrigger asChild>
30
+ <button
31
+ type="button"
32
+ className="text-muted-foreground transition-colors hover:text-foreground"
33
+ aria-label={label}
34
+ >
35
+ <Info className="h-3.5 w-3.5" />
36
+ </button>
37
+ </TooltipTrigger>
38
+ <TooltipContent className="max-w-xs">{content}</TooltipContent>
39
+ </Tooltip>
40
+ </TooltipProvider>
41
+ );
42
+ }
43
+
44
+ interface ShareSectionProps {
45
+ children: ReactNode;
46
+ icon: ReactNode;
47
+ onOpenChange: (open: boolean) => void;
48
+ open: boolean;
49
+ statusBadge: ReactNode;
50
+ title: string;
51
+ tooltip: string;
52
+ }
53
+
54
+ export function ShareSection({
55
+ children,
56
+ icon,
57
+ onOpenChange,
58
+ open,
59
+ statusBadge,
60
+ title,
61
+ tooltip,
62
+ }: ShareSectionProps) {
63
+ const t = useTranslations();
64
+
65
+ return (
66
+ <Collapsible
67
+ open={open}
68
+ onOpenChange={onOpenChange}
69
+ className="rounded-md border"
70
+ >
71
+ <div className="flex min-h-11 items-center gap-2 px-3">
72
+ <CollapsibleTrigger asChild>
73
+ <button
74
+ type="button"
75
+ className="flex min-w-0 flex-1 items-center gap-2 text-left transition-colors hover:text-foreground"
76
+ >
77
+ {icon}
78
+ <span className="min-w-0 flex-1 truncate font-medium text-sm">
79
+ {title}
80
+ </span>
81
+ {statusBadge}
82
+ <ChevronDown
83
+ className={cn(
84
+ 'h-4 w-4 shrink-0 text-muted-foreground transition-transform',
85
+ open && 'rotate-180'
86
+ )}
87
+ />
88
+ </button>
89
+ </CollapsibleTrigger>
90
+ <ShareInfoTooltip
91
+ label={t('ws-task-boards.share.note')}
92
+ content={tooltip}
93
+ />
94
+ </div>
95
+ <CollapsibleContent className="border-t p-3">
96
+ {children}
97
+ </CollapsibleContent>
98
+ </Collapsible>
99
+ );
100
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useQuery } from '@tanstack/react-query';
4
4
  import {
5
+ convertWorkspaceTaskDraft,
5
6
  listWorkspaceTaskBoards,
6
7
  listWorkspaceTaskLists,
7
8
  } from '@tuturuuu/internal-api';
@@ -46,6 +47,10 @@ export function DraftConvertDialog({
46
47
  const [selectedBoardId, setSelectedBoardId] = useState<string>('');
47
48
  const [selectedListId, setSelectedListId] = useState<string>('');
48
49
  const [isConverting, setIsConverting] = useState(false);
50
+ const internalApiOptions =
51
+ typeof window !== 'undefined'
52
+ ? { baseUrl: window.location.origin }
53
+ : undefined;
49
54
 
50
55
  // Sync selections when draft changes (useState initializer only runs on mount)
51
56
  useEffect(() => {
@@ -107,20 +112,13 @@ export function DraftConvertDialog({
107
112
 
108
113
  setIsConverting(true);
109
114
  try {
110
- const res = await fetch(
111
- `/api/v1/workspaces/${wsId}/task-drafts/${draft.id}/convert`,
112
- {
113
- method: 'POST',
114
- headers: { 'Content-Type': 'application/json' },
115
- body: JSON.stringify({ listId: selectedListId }),
116
- }
115
+ await convertWorkspaceTaskDraft(
116
+ wsId,
117
+ draft.id,
118
+ { listId: selectedListId },
119
+ internalApiOptions
117
120
  );
118
121
 
119
- if (!res.ok) {
120
- const data = await res.json();
121
- throw new Error(data.error || 'Failed to convert draft');
122
- }
123
-
124
122
  toast.success(t('converted_success'));
125
123
  onConverted();
126
124
  onClose();
@@ -2,6 +2,10 @@
2
2
 
3
3
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import { FileText } from '@tuturuuu/icons';
5
+ import {
6
+ deleteWorkspaceTaskDraft,
7
+ listWorkspaceTaskDrafts,
8
+ } from '@tuturuuu/internal-api/tasks';
5
9
  import { toast } from '@tuturuuu/ui/sonner';
6
10
  import { useTranslations } from 'next-intl';
7
11
  import { useState } from 'react';
@@ -11,38 +15,51 @@ import { DraftConvertDialog } from './draft-convert-dialog';
11
15
 
12
16
  interface DraftsPageProps {
13
17
  wsId: string;
18
+ boardId?: string;
19
+ includeUnassignedForBoard?: boolean;
14
20
  }
15
21
 
16
- export function DraftsPage({ wsId }: DraftsPageProps) {
22
+ function getBrowserInternalApiOptions() {
23
+ return typeof window !== 'undefined'
24
+ ? { baseUrl: window.location.origin }
25
+ : undefined;
26
+ }
27
+
28
+ export function DraftsPage({
29
+ wsId,
30
+ boardId,
31
+ includeUnassignedForBoard = false,
32
+ }: DraftsPageProps) {
17
33
  const t = useTranslations('task-drafts');
18
34
  const queryClient = useQueryClient();
19
35
  const { editDraft } = useTaskDialogContext();
20
36
  const [convertDraft, setConvertDraft] = useState<TaskDraft | null>(null);
37
+ const draftQueryKey = [
38
+ 'task-drafts',
39
+ wsId,
40
+ boardId ?? 'all',
41
+ includeUnassignedForBoard,
42
+ ] as const;
21
43
 
22
44
  const { data: drafts = [], isLoading } = useQuery<TaskDraft[]>({
23
- queryKey: ['task-drafts', wsId],
24
- queryFn: async () => {
25
- const res = await fetch(`/api/v1/workspaces/${wsId}/task-drafts`);
26
- if (!res.ok) throw new Error('Failed to fetch drafts');
27
- const json = await res.json();
28
- return json.data;
29
- },
45
+ queryKey: draftQueryKey,
46
+ queryFn: () =>
47
+ listWorkspaceTaskDrafts(
48
+ wsId,
49
+ { boardId, includeUnassignedForBoard },
50
+ getBrowserInternalApiOptions()
51
+ ) as Promise<TaskDraft[]>,
30
52
  });
31
53
 
32
54
  const deleteMutation = useMutation({
33
- mutationFn: async (draftId: string) => {
34
- const res = await fetch(
35
- `/api/v1/workspaces/${wsId}/task-drafts/${draftId}`,
36
- { method: 'DELETE' }
37
- );
38
- if (!res.ok) throw new Error('Failed to delete draft');
39
- },
55
+ mutationFn: async (draftId: string) =>
56
+ deleteWorkspaceTaskDraft(wsId, draftId, getBrowserInternalApiOptions()),
40
57
  onSuccess: () => {
41
58
  queryClient.invalidateQueries({ queryKey: ['task-drafts', wsId] });
42
59
  toast.success(t('deleted_success'));
43
60
  },
44
61
  onError: () => {
45
- toast.error('Failed to delete draft');
62
+ toast.error(t('delete_failed'));
46
63
  },
47
64
  });
48
65
 
@@ -13,6 +13,15 @@ import {
13
13
  Trash2,
14
14
  User,
15
15
  } from '@tuturuuu/icons';
16
+ import {
17
+ createWorkspaceTaskInitiative,
18
+ deleteWorkspaceTaskInitiative,
19
+ linkWorkspaceTaskInitiativeProject,
20
+ listWorkspaceTaskInitiatives,
21
+ listWorkspaceTaskProjects,
22
+ unlinkWorkspaceTaskInitiativeProject,
23
+ updateWorkspaceTaskInitiative,
24
+ } from '@tuturuuu/internal-api/tasks';
16
25
  import { Badge } from '@tuturuuu/ui/badge';
17
26
  import { Button } from '@tuturuuu/ui/button';
18
27
  import {
@@ -98,6 +107,12 @@ const STATUS_BADGE_CLASS: Record<InitiativeStatus, string> = {
98
107
  cancelled: 'bg-dynamic-red/15 text-dynamic-red border-transparent',
99
108
  };
100
109
 
110
+ function getBrowserInternalApiOptions() {
111
+ return typeof window !== 'undefined'
112
+ ? { baseUrl: window.location.origin }
113
+ : undefined;
114
+ }
115
+
101
116
  export function TaskInitiativesClient({
102
117
  wsId,
103
118
  initialInitiatives,
@@ -136,16 +151,8 @@ export function TaskInitiativesClient({
136
151
  refetch: refetchInitiatives,
137
152
  } = useQuery<TaskInitiative[]>({
138
153
  queryKey: ['workspace', wsId, 'task-initiatives'],
139
- queryFn: async () => {
140
- const response = await fetch(
141
- `/api/v1/workspaces/${wsId}/task-initiatives`,
142
- { cache: 'no-store' }
143
- );
144
- if (!response.ok) {
145
- throw new Error(t('errors.fetch_initiatives'));
146
- }
147
- return response.json();
148
- },
154
+ queryFn: () =>
155
+ listWorkspaceTaskInitiatives(wsId, getBrowserInternalApiOptions()),
149
156
  initialData: initialInitiatives,
150
157
  staleTime: 30_000,
151
158
  });
@@ -157,20 +164,12 @@ export function TaskInitiativesClient({
157
164
  } = useQuery<TaskProjectOption[]>({
158
165
  queryKey: ['workspace', wsId, 'task-projects-for-initiatives'],
159
166
  queryFn: async () => {
160
- const response = await fetch(`/api/v1/workspaces/${wsId}/task-projects`, {
161
- cache: 'no-store',
162
- });
163
- if (!response.ok) {
164
- throw new Error(t('errors.fetch_projects'));
165
- }
166
- const rawProjects = await response.json();
167
- return (
168
- rawProjects as Array<{
169
- id: string;
170
- name: string;
171
- status?: string | null;
172
- }>
173
- ).map((project) => ({
167
+ const rawProjects = await listWorkspaceTaskProjects(
168
+ wsId,
169
+ getBrowserInternalApiOptions()
170
+ );
171
+
172
+ return rawProjects.map((project) => ({
174
173
  id: project.id,
175
174
  name: project.name,
176
175
  status: project.status ?? null,
@@ -218,19 +217,13 @@ export function TaskInitiativesClient({
218
217
  description?: string;
219
218
  status: InitiativeStatus;
220
219
  }) => {
221
- const response = await fetch(
222
- `/api/v1/workspaces/${wsId}/task-initiatives`,
223
- {
224
- method: 'POST',
225
- headers: { 'Content-Type': 'application/json' },
226
- body: JSON.stringify({ name, description, status }),
227
- }
220
+ const payload = { name, description: description ?? '', status };
221
+
222
+ return createWorkspaceTaskInitiative(
223
+ wsId,
224
+ payload,
225
+ getBrowserInternalApiOptions()
228
226
  );
229
- if (!response.ok) {
230
- const errorData = await response.json().catch(() => ({}));
231
- throw new Error(errorData.error || t('errors.create_initiative'));
232
- }
233
- return response.json();
234
227
  },
235
228
  onSuccess: () => {
236
229
  toast.success(t('success.initiative_created'));
@@ -257,19 +250,14 @@ export function TaskInitiativesClient({
257
250
  description?: string;
258
251
  status: InitiativeStatus;
259
252
  }) => {
260
- const response = await fetch(
261
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}`,
262
- {
263
- method: 'PUT',
264
- headers: { 'Content-Type': 'application/json' },
265
- body: JSON.stringify({ name, description, status }),
266
- }
253
+ const payload = { name, description: description ?? '', status };
254
+
255
+ return updateWorkspaceTaskInitiative(
256
+ wsId,
257
+ initiativeId,
258
+ payload,
259
+ getBrowserInternalApiOptions()
267
260
  );
268
- if (!response.ok) {
269
- const errorData = await response.json().catch(() => ({}));
270
- throw new Error(errorData.error || t('errors.update_initiative'));
271
- }
272
- return response.json();
273
261
  },
274
262
  onSuccess: () => {
275
263
  toast.success(t('success.initiative_updated'));
@@ -286,18 +274,12 @@ export function TaskInitiativesClient({
286
274
  });
287
275
 
288
276
  const deleteInitiativeMutation = useMutation({
289
- mutationFn: async (initiativeId: string) => {
290
- const response = await fetch(
291
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}`,
292
- {
293
- method: 'DELETE',
294
- }
295
- );
296
- if (!response.ok) {
297
- const errorData = await response.json().catch(() => ({}));
298
- throw new Error(errorData.error || t('errors.delete_initiative'));
299
- }
300
- },
277
+ mutationFn: (initiativeId: string) =>
278
+ deleteWorkspaceTaskInitiative(
279
+ wsId,
280
+ initiativeId,
281
+ getBrowserInternalApiOptions()
282
+ ),
301
283
  onSuccess: () => {
302
284
  toast.success(t('success.initiative_deleted'));
303
285
  refetchInitiatives();
@@ -314,21 +296,13 @@ export function TaskInitiativesClient({
314
296
  }: {
315
297
  initiativeId: string;
316
298
  projectId: string;
317
- }) => {
318
- const response = await fetch(
319
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}/projects`,
320
- {
321
- method: 'POST',
322
- headers: { 'Content-Type': 'application/json' },
323
- body: JSON.stringify({ projectId }),
324
- }
325
- );
326
- if (!response.ok) {
327
- const errorData = await response.json().catch(() => ({}));
328
- throw new Error(errorData.error || t('errors.link_project'));
329
- }
330
- return response.json();
331
- },
299
+ }) =>
300
+ linkWorkspaceTaskInitiativeProject(
301
+ wsId,
302
+ initiativeId,
303
+ projectId,
304
+ getBrowserInternalApiOptions()
305
+ ),
332
306
  onSuccess: (_data, variables) => {
333
307
  toast.success(t('success.project_linked'));
334
308
  setProjectToLink('');
@@ -362,19 +336,13 @@ export function TaskInitiativesClient({
362
336
  }: {
363
337
  initiativeId: string;
364
338
  projectId: string;
365
- }) => {
366
- const response = await fetch(
367
- `/api/v1/workspaces/${wsId}/task-initiatives/${initiativeId}/projects/${projectId}`,
368
- {
369
- method: 'DELETE',
370
- }
371
- );
372
- if (!response.ok) {
373
- const errorData = await response.json().catch(() => ({}));
374
- throw new Error(errorData.error || t('errors.unlink_project'));
375
- }
376
- return response.json();
377
- },
339
+ }) =>
340
+ unlinkWorkspaceTaskInitiativeProject(
341
+ wsId,
342
+ initiativeId,
343
+ projectId,
344
+ getBrowserInternalApiOptions()
345
+ ),
378
346
  onSuccess: (_data, variables) => {
379
347
  toast.success(t('success.project_unlinked'));
380
348
  if (variables) {
@@ -20,12 +20,30 @@ import { TaskPreviewDialog } from './task-preview-dialog';
20
20
  import { useMyTasksState } from './use-my-tasks-state';
21
21
 
22
22
  interface MyTasksContentProps {
23
+ disableAutoCreateBoard?: boolean;
24
+ embedded?: boolean;
25
+ initialBoard?: {
26
+ id: string;
27
+ name: string | null;
28
+ };
29
+ initialListId?: string;
30
+ initialLists?: Array<{
31
+ deleted?: boolean | null;
32
+ id: string;
33
+ name: string | null;
34
+ position?: number | null;
35
+ }>;
23
36
  wsId: string;
24
37
  userId: string;
25
38
  isPersonal: boolean;
26
39
  }
27
40
 
28
41
  export default function MyTasksContent({
42
+ disableAutoCreateBoard = false,
43
+ embedded = false,
44
+ initialBoard,
45
+ initialListId,
46
+ initialLists,
29
47
  wsId,
30
48
  userId,
31
49
  isPersonal,
@@ -40,13 +58,17 @@ export default function MyTasksContent({
40
58
  'enter'
41
59
  );
42
60
  const state = useMyTasksState({
61
+ ...(disableAutoCreateBoard ? { disableAutoCreateBoard } : {}),
62
+ ...(initialBoard ? { initialBoard } : {}),
63
+ ...(initialListId ? { initialListId } : {}),
64
+ ...(initialLists ? { initialLists } : {}),
43
65
  wsId,
44
66
  userId,
45
67
  isPersonal,
46
68
  });
47
69
 
48
70
  return (
49
- <div className="space-y-4 md:space-y-6">
71
+ <div className={embedded ? 'space-y-4' : 'space-y-4 md:space-y-6'}>
50
72
  {/* Header with greeting + summary cards */}
51
73
  <MyTasksHeader
52
74
  overdueCount={state.filteredTasks.overdueTasks?.length ?? 0}
@@ -55,7 +77,9 @@ export default function MyTasksContent({
55
77
  />
56
78
 
57
79
  {/* Command Bar */}
58
- <div className="mx-auto mb-32 max-w-5xl">
80
+ <div
81
+ className={embedded ? 'mx-auto max-w-5xl' : 'mx-auto mb-32 max-w-5xl'}
82
+ >
59
83
  <CommandBar
60
84
  value={state.commandBarInput}
61
85
  onValueChange={state.setCommandBarInput}
@@ -39,12 +39,28 @@ dayjs.extend(utc);
39
39
  dayjs.extend(timezone);
40
40
 
41
41
  interface UseMyTasksStateProps {
42
+ disableAutoCreateBoard?: boolean;
43
+ initialBoard?: {
44
+ id: string;
45
+ name: string | null;
46
+ };
47
+ initialListId?: string;
48
+ initialLists?: Array<{
49
+ deleted?: boolean | null;
50
+ id: string;
51
+ name: string | null;
52
+ position?: number | null;
53
+ }>;
42
54
  wsId: string;
43
55
  userId: string;
44
56
  isPersonal: boolean;
45
57
  }
46
58
 
47
59
  export function useMyTasksState({
60
+ disableAutoCreateBoard = false,
61
+ initialBoard,
62
+ initialListId,
63
+ initialLists,
48
64
  wsId,
49
65
  userId,
50
66
  isPersonal,
@@ -96,8 +112,16 @@ export function useMyTasksState({
96
112
  // Board selector state
97
113
  const [boardSelectorOpen, setBoardSelectorOpen] = useState(false);
98
114
  const [selectedWorkspaceId, setSelectedWorkspaceId] = useState<string>(wsId);
99
- const [selectedBoardId, setSelectedBoardId] = useState<string>('');
100
- const [selectedListId, setSelectedListId] = useState<string>('');
115
+ const [selectedBoardId, setSelectedBoardId] = useState<string>(
116
+ initialBoard?.id ?? ''
117
+ );
118
+ const [selectedListId, setSelectedListId] = useState<string>(
119
+ initialListId ??
120
+ initialLists
121
+ ?.filter((list) => !list.deleted)
122
+ .sort((a, b) => (a.position ?? 0) - (b.position ?? 0))[0]?.id ??
123
+ ''
124
+ );
101
125
  const [newBoardDialogOpen, setNewBoardDialogOpen] = useState(false);
102
126
  const [newBoardName, setNewBoardName] = useState<string>('');
103
127
  const [newListDialogOpen, setNewListDialogOpen] = useState(false);
@@ -276,6 +300,7 @@ export function useMyTasksState({
276
300
  });
277
301
  return payload.count ?? 0;
278
302
  },
303
+ enabled: !disableAutoCreateBoard,
279
304
  });
280
305
 
281
306
  // Auto-create a board if the workspace has none
@@ -294,7 +319,13 @@ export function useMyTasksState({
294
319
  );
295
320
 
296
321
  useEffect(() => {
297
- if (wsBoardCountLoading || wsBoardCount === undefined) return;
322
+ if (
323
+ disableAutoCreateBoard ||
324
+ wsBoardCountLoading ||
325
+ wsBoardCount === undefined
326
+ ) {
327
+ return;
328
+ }
298
329
  if (wsBoardCount > 0) return;
299
330
  if (autoCreateAttemptedRef.current) return;
300
331
  autoCreateAttemptedRef.current = true;
@@ -344,6 +375,7 @@ export function useMyTasksState({
344
375
  queryClient,
345
376
  defaultBoardName,
346
377
  defaultListNames,
378
+ disableAutoCreateBoard,
347
379
  ]);
348
380
 
349
381
  // Fetch boards with lists for selected workspace
@@ -422,11 +454,16 @@ export function useMyTasksState({
422
454
  const availableLists = useMemo(() => {
423
455
  if (!selectedBoardId) return [];
424
456
  const board = boardsData.find((b: any) => b.id === selectedBoardId);
425
- if (!board?.task_lists) return [];
457
+ if (!board?.task_lists) {
458
+ if (selectedBoardId !== initialBoard?.id) return [];
459
+ return (initialLists ?? [])
460
+ .filter((list) => !list.deleted)
461
+ .sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
462
+ }
426
463
  return (board.task_lists as any[])
427
464
  .filter((l: any) => !l.deleted)
428
465
  .sort((a: any, b: any) => (a.position || 0) - (b.position || 0));
429
- }, [selectedBoardId, boardsData]);
466
+ }, [selectedBoardId, boardsData, initialBoard?.id, initialLists]);
430
467
 
431
468
  const hasValidSelectedList = useMemo(
432
469
  () => availableLists.some((list: any) => list.id === selectedListId),
@@ -794,13 +831,23 @@ export function useMyTasksState({
794
831
  const selectedDestination = useMemo(() => {
795
832
  if (!selectedBoardId || !selectedListId) return null;
796
833
  const board = boardsData.find((b: any) => b.id === selectedBoardId);
797
- const lists = (board?.task_lists as any[]) || [];
834
+ const isInitialBoard = selectedBoardId === initialBoard?.id;
835
+ const lists =
836
+ (board?.task_lists as any[]) ||
837
+ (isInitialBoard ? (initialLists ?? []) : []);
798
838
  const list = lists.find((l: any) => l.id === selectedListId);
799
839
  return {
800
- boardName: board?.name || 'Unknown Board',
840
+ boardName: board?.name || initialBoard?.name || 'Unknown Board',
801
841
  listName: list?.name || 'Unknown List',
802
842
  };
803
- }, [selectedBoardId, selectedListId, boardsData]);
843
+ }, [
844
+ selectedBoardId,
845
+ selectedListId,
846
+ boardsData,
847
+ initialBoard?.id,
848
+ initialBoard?.name,
849
+ initialLists,
850
+ ]);
804
851
 
805
852
  const handleClearDestination = () => {
806
853
  setSelectedBoardId('');
@@ -41,10 +41,7 @@ export function NoteEditDialog({
41
41
 
42
42
  return (
43
43
  <Dialog open={isOpen} onOpenChange={onOpenChange} modal={true}>
44
- <DialogContent
45
- showCloseButton={false}
46
- className="inset-0! top-0! left-0! flex h-screen max-h-screen w-screen max-w-none! translate-x-0! translate-y-0! gap-0 rounded-none! border-0 p-0"
47
- >
44
+ <DialogContent showCloseButton={false} presentation="fullscreen">
48
45
  {/* Main content area - Note title and description */}
49
46
  <div className="flex min-w-0 flex-1 flex-col bg-background transition-all duration-300">
50
47
  {/* Enhanced Header with gradient */}