@tuturuuu/ui 0.8.0 → 0.10.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.
- package/CHANGELOG.md +69 -0
- package/biome.json +1 -1
- package/package.json +74 -71
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +1 -1
- package/src/components/ui/calendar-app/calendar-page-shell.tsx +4 -0
- package/src/components/ui/calendar-app/components/calendar-connections-settings-content.tsx +239 -33
- package/src/components/ui/calendar-app/components/load-smart-scheduling-tasks.tsx +143 -0
- package/src/components/ui/calendar-app/components/priority-view.tsx +10 -3
- package/src/components/ui/calendar-app/components/tasks-sidebar.tsx +4 -116
- package/src/components/ui/calendar-app/components/use-calendar-connections-manager.ts +67 -2
- package/src/components/ui/calendar.tsx +1 -1
- package/src/components/ui/carousel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-external-thread-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-operations-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-setup-panel.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.test.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-sidebar.tsx +2 -2
- package/src/components/ui/chat/chat-agent-details-utils.test.ts +1 -1
- package/src/components/ui/chat/chat-agent-details-utils.tsx +1 -1
- package/src/components/ui/chat/chat-agent-details-zalo-personal-panel.tsx +2 -2
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/color-picker.tsx +1 -1
- package/src/components/ui/command.tsx +1 -1
- package/src/components/ui/context-menu.tsx +5 -1
- package/src/components/ui/custom/__tests__/settings-dialog-search.test.ts +78 -0
- package/src/components/ui/custom/__tests__/settings-dialog-shell-compile-graph.test.ts +76 -0
- package/src/components/ui/custom/__tests__/settings-dialog-shell.test.tsx +3 -0
- package/src/components/ui/custom/__tests__/workspace-select-helpers.test.ts +46 -1
- package/src/components/ui/custom/combobox.test.tsx +195 -0
- package/src/components/ui/custom/combobox.tsx +273 -156
- package/src/components/ui/custom/education/modules/youtube/delete-link-button.tsx +5 -13
- package/src/components/ui/custom/facebook-mockup/facebook-mockup.tsx +7 -1
- package/src/components/ui/custom/facebook-mockup/form.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/image-upload-field.tsx +1 -1
- package/src/components/ui/custom/facebook-mockup/preview.tsx +1 -1
- package/src/components/ui/custom/nav-link.test.tsx +165 -0
- package/src/components/ui/custom/nav-link.tsx +69 -11
- package/src/components/ui/custom/navigation.tsx +1 -0
- package/src/components/ui/custom/settings/task-settings.tsx +104 -0
- package/src/components/ui/custom/settings-dialog-search-loader.d.ts +5 -0
- package/src/components/ui/custom/settings-dialog-search-loader.js +3 -0
- package/src/components/ui/custom/settings-dialog-search.ts +75 -0
- package/src/components/ui/custom/settings-dialog-shell.tsx +65 -28
- package/src/components/ui/custom/theme-toggle.tsx +1 -1
- package/src/components/ui/custom/workspace-select-helpers.ts +23 -0
- package/src/components/ui/custom/workspace-select.tsx +25 -19
- package/src/components/ui/dialog.test.tsx +52 -0
- package/src/components/ui/dialog.tsx +6 -2
- package/src/components/ui/dropdown-menu.tsx +5 -1
- package/src/components/ui/finance/debts/debt-loan-form.tsx +12 -5
- package/src/components/ui/finance/debts/debt-loan-summary.tsx +3 -2
- package/src/components/ui/finance/debts/debts-page.test.tsx +54 -5
- package/src/components/ui/finance/debts/debts-page.tsx +15 -2
- package/src/components/ui/finance/invoices/components/subscription-group-selector.tsx +3 -5
- package/src/components/ui/finance/invoices/new-invoice-page.test.tsx +25 -5
- package/src/components/ui/finance/invoices/new-invoice-page.tsx +7 -2
- package/src/components/ui/finance/invoices/standard-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/subscription-invoice.tsx +4 -2
- package/src/components/ui/finance/invoices/utils.ts +3 -1
- package/src/components/ui/finance/transactions/form-content-dialog.tsx +3 -0
- package/src/components/ui/finance/transactions/form-types.ts +1 -0
- package/src/components/ui/finance/transactions/form.tsx +2 -0
- package/src/components/ui/finance/transactions/infinite-transactions-list.tsx +2 -0
- package/src/components/ui/finance/transactions/period-charts/category-breakdown-dialog.tsx +1 -1
- package/src/components/ui/finance/transactions/transaction-edit-dialog.tsx +1 -4
- package/src/components/ui/finance/transactions/transactions-create-summary.tsx +3 -0
- package/src/components/ui/finance/transactions/transactions-page.tsx +4 -1
- package/src/components/ui/finance/wallets/form.test.tsx +51 -3
- package/src/components/ui/finance/wallets/form.tsx +15 -4
- package/src/components/ui/finance/wallets/walletId/wallet-details-actions.tsx +4 -0
- package/src/components/ui/finance/wallets/walletId/wallet-details-page.tsx +4 -2
- package/src/components/ui/finance/wallets/wallets-data-table.tsx +1 -0
- package/src/components/ui/finance/wallets/wallets-page.tsx +5 -2
- package/src/components/ui/input-otp.tsx +1 -1
- package/src/components/ui/legacy/calendar/all-day-event-bar.tsx +28 -39
- package/src/components/ui/legacy/calendar/calendar-cell.tsx +2 -0
- package/src/components/ui/legacy/calendar/calendar-content.tsx +10 -6
- package/src/components/ui/legacy/calendar/calendar-header.tsx +23 -3
- package/src/components/ui/legacy/calendar/calendar-loading-skeleton.tsx +135 -0
- package/src/components/ui/legacy/calendar/calendar-matrix.tsx +175 -237
- package/src/components/ui/legacy/calendar/event-card.test.tsx +177 -0
- package/src/components/ui/legacy/calendar/event-card.tsx +220 -131
- package/src/components/ui/legacy/calendar/event-modal.tsx +17 -17
- package/src/components/ui/legacy/calendar/event-provider-display.tsx +69 -0
- package/src/components/ui/legacy/calendar/smart-calendar.test.tsx +86 -4
- package/src/components/ui/legacy/calendar/smart-calendar.tsx +32 -2
- package/src/components/ui/legacy/meet/create-plan-dialog.tsx +19 -10
- package/src/components/ui/navigation-menu.tsx +1 -1
- package/src/components/ui/pagination.tsx +1 -1
- package/src/components/ui/radio-group.tsx +1 -1
- package/src/components/ui/select.tsx +5 -1
- package/src/components/ui/sheet.tsx +1 -1
- package/src/components/ui/sidebar.tsx +1 -1
- package/src/components/ui/storefront/cart-popover.tsx +61 -0
- package/src/components/ui/storefront/cart-summary-parts.tsx +290 -0
- package/src/components/ui/storefront/cart-summary.tsx +93 -154
- package/src/components/ui/storefront/checkout-overlay.tsx +4 -5
- package/src/components/ui/storefront/listing-card.tsx +1 -1
- package/src/components/ui/storefront/merch-sections.tsx +70 -0
- package/src/components/ui/storefront/product-detail.tsx +1 -1
- package/src/components/ui/storefront/storefront-surface.test.tsx +106 -11
- package/src/components/ui/storefront/storefront-surface.tsx +101 -166
- package/src/components/ui/storefront/types.ts +4 -0
- package/src/components/ui/storefront/utils.ts +6 -0
- package/src/components/ui/text-editor/__tests__/extensions.test.ts +123 -0
- package/src/components/ui/text-editor/background-color-extension.ts +62 -0
- package/src/components/ui/text-editor/color-controls.tsx +284 -0
- package/src/components/ui/text-editor/editor.tsx +69 -14
- package/src/components/ui/text-editor/extensions.ts +8 -2
- package/src/components/ui/text-editor/highlight-extension.ts +22 -0
- package/src/components/ui/text-editor/tool-bar.tsx +9 -16
- package/src/components/ui/toast.tsx +1 -1
- package/src/components/ui/tu-do/boards/__tests__/board-share-dialog.test.tsx +286 -0
- package/src/components/ui/tu-do/boards/__tests__/task-board-form.test.tsx +12 -0
- package/src/components/ui/tu-do/boards/board-public-link-section.tsx +231 -0
- package/src/components/ui/tu-do/boards/board-share-dialog.tsx +15 -226
- package/src/components/ui/tu-do/boards/board-share-settings-panel.tsx +351 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +121 -39
- package/src/components/ui/tu-do/boards/boardId/enhanced-task-list.tsx +7 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-clear-delete.ts +2 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-move.ts +5 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-mutations-updates.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/bulk/bulk-operation-types.ts +3 -3
- package/src/components/ui/tu-do/boards/boardId/kanban/data/kanban-deadline-query.ts +50 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/data/use-bulk-resources.ts +59 -5
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/__tests__/column-reorder.test.ts +17 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/column-reorder.ts +4 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/drag-preview.tsx +20 -1
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +38 -9
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-order.ts +2 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-sort-key.ts +47 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +81 -30
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/__tests__/kanban-planner-island.test.tsx +380 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/kanban-planner-dialog.tsx +204 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-digest-panel.tsx +61 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-item-strip.tsx +54 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-plan-toolbar.tsx +251 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-scope-badge.tsx +27 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-section.tsx +58 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-share-dialog.tsx +238 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-target-controls.tsx +143 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/planner-utils.ts +65 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/planner/use-kanban-planner-state.ts +234 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +642 -5
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +224 -15
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-deadline-panels.tsx +535 -53
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-skeleton.tsx +101 -33
- package/src/components/ui/tu-do/boards/boardId/kanban.tsx +235 -113
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +50 -5
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +12 -2
- package/src/components/ui/tu-do/boards/boardId/task-card/measured-task-card.tsx +10 -1
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-comparator.ts +3 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.test.ts +20 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card-open-options.ts +10 -0
- package/src/components/ui/tu-do/boards/boardId/task-card/task-card.tsx +271 -36
- package/src/components/ui/tu-do/boards/boardId/task-filter.test.tsx +152 -0
- package/src/components/ui/tu-do/boards/boardId/task-filter.tsx +555 -545
- package/src/components/ui/tu-do/boards/boardId/task-list.tsx +22 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-toolbar.tsx +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline-board.tsx +35 -3
- package/src/components/ui/tu-do/boards/form.tsx +1 -1
- package/src/components/ui/tu-do/boards/share-section.tsx +100 -0
- package/src/components/ui/tu-do/drafts/draft-convert-dialog.tsx +10 -12
- package/src/components/ui/tu-do/drafts/drafts-page.tsx +33 -16
- package/src/components/ui/tu-do/hooks/__tests__/useTaskLabelManagement.test.tsx +48 -0
- package/src/components/ui/tu-do/hooks/__tests__/useTaskProjectManagement.test.tsx +144 -0
- package/src/components/ui/tu-do/hooks/useTaskDialog.ts +7 -0
- package/src/components/ui/tu-do/hooks/useTaskLabelManagement.ts +115 -106
- package/src/components/ui/tu-do/hooks/useTaskProjectManagement.ts +115 -122
- package/src/components/ui/tu-do/initiatives/task-initiatives-client.tsx +56 -88
- package/src/components/ui/tu-do/my-tasks/my-tasks-content.tsx +26 -2
- package/src/components/ui/tu-do/my-tasks/use-my-tasks-state.ts +55 -8
- package/src/components/ui/tu-do/notes/note-edit-dialog.tsx +1 -4
- package/src/components/ui/tu-do/progress/task-progress-import-panel.tsx +60 -0
- package/src/components/ui/tu-do/progress/task-progress-leaderboards-panel.tsx +156 -0
- package/src/components/ui/tu-do/progress/task-progress-page.tsx +348 -0
- package/src/components/ui/tu-do/progress/task-progress-panels.tsx +301 -0
- package/src/components/ui/tu-do/providers/task-dialog-provider.tsx +26 -0
- package/src/components/ui/tu-do/shared/__tests__/assignee-select.test.tsx +81 -10
- package/src/components/ui/tu-do/shared/__tests__/board-client.test.tsx +141 -1
- package/src/components/ui/tu-do/shared/__tests__/board-header.test.tsx +377 -36
- package/src/components/ui/tu-do/shared/__tests__/board-switcher.test.tsx +374 -0
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +419 -5
- package/src/components/ui/tu-do/shared/__tests__/task-board-loading-state.test.tsx +38 -0
- package/src/components/ui/tu-do/shared/__tests__/task-cache-patches.test.ts +147 -0
- package/src/components/ui/tu-do/shared/__tests__/task-legacy-route-recovery.test.tsx +16 -0
- package/src/components/ui/tu-do/shared/__tests__/use-progressive-board-loader.test.tsx +3 -0
- package/src/components/ui/tu-do/shared/assignee-select.tsx +77 -26
- package/src/components/ui/tu-do/shared/board-client.tsx +15 -10
- package/src/components/ui/tu-do/shared/board-config-storage.ts +7 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +471 -975
- package/src/components/ui/tu-do/shared/board-layout-settings.tsx +165 -136
- package/src/components/ui/tu-do/shared/board-switcher.tsx +244 -220
- package/src/components/ui/tu-do/shared/board-user-presence-avatars.tsx +18 -12
- package/src/components/ui/tu-do/shared/board-views.tsx +577 -85
- package/src/components/ui/tu-do/shared/list-view.tsx +246 -2
- package/src/components/ui/tu-do/shared/recycle-bin-panel.tsx +142 -94
- package/src/components/ui/tu-do/shared/special-task-list-pins.ts +51 -0
- package/src/components/ui/tu-do/shared/task-board-loading-state.tsx +28 -0
- package/src/components/ui/tu-do/shared/task-cache-patches.ts +394 -0
- package/src/components/ui/tu-do/shared/task-dialog-manager.tsx +21 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/quick-settings-popover.tsx +5 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-dialog-header.tsx +25 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-list-selector.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/field-diff-viewer.tsx +3 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-data.ts +79 -10
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-mutations.ts +76 -77
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.test.tsx +63 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/hooks/use-task-relationships.ts +78 -69
- package/src/components/ui/tu-do/shared/task-edit-dialog/personal-overrides-section.tsx +28 -8
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/dependencies-section.tsx +14 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/parent-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/related-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/subtasks-section.tsx +6 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/relationships/types/task-relationships.types.ts +8 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.test.tsx +91 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/selective-revert-panel.tsx +123 -78
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-activity-section.tsx +7 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-dialog-actions.tsx +8 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.test.tsx +150 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-properties-section.tsx +61 -35
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-relationships-properties.tsx +44 -2
- package/src/components/ui/tu-do/shared/task-edit-dialog/task-snapshot-dialog.tsx +8 -3
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +11 -1
- package/src/components/ui/tu-do/shared/task-legacy-route-recovery.tsx +2 -9
- package/src/components/ui/tu-do/shared/task-row-actions-menu.tsx +11 -0
- package/src/components/ui/tu-do/shared/use-progressive-board-loader.ts +2 -0
- package/src/declarations.d.ts +1 -0
- package/src/hooks/__tests__/use-calendar-readonly.test.tsx +322 -2
- package/src/hooks/__tests__/use-calendar-sync.test.tsx +446 -0
- package/src/hooks/__tests__/useBoardPresence.test.tsx +191 -0
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +24 -144
- package/src/hooks/use-calendar-sync.tsx +247 -243
- package/src/hooks/use-calendar.tsx +323 -138
- package/src/hooks/use-task-actions.ts +24 -0
- package/src/hooks/use-user-workspace-config.ts +75 -0
- package/src/hooks/use-workspace-currency.ts +8 -3
- package/src/hooks/useBoardPresence.ts +364 -0
- package/src/hooks/useBoardRealtimeEventHandler.ts +45 -90
- package/src/lib/workspace-actions.ts +2 -6
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from '@tuturuuu/icons';
|
|
31
31
|
import {
|
|
32
32
|
getWorkspaceTask,
|
|
33
|
+
listWorkspaceTaskBoardViewableMembers,
|
|
33
34
|
listWorkspaceTaskLists,
|
|
34
35
|
listWorkspaceTaskProjects,
|
|
35
36
|
removeCurrentUserTaskPersonalPlacement,
|
|
@@ -50,7 +51,10 @@ import {
|
|
|
50
51
|
import { useCalendarPreferences } from '@tuturuuu/ui/hooks/use-calendar-preferences';
|
|
51
52
|
import { useTaskActions } from '@tuturuuu/ui/hooks/use-task-actions';
|
|
52
53
|
import { useUserBooleanConfig } from '@tuturuuu/ui/hooks/use-user-config';
|
|
53
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
useWorkspaceMembers,
|
|
56
|
+
type WorkspaceMember,
|
|
57
|
+
} from '@tuturuuu/ui/hooks/use-workspace-members';
|
|
54
58
|
import {
|
|
55
59
|
HoverCard,
|
|
56
60
|
HoverCardContent,
|
|
@@ -69,7 +73,7 @@ import {
|
|
|
69
73
|
import { isTaskBoardResolvedStatus } from '@tuturuuu/utils/task-list-status';
|
|
70
74
|
import { getDescriptionMetadata } from '@tuturuuu/utils/text-helper';
|
|
71
75
|
import { getTimeFormatPattern } from '@tuturuuu/utils/time-helper';
|
|
72
|
-
import { format,
|
|
76
|
+
import { format, formatDistance } from 'date-fns';
|
|
73
77
|
import { enUS, vi } from 'date-fns/locale';
|
|
74
78
|
import Link from 'next/link';
|
|
75
79
|
import { useParams } from 'next/navigation';
|
|
@@ -116,6 +120,7 @@ import {
|
|
|
116
120
|
import { formatSmartDate } from '../../../utils/taskDateUtils';
|
|
117
121
|
import { getPriorityIndicator } from '../../../utils/taskPriorityUtils';
|
|
118
122
|
import { sortByDisplayName } from '../board-text-utils';
|
|
123
|
+
import { invalidateKanbanDeadlineTasks } from '../kanban/data/kanban-deadline-query';
|
|
119
124
|
import {
|
|
120
125
|
TaskAssigneesMenu,
|
|
121
126
|
TaskBlockingMenu,
|
|
@@ -151,6 +156,11 @@ import {
|
|
|
151
156
|
import { getTaskCardVisibilityState } from './task-card-visibility';
|
|
152
157
|
import { TaskSchedulingBadge } from './task-scheduling-badge';
|
|
153
158
|
|
|
159
|
+
export type TaskCardAssigneeMemberSource =
|
|
160
|
+
| 'workspace'
|
|
161
|
+
| 'board'
|
|
162
|
+
| 'workspace-and-board';
|
|
163
|
+
|
|
154
164
|
export interface TaskCardProps {
|
|
155
165
|
task: Task;
|
|
156
166
|
boardId: string;
|
|
@@ -162,6 +172,8 @@ export interface TaskCardProps {
|
|
|
162
172
|
isSelected?: boolean;
|
|
163
173
|
isMultiSelectMode?: boolean;
|
|
164
174
|
isPersonalWorkspace?: boolean;
|
|
175
|
+
canUseBoardAssignees?: boolean;
|
|
176
|
+
assigneeMemberSource?: TaskCardAssigneeMemberSource;
|
|
165
177
|
onSelect?: (taskId: string, event: React.MouseEvent) => void;
|
|
166
178
|
onClearSelection?: () => void;
|
|
167
179
|
dragDisabled?: boolean;
|
|
@@ -170,6 +182,128 @@ export interface TaskCardProps {
|
|
|
170
182
|
optimisticUpdateInProgress?: Set<string>;
|
|
171
183
|
selectedTasks?: Set<string>; // For bulk operations
|
|
172
184
|
bulkUpdateCustomDueDate?: (date: Date | null) => Promise<void>; // From useBulkOperations
|
|
185
|
+
deadlineContext?: 'overdue' | 'upcoming';
|
|
186
|
+
deadlineNow?: number;
|
|
187
|
+
readOnly?: boolean;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function ReadOnlyTaskCard({ task, taskList }: TaskCardProps) {
|
|
191
|
+
const t = useTranslations('common');
|
|
192
|
+
const publicBoardT = useTranslations('ws-task-boards.public');
|
|
193
|
+
const priorityT = useTranslations('ws-task-boards.dialog.priority');
|
|
194
|
+
const locale = useLocale();
|
|
195
|
+
const dateLocale = locale === 'vi' ? vi : enUS;
|
|
196
|
+
const ticketPrefix = (task as Task & { ticket_prefix?: string | null })
|
|
197
|
+
.ticket_prefix;
|
|
198
|
+
const ticketIdentifier =
|
|
199
|
+
typeof task.display_number === 'number' && task.display_number > 0
|
|
200
|
+
? getTicketIdentifier(ticketPrefix, task.display_number)
|
|
201
|
+
: null;
|
|
202
|
+
const dueDate = task.end_date
|
|
203
|
+
? format(new Date(task.end_date), 'MMM d, yyyy', { locale: dateLocale })
|
|
204
|
+
: null;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Card
|
|
208
|
+
className={cn(
|
|
209
|
+
'relative overflow-hidden rounded-lg border border-l-4 bg-background p-3 shadow-xs',
|
|
210
|
+
getCardColorClassesUtil(taskList, task.priority),
|
|
211
|
+
task.closed_at && 'opacity-60 saturate-50'
|
|
212
|
+
)}
|
|
213
|
+
data-task-card-id={task.id}
|
|
214
|
+
data-task-read-only="true"
|
|
215
|
+
>
|
|
216
|
+
<TaskCardIdentifierRow
|
|
217
|
+
externalSourceLabel=""
|
|
218
|
+
isMultiSelectMode={false}
|
|
219
|
+
isPersonalExternalTask={false}
|
|
220
|
+
isSelected={false}
|
|
221
|
+
selectTaskLabel={t('select_task', { name: task.name })}
|
|
222
|
+
taskListStatus={taskList?.status}
|
|
223
|
+
ticketBadgeClassName={getTicketBadgeColorClasses(
|
|
224
|
+
taskList,
|
|
225
|
+
task.priority
|
|
226
|
+
)}
|
|
227
|
+
ticketIdentifier={ticketIdentifier}
|
|
228
|
+
ticketTitle={ticketIdentifier ?? ''}
|
|
229
|
+
/>
|
|
230
|
+
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
<h3
|
|
233
|
+
className={cn(
|
|
234
|
+
'line-clamp-2 break-words font-medium text-sm leading-5',
|
|
235
|
+
(task.completed_at || task.closed_at) &&
|
|
236
|
+
'text-muted-foreground line-through'
|
|
237
|
+
)}
|
|
238
|
+
>
|
|
239
|
+
{task.name}
|
|
240
|
+
</h3>
|
|
241
|
+
|
|
242
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
243
|
+
{task.priority && (
|
|
244
|
+
<Badge variant="outline" className="h-5 px-1.5 font-normal text-xs">
|
|
245
|
+
{priorityT(task.priority)}
|
|
246
|
+
</Badge>
|
|
247
|
+
)}
|
|
248
|
+
{dueDate && (
|
|
249
|
+
<Badge
|
|
250
|
+
variant="secondary"
|
|
251
|
+
className="h-5 gap-1 px-1.5 font-normal text-xs"
|
|
252
|
+
>
|
|
253
|
+
<Calendar className="h-3 w-3" />
|
|
254
|
+
{dueDate}
|
|
255
|
+
</Badge>
|
|
256
|
+
)}
|
|
257
|
+
{typeof task.estimation_points === 'number' && (
|
|
258
|
+
<Badge variant="outline" className="h-5 px-1.5 font-normal text-xs">
|
|
259
|
+
{publicBoardT('points', { count: task.estimation_points })}
|
|
260
|
+
</Badge>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{(task.labels?.length ||
|
|
265
|
+
task.projects?.length ||
|
|
266
|
+
task.assignees?.length) && (
|
|
267
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
268
|
+
{task.labels?.map((label) => (
|
|
269
|
+
<Badge
|
|
270
|
+
key={label.id}
|
|
271
|
+
variant="outline"
|
|
272
|
+
className="h-5 gap-1 px-1.5 font-normal text-xs"
|
|
273
|
+
>
|
|
274
|
+
<span
|
|
275
|
+
aria-hidden="true"
|
|
276
|
+
className="h-2 w-2 rounded-full"
|
|
277
|
+
style={{ backgroundColor: label.color }}
|
|
278
|
+
/>
|
|
279
|
+
{label.name}
|
|
280
|
+
</Badge>
|
|
281
|
+
))}
|
|
282
|
+
{task.projects?.map((project) => (
|
|
283
|
+
<Badge
|
|
284
|
+
key={project.id}
|
|
285
|
+
variant="outline"
|
|
286
|
+
className="h-5 gap-1 px-1.5 font-normal text-xs"
|
|
287
|
+
>
|
|
288
|
+
<Box className="h-3 w-3" />
|
|
289
|
+
{project.name}
|
|
290
|
+
</Badge>
|
|
291
|
+
))}
|
|
292
|
+
{task.assignees?.map((assignee) => (
|
|
293
|
+
<Badge
|
|
294
|
+
key={assignee.id}
|
|
295
|
+
variant="secondary"
|
|
296
|
+
className="h-5 px-1.5 font-normal text-xs"
|
|
297
|
+
>
|
|
298
|
+
{assignee.display_name ||
|
|
299
|
+
(assignee.handle ? `@${assignee.handle}` : t('assignee'))}
|
|
300
|
+
</Badge>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
</Card>
|
|
306
|
+
);
|
|
173
307
|
}
|
|
174
308
|
|
|
175
309
|
// Memoized full TaskCard
|
|
@@ -184,6 +318,8 @@ function TaskCardInner({
|
|
|
184
318
|
isSelected = false,
|
|
185
319
|
isMultiSelectMode = false,
|
|
186
320
|
isPersonalWorkspace = false,
|
|
321
|
+
canUseBoardAssignees,
|
|
322
|
+
assigneeMemberSource,
|
|
187
323
|
onSelect,
|
|
188
324
|
onClearSelection,
|
|
189
325
|
dragDisabled: dragDisabledProp = false,
|
|
@@ -192,6 +328,8 @@ function TaskCardInner({
|
|
|
192
328
|
optimisticUpdateInProgress,
|
|
193
329
|
selectedTasks,
|
|
194
330
|
bulkUpdateCustomDueDate,
|
|
331
|
+
deadlineContext,
|
|
332
|
+
deadlineNow,
|
|
195
333
|
}: TaskCardProps) {
|
|
196
334
|
const { wsId: rawWsId } = useParams();
|
|
197
335
|
const wsId = Array.isArray(rawWsId) ? rawWsId[0] : rawWsId;
|
|
@@ -319,12 +457,59 @@ function TaskCardInner({
|
|
|
319
457
|
isMultiSelectMode,
|
|
320
458
|
onClearSelection,
|
|
321
459
|
});
|
|
460
|
+
const shouldUseBoardAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
|
|
461
|
+
const effectiveAssigneeMemberSource =
|
|
462
|
+
assigneeMemberSource ?? (isPersonalWorkspace ? 'board' : 'workspace');
|
|
463
|
+
const shouldLoadWorkspaceMembers =
|
|
464
|
+
shouldUseBoardAssignees && effectiveAssigneeMemberSource !== 'board';
|
|
465
|
+
const shouldLoadBoardViewableMembers =
|
|
466
|
+
shouldUseBoardAssignees && effectiveAssigneeMemberSource !== 'workspace';
|
|
322
467
|
|
|
323
468
|
// Fetch workspace members
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
469
|
+
const normalMembersQuery = useWorkspaceMembers(effectiveWorkspaceId, {
|
|
470
|
+
enabled: !!effectiveWorkspaceId && shouldLoadWorkspaceMembers,
|
|
471
|
+
});
|
|
472
|
+
const boardViewableMembersQuery = useQuery({
|
|
473
|
+
queryKey: ['task-board-viewable-members', effectiveWorkspaceId, boardId],
|
|
474
|
+
queryFn: async (): Promise<WorkspaceMember[]> => {
|
|
475
|
+
if (!effectiveWorkspaceId || !boardId) return [];
|
|
476
|
+
|
|
477
|
+
const payload = await listWorkspaceTaskBoardViewableMembers(
|
|
478
|
+
effectiveWorkspaceId,
|
|
479
|
+
boardId
|
|
480
|
+
);
|
|
481
|
+
const members = Array.isArray(payload?.members) ? payload.members : [];
|
|
482
|
+
|
|
483
|
+
return members.map((member) => ({
|
|
484
|
+
id: member.user_id,
|
|
485
|
+
user_id: member.user_id,
|
|
486
|
+
workspace_id: effectiveWorkspaceId,
|
|
487
|
+
display_name: member.display_name ?? member.email ?? member.user_id,
|
|
488
|
+
email: member.email ?? undefined,
|
|
489
|
+
avatar_url: member.avatar_url ?? undefined,
|
|
490
|
+
}));
|
|
491
|
+
},
|
|
492
|
+
enabled:
|
|
493
|
+
!!effectiveWorkspaceId && !!boardId && shouldLoadBoardViewableMembers,
|
|
494
|
+
staleTime: 5 * 60 * 1000,
|
|
495
|
+
});
|
|
496
|
+
const normalWorkspaceMembers = normalMembersQuery.data ?? [];
|
|
497
|
+
const boardViewableMembers = boardViewableMembersQuery.data ?? [];
|
|
498
|
+
const workspaceMembers = useMemo(() => {
|
|
499
|
+
const seen = new Set<string>();
|
|
500
|
+
const merged: WorkspaceMember[] = [];
|
|
501
|
+
|
|
502
|
+
for (const member of [...normalWorkspaceMembers, ...boardViewableMembers]) {
|
|
503
|
+
const memberId = member.user_id ?? member.id;
|
|
504
|
+
if (!memberId || seen.has(memberId)) continue;
|
|
505
|
+
seen.add(memberId);
|
|
506
|
+
merged.push(member);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return merged;
|
|
510
|
+
}, [boardViewableMembers, normalWorkspaceMembers]);
|
|
511
|
+
const membersLoading =
|
|
512
|
+
normalMembersQuery.isLoading || boardViewableMembersQuery.isLoading;
|
|
328
513
|
|
|
329
514
|
const relationshipSummary =
|
|
330
515
|
task.relationship_summary ??
|
|
@@ -769,7 +954,7 @@ function TaskCardInner({
|
|
|
769
954
|
opacity: isOverlay ? 1 : isOptimistic ? 0.6 : undefined,
|
|
770
955
|
};
|
|
771
956
|
|
|
772
|
-
const now = new Date();
|
|
957
|
+
const now = useMemo(() => new Date(deadlineNow ?? Date.now()), [deadlineNow]);
|
|
773
958
|
const shouldRenderDueDate = shouldShowTaskDueDate({
|
|
774
959
|
completedAt: task.completed_at,
|
|
775
960
|
closedAt: task.closed_at,
|
|
@@ -789,6 +974,18 @@ function TaskCardInner({
|
|
|
789
974
|
const isResolvedListStatus = isTaskBoardResolvedStatus(taskList?.status);
|
|
790
975
|
const startDate = task.start_date ? new Date(task.start_date) : null;
|
|
791
976
|
const endDate = task.end_date ? new Date(task.end_date) : null;
|
|
977
|
+
const upcomingDeadlineCountdown =
|
|
978
|
+
deadlineContext === 'upcoming' && endDate
|
|
979
|
+
? formatDistance(endDate, now, {
|
|
980
|
+
addSuffix: true,
|
|
981
|
+
locale: dateLocale,
|
|
982
|
+
})
|
|
983
|
+
: null;
|
|
984
|
+
const upcomingDeadlineExactDate = endDate
|
|
985
|
+
? format(endDate, `MMM dd '${t('at')}' ${timePattern}`, {
|
|
986
|
+
locale: dateLocale,
|
|
987
|
+
})
|
|
988
|
+
: null;
|
|
792
989
|
const selectionCheckboxClassName = cn(
|
|
793
990
|
getTaskCardSelectionCheckboxToneClasses(taskList?.color as SupportedColor),
|
|
794
991
|
isOverdue &&
|
|
@@ -897,6 +1094,12 @@ function TaskCardInner({
|
|
|
897
1094
|
task,
|
|
898
1095
|
boardId,
|
|
899
1096
|
availableLists,
|
|
1097
|
+
canUseBoardAssignees: task.source_workspace_id
|
|
1098
|
+
? true
|
|
1099
|
+
: shouldUseBoardAssignees,
|
|
1100
|
+
assigneeMemberSource: task.source_workspace_id
|
|
1101
|
+
? 'workspace'
|
|
1102
|
+
: effectiveAssigneeMemberSource,
|
|
900
1103
|
effectiveWorkspaceId,
|
|
901
1104
|
isPersonalWorkspace,
|
|
902
1105
|
})
|
|
@@ -905,6 +1108,8 @@ function TaskCardInner({
|
|
|
905
1108
|
task,
|
|
906
1109
|
boardId,
|
|
907
1110
|
availableLists,
|
|
1111
|
+
shouldUseBoardAssignees,
|
|
1112
|
+
effectiveAssigneeMemberSource,
|
|
908
1113
|
effectiveWorkspaceId,
|
|
909
1114
|
isPersonalWorkspace,
|
|
910
1115
|
openTaskById,
|
|
@@ -938,6 +1143,8 @@ function TaskCardInner({
|
|
|
938
1143
|
openTask(task, boardId, availableLists, false, {
|
|
939
1144
|
taskWsId: effectiveWorkspaceId,
|
|
940
1145
|
taskWorkspacePersonal: isPersonalWorkspace,
|
|
1146
|
+
canUseBoardAssignees: shouldUseBoardAssignees,
|
|
1147
|
+
assigneeMemberSource: effectiveAssigneeMemberSource,
|
|
941
1148
|
});
|
|
942
1149
|
}
|
|
943
1150
|
},
|
|
@@ -945,7 +1152,9 @@ function TaskCardInner({
|
|
|
945
1152
|
task,
|
|
946
1153
|
boardId,
|
|
947
1154
|
effectiveWorkspaceId,
|
|
1155
|
+
effectiveAssigneeMemberSource,
|
|
948
1156
|
isPersonalWorkspace,
|
|
1157
|
+
shouldUseBoardAssignees,
|
|
949
1158
|
isPersonalExternalTask,
|
|
950
1159
|
isMultiSelectMode,
|
|
951
1160
|
availableLists,
|
|
@@ -1025,6 +1234,7 @@ function TaskCardInner({
|
|
|
1025
1234
|
)
|
|
1026
1235
|
);
|
|
1027
1236
|
|
|
1237
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1028
1238
|
toast.success(tTasks('moved_to_external_tasks'));
|
|
1029
1239
|
} catch (error) {
|
|
1030
1240
|
console.error('Failed to move task to external staging:', error);
|
|
@@ -1045,6 +1255,7 @@ function TaskCardInner({
|
|
|
1045
1255
|
old?.filter((candidate) => candidate.id !== task.id)
|
|
1046
1256
|
);
|
|
1047
1257
|
|
|
1258
|
+
void invalidateKanbanDeadlineTasks(queryClient, boardId);
|
|
1048
1259
|
toast.success(tTasks('removed_from_personal_board'));
|
|
1049
1260
|
} catch (error) {
|
|
1050
1261
|
console.error('Failed to remove task from personal board:', error);
|
|
@@ -2135,8 +2346,8 @@ function TaskCardInner({
|
|
|
2135
2346
|
</>
|
|
2136
2347
|
)}
|
|
2137
2348
|
|
|
2138
|
-
{/* Assignee Actions
|
|
2139
|
-
{
|
|
2349
|
+
{/* Assignee Actions */}
|
|
2350
|
+
{shouldUseBoardAssignees && (
|
|
2140
2351
|
<TaskAssigneesMenu
|
|
2141
2352
|
taskAssignees={displayAssignees}
|
|
2142
2353
|
availableMembers={workspaceMembers}
|
|
@@ -2206,7 +2417,7 @@ function TaskCardInner({
|
|
|
2206
2417
|
)}
|
|
2207
2418
|
</div>
|
|
2208
2419
|
{/* Assignee: left, not cut off */}
|
|
2209
|
-
{
|
|
2420
|
+
{shouldUseBoardAssignees && (
|
|
2210
2421
|
<div className="flex flex-none items-start justify-start">
|
|
2211
2422
|
<AssigneeSelect
|
|
2212
2423
|
taskId={task.id}
|
|
@@ -2247,30 +2458,46 @@ function TaskCardInner({
|
|
|
2247
2458
|
: 'text-muted-foreground'
|
|
2248
2459
|
)}
|
|
2249
2460
|
>
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
{isOverdue && !task.closed_at ? (
|
|
2265
|
-
<Badge className="ml-1 h-4 bg-dynamic-red px-1 font-semibold text-[9px] text-white tracking-wide">
|
|
2266
|
-
{t('overdue')}
|
|
2267
|
-
</Badge>
|
|
2461
|
+
{upcomingDeadlineCountdown && upcomingDeadlineExactDate ? (
|
|
2462
|
+
<Tooltip>
|
|
2463
|
+
<TooltipTrigger asChild>
|
|
2464
|
+
<span className="inline-flex min-w-0 items-center gap-1 truncate font-medium">
|
|
2465
|
+
<Timer className="h-2.5 w-2.5" />
|
|
2466
|
+
<span className="truncate">
|
|
2467
|
+
{upcomingDeadlineCountdown}
|
|
2468
|
+
</span>
|
|
2469
|
+
</span>
|
|
2470
|
+
</TooltipTrigger>
|
|
2471
|
+
<TooltipContent side="top" className="text-xs">
|
|
2472
|
+
{upcomingDeadlineExactDate}
|
|
2473
|
+
</TooltipContent>
|
|
2474
|
+
</Tooltip>
|
|
2268
2475
|
) : (
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2476
|
+
<>
|
|
2477
|
+
<Calendar className="h-2.5 w-2.5 shrink-0" />
|
|
2478
|
+
<span className="truncate">
|
|
2479
|
+
{t('due_at', {
|
|
2480
|
+
date: formatSmartDate(
|
|
2481
|
+
endDate,
|
|
2482
|
+
{
|
|
2483
|
+
today: t('today'),
|
|
2484
|
+
tomorrow: t('tomorrow'),
|
|
2485
|
+
yesterday: t('yesterday'),
|
|
2486
|
+
},
|
|
2487
|
+
dateLocale
|
|
2488
|
+
),
|
|
2489
|
+
})}
|
|
2490
|
+
</span>
|
|
2491
|
+
{isOverdue && !task.closed_at ? (
|
|
2492
|
+
<Badge className="ml-1 h-4 bg-dynamic-red px-1 font-semibold text-[9px] text-white tracking-wide">
|
|
2493
|
+
{t('overdue')}
|
|
2494
|
+
</Badge>
|
|
2495
|
+
) : (
|
|
2496
|
+
<span className="ml-1 hidden text-[10px] text-muted-foreground md:inline">
|
|
2497
|
+
{upcomingDeadlineExactDate}
|
|
2498
|
+
</span>
|
|
2499
|
+
)}
|
|
2500
|
+
</>
|
|
2274
2501
|
)}
|
|
2275
2502
|
</div>
|
|
2276
2503
|
)}
|
|
@@ -2293,7 +2520,7 @@ function TaskCardInner({
|
|
|
2293
2520
|
<CheckCircle2 className="h-2.5 w-2.5 shrink-0" />
|
|
2294
2521
|
<span className="truncate">
|
|
2295
2522
|
{t('completed')}{' '}
|
|
2296
|
-
{
|
|
2523
|
+
{formatDistance(new Date(task.completed_at), now, {
|
|
2297
2524
|
addSuffix: true,
|
|
2298
2525
|
locale: dateLocale,
|
|
2299
2526
|
})}
|
|
@@ -2317,7 +2544,7 @@ function TaskCardInner({
|
|
|
2317
2544
|
<CircleSlash className="h-2.5 w-2.5 shrink-0" />
|
|
2318
2545
|
<span className="truncate">
|
|
2319
2546
|
{t('closed')}{' '}
|
|
2320
|
-
{
|
|
2547
|
+
{formatDistance(new Date(task.closed_at), now, {
|
|
2321
2548
|
addSuffix: true,
|
|
2322
2549
|
locale: dateLocale,
|
|
2323
2550
|
})}
|
|
@@ -2590,4 +2817,12 @@ function TaskCardInner({
|
|
|
2590
2817
|
);
|
|
2591
2818
|
}
|
|
2592
2819
|
|
|
2593
|
-
|
|
2820
|
+
function TaskCardComponent(props: TaskCardProps) {
|
|
2821
|
+
if (props.readOnly) {
|
|
2822
|
+
return <ReadOnlyTaskCard {...props} />;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
return <TaskCardInner {...props} />;
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
export const TaskCard = React.memo(TaskCardComponent, areTaskCardPropsEqual);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
4
|
+
import type React from 'react';
|
|
5
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import type { TaskFilters } from '../../shared/task-filter.types';
|
|
7
|
+
import { TaskFilter } from './task-filter';
|
|
8
|
+
|
|
9
|
+
vi.mock('next-intl', () => ({
|
|
10
|
+
useTranslations: () => (key: string) => key,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('@tuturuuu/internal-api/tasks', () => ({
|
|
14
|
+
listWorkspaceLabels: vi.fn(() => Promise.resolve([])),
|
|
15
|
+
listWorkspaceTaskBoards: vi.fn(() =>
|
|
16
|
+
Promise.resolve({ boards: [], count: 0 })
|
|
17
|
+
),
|
|
18
|
+
listWorkspaceTaskProjects: vi.fn(() => Promise.resolve([])),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('@tuturuuu/internal-api/workspaces', () => ({
|
|
22
|
+
listWorkspaces: vi.fn(() => Promise.resolve([])),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock('@tuturuuu/ui/hooks/use-workspace-members', () => ({
|
|
26
|
+
useWorkspaceMembers: () => ({ data: [] }),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('@tuturuuu/ui/custom/combobox', () => ({
|
|
30
|
+
Combobox: ({
|
|
31
|
+
placeholder,
|
|
32
|
+
}: {
|
|
33
|
+
children?: React.ReactNode;
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
}) => (
|
|
36
|
+
<button type="button" aria-label={placeholder}>
|
|
37
|
+
{placeholder}
|
|
38
|
+
</button>
|
|
39
|
+
),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
class ResizeObserverMock {
|
|
44
|
+
observe() {}
|
|
45
|
+
unobserve() {}
|
|
46
|
+
disconnect() {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
vi.stubGlobal('ResizeObserver', ResizeObserverMock);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const baseFilters: TaskFilters = {
|
|
53
|
+
assignees: [],
|
|
54
|
+
dueDateRange: null,
|
|
55
|
+
estimationRange: null,
|
|
56
|
+
includeMyTasks: false,
|
|
57
|
+
includeUnassigned: false,
|
|
58
|
+
labels: [],
|
|
59
|
+
priorities: [],
|
|
60
|
+
projects: [],
|
|
61
|
+
sourceBoardIds: [],
|
|
62
|
+
sourceScope: 'all_visible',
|
|
63
|
+
sourceWorkspaceIds: [],
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function renderTaskFilter(
|
|
67
|
+
overrides?: Partial<React.ComponentProps<typeof TaskFilter>>
|
|
68
|
+
) {
|
|
69
|
+
const queryClient = new QueryClient({
|
|
70
|
+
defaultOptions: {
|
|
71
|
+
queries: {
|
|
72
|
+
retry: false,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const onFiltersChange = vi.fn();
|
|
77
|
+
|
|
78
|
+
render(
|
|
79
|
+
<QueryClientProvider client={queryClient}>
|
|
80
|
+
<TaskFilter
|
|
81
|
+
currentUserId="user-1"
|
|
82
|
+
filters={baseFilters}
|
|
83
|
+
onFiltersChange={onFiltersChange}
|
|
84
|
+
wsId="ws-1"
|
|
85
|
+
{...overrides}
|
|
86
|
+
/>
|
|
87
|
+
</QueryClientProvider>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return { onFiltersChange };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe('TaskFilter', () => {
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
vi.clearAllMocks();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('renders compact filter sections with responsive due-date controls', () => {
|
|
99
|
+
const { onFiltersChange } = renderTaskFilter();
|
|
100
|
+
|
|
101
|
+
fireEvent.click(screen.getByRole('button', { name: 'common.filters' }));
|
|
102
|
+
|
|
103
|
+
expect(screen.getByText('common.quick_filters')).toBeInTheDocument();
|
|
104
|
+
expect(
|
|
105
|
+
screen.getAllByText('ws-tasks.filter_source_scope').length
|
|
106
|
+
).toBeGreaterThan(0);
|
|
107
|
+
expect(screen.getByText('common.people')).toBeInTheDocument();
|
|
108
|
+
expect(screen.getByText('common.details')).toBeInTheDocument();
|
|
109
|
+
expect(screen.getAllByText('common.due_date').length).toBeGreaterThan(0);
|
|
110
|
+
expect(screen.getByLabelText('common.from')).toHaveAttribute(
|
|
111
|
+
'type',
|
|
112
|
+
'date'
|
|
113
|
+
);
|
|
114
|
+
expect(screen.getByLabelText('common.to')).toHaveAttribute('type', 'date');
|
|
115
|
+
expect(screen.getByLabelText('common.clear')).toBeInTheDocument();
|
|
116
|
+
expect(
|
|
117
|
+
screen.queryByRole('grid', { name: /calendar/i })
|
|
118
|
+
).not.toBeInTheDocument();
|
|
119
|
+
|
|
120
|
+
fireEvent.change(screen.getByLabelText('common.from'), {
|
|
121
|
+
target: { value: '2026-06-22' },
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(onFiltersChange).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
dueDateRange: expect.objectContaining({
|
|
127
|
+
from: expect.any(Date),
|
|
128
|
+
}),
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('shows active count badges for compact sections', () => {
|
|
134
|
+
renderTaskFilter({
|
|
135
|
+
filters: {
|
|
136
|
+
...baseFilters,
|
|
137
|
+
dueDateRange: { from: new Date(2026, 5, 22), to: undefined },
|
|
138
|
+
includeMyTasks: true,
|
|
139
|
+
sourceScope: 'external_current_workspace',
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(
|
|
144
|
+
screen.getByRole('button', { name: 'common.filters' })
|
|
145
|
+
).toHaveTextContent('3');
|
|
146
|
+
|
|
147
|
+
fireEvent.click(screen.getByRole('button', { name: 'common.filters' }));
|
|
148
|
+
|
|
149
|
+
expect(screen.getByText('common.quick_filters')).toBeInTheDocument();
|
|
150
|
+
expect(screen.getAllByText('1').length).toBeGreaterThanOrEqual(3);
|
|
151
|
+
});
|
|
152
|
+
});
|