@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
|
@@ -61,6 +61,7 @@ import { useLocale, useTranslations } from 'next-intl';
|
|
|
61
61
|
import { useTheme } from 'next-themes';
|
|
62
62
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
63
63
|
import { useBulkOperations } from '../boards/boardId/kanban/bulk/bulk-operations';
|
|
64
|
+
import type { TaskCardAssigneeMemberSource } from '../boards/boardId/task-card/task-card';
|
|
64
65
|
import {
|
|
65
66
|
getTaskCardHydratingOpenOptions,
|
|
66
67
|
isExternalTaskSnapshot,
|
|
@@ -85,9 +86,12 @@ interface Props {
|
|
|
85
86
|
tasks: Task[];
|
|
86
87
|
lists: TaskList[];
|
|
87
88
|
isPersonalWorkspace?: boolean;
|
|
89
|
+
canUseBoardAssignees?: boolean;
|
|
90
|
+
assigneeMemberSource?: TaskCardAssigneeMemberSource;
|
|
88
91
|
preserveTaskOrder?: boolean;
|
|
89
92
|
searchQuery?: string;
|
|
90
93
|
weekStartsOn?: 0 | 1 | 6;
|
|
94
|
+
readOnly?: boolean;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
interface ColumnVisibility {
|
|
@@ -105,12 +109,241 @@ type TaskMenuState = {
|
|
|
105
109
|
point?: { x: number; y: number } | null;
|
|
106
110
|
};
|
|
107
111
|
|
|
108
|
-
export function ListView({
|
|
112
|
+
export function ListView(props: Props) {
|
|
113
|
+
if (props.readOnly) {
|
|
114
|
+
return <ReadOnlyListView {...props} />;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return <InteractiveListView {...props} />;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function ReadOnlyListView({
|
|
121
|
+
tasks,
|
|
122
|
+
lists,
|
|
123
|
+
isPersonalWorkspace = false,
|
|
124
|
+
canUseBoardAssignees,
|
|
125
|
+
preserveTaskOrder = false,
|
|
126
|
+
searchQuery,
|
|
127
|
+
}: Props) {
|
|
128
|
+
const t = useTranslations();
|
|
129
|
+
const tc = useTranslations('common');
|
|
130
|
+
const locale = useLocale();
|
|
131
|
+
const dateLocale = locale === 'vi' ? vi : enUS;
|
|
132
|
+
const [sortField, setSortField] = useState<ListViewSortField>('created_at');
|
|
133
|
+
const [sortOrder, setSortOrder] = useState<ListViewSortOrder>('desc');
|
|
134
|
+
const showAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
|
|
135
|
+
const listsById = useMemo(
|
|
136
|
+
() => new Map(lists.map((list) => [list.id, list])),
|
|
137
|
+
[lists]
|
|
138
|
+
);
|
|
139
|
+
const sortedTasks = useMemo(
|
|
140
|
+
() =>
|
|
141
|
+
sortListViewTasks(tasks, {
|
|
142
|
+
preserveTaskOrder,
|
|
143
|
+
searchQuery,
|
|
144
|
+
sortField,
|
|
145
|
+
sortOrder,
|
|
146
|
+
}),
|
|
147
|
+
[preserveTaskOrder, searchQuery, sortField, sortOrder, tasks]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
function handleSort(field: ListViewSortField) {
|
|
151
|
+
if (field === sortField) {
|
|
152
|
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
|
153
|
+
} else {
|
|
154
|
+
setSortField(field);
|
|
155
|
+
setSortOrder('asc');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getSortIcon(field: ListViewSortField) {
|
|
160
|
+
if (sortField !== field) {
|
|
161
|
+
return <ArrowDownUp className="ml-2 h-3 w-3 text-muted-foreground" />;
|
|
162
|
+
}
|
|
163
|
+
return sortOrder === 'asc' ? (
|
|
164
|
+
<ArrowUp className="ml-2 h-3 w-3 text-foreground" />
|
|
165
|
+
) : (
|
|
166
|
+
<ArrowDown className="ml-2 h-3 w-3 text-foreground" />
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function formatDate(date: string) {
|
|
171
|
+
return format(new Date(date), 'MMM dd', { locale: dateLocale });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div className="flex h-full flex-col">
|
|
176
|
+
{sortedTasks.length === 0 ? (
|
|
177
|
+
<div className="flex flex-1 items-center justify-center">
|
|
178
|
+
<p className="text-muted-foreground text-sm">{tc('no_tasks')}</p>
|
|
179
|
+
</div>
|
|
180
|
+
) : (
|
|
181
|
+
<div className="relative flex-1 overflow-auto">
|
|
182
|
+
<Table>
|
|
183
|
+
<TableHeader className="sticky top-0 z-10 border-b bg-background">
|
|
184
|
+
<TableRow className="hover:bg-transparent">
|
|
185
|
+
<TableHead className="h-9 min-w-62.5 px-3">
|
|
186
|
+
<Button
|
|
187
|
+
variant="ghost"
|
|
188
|
+
className={cn(
|
|
189
|
+
'-ml-2 h-6 justify-start gap-1 px-2 font-medium text-[10px] uppercase tracking-wider transition-colors hover:bg-muted/50',
|
|
190
|
+
sortField === 'name'
|
|
191
|
+
? 'text-foreground'
|
|
192
|
+
: 'text-muted-foreground'
|
|
193
|
+
)}
|
|
194
|
+
onClick={() => handleSort('name')}
|
|
195
|
+
>
|
|
196
|
+
{tc('task_header')}
|
|
197
|
+
{getSortIcon('name')}
|
|
198
|
+
</Button>
|
|
199
|
+
</TableHead>
|
|
200
|
+
<TableHead className="h-9 w-32 px-2">
|
|
201
|
+
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
202
|
+
{tc('status')}
|
|
203
|
+
</span>
|
|
204
|
+
</TableHead>
|
|
205
|
+
<TableHead className="h-9 w-24 px-2">
|
|
206
|
+
<Button
|
|
207
|
+
variant="ghost"
|
|
208
|
+
className={cn(
|
|
209
|
+
'-ml-2 h-6 justify-start gap-1 px-2 font-medium text-[10px] uppercase tracking-wider transition-colors hover:bg-muted/50',
|
|
210
|
+
sortField === 'priority'
|
|
211
|
+
? 'text-foreground'
|
|
212
|
+
: 'text-muted-foreground'
|
|
213
|
+
)}
|
|
214
|
+
onClick={() => handleSort('priority')}
|
|
215
|
+
>
|
|
216
|
+
{tc('priority')}
|
|
217
|
+
{getSortIcon('priority')}
|
|
218
|
+
</Button>
|
|
219
|
+
</TableHead>
|
|
220
|
+
<TableHead className="h-9 w-28 px-2">
|
|
221
|
+
<Button
|
|
222
|
+
variant="ghost"
|
|
223
|
+
className={cn(
|
|
224
|
+
'-ml-2 h-6 justify-start gap-1 px-2 font-medium text-[10px] uppercase tracking-wider transition-colors hover:bg-muted/50',
|
|
225
|
+
sortField === 'end_date'
|
|
226
|
+
? 'text-foreground'
|
|
227
|
+
: 'text-muted-foreground'
|
|
228
|
+
)}
|
|
229
|
+
onClick={() => handleSort('end_date')}
|
|
230
|
+
>
|
|
231
|
+
{tc('due')}
|
|
232
|
+
{getSortIcon('end_date')}
|
|
233
|
+
</Button>
|
|
234
|
+
</TableHead>
|
|
235
|
+
{showAssignees && (
|
|
236
|
+
<TableHead className="h-9 w-32 px-2">
|
|
237
|
+
<span className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
|
|
238
|
+
{tc('assignee')}
|
|
239
|
+
</span>
|
|
240
|
+
</TableHead>
|
|
241
|
+
)}
|
|
242
|
+
</TableRow>
|
|
243
|
+
</TableHeader>
|
|
244
|
+
<TableBody>
|
|
245
|
+
{sortedTasks.map((task) => {
|
|
246
|
+
const list = listsById.get(task.list_id);
|
|
247
|
+
return (
|
|
248
|
+
<TableRow key={task.id} className="h-12 border-b">
|
|
249
|
+
<TableCell className="px-3 py-2">
|
|
250
|
+
<div className="min-w-0 space-y-1">
|
|
251
|
+
<div
|
|
252
|
+
className={cn(
|
|
253
|
+
'truncate font-medium text-sm',
|
|
254
|
+
(task.completed_at || task.closed_at) &&
|
|
255
|
+
'text-muted-foreground line-through'
|
|
256
|
+
)}
|
|
257
|
+
>
|
|
258
|
+
{task.name}
|
|
259
|
+
</div>
|
|
260
|
+
{(task.labels?.length || task.projects?.length) && (
|
|
261
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
262
|
+
{task.labels?.slice(0, 3).map((label) => (
|
|
263
|
+
<Badge
|
|
264
|
+
key={label.id}
|
|
265
|
+
variant="outline"
|
|
266
|
+
className="h-4 gap-1 px-1.5 font-normal text-[10px]"
|
|
267
|
+
>
|
|
268
|
+
<span
|
|
269
|
+
aria-hidden="true"
|
|
270
|
+
className="h-2 w-2 rounded-full"
|
|
271
|
+
style={{ backgroundColor: label.color }}
|
|
272
|
+
/>
|
|
273
|
+
{label.name}
|
|
274
|
+
</Badge>
|
|
275
|
+
))}
|
|
276
|
+
{task.projects?.slice(0, 2).map((project) => (
|
|
277
|
+
<Badge
|
|
278
|
+
key={project.id}
|
|
279
|
+
variant="secondary"
|
|
280
|
+
className="h-4 px-1.5 font-normal text-[10px]"
|
|
281
|
+
>
|
|
282
|
+
{project.name}
|
|
283
|
+
</Badge>
|
|
284
|
+
))}
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
</TableCell>
|
|
289
|
+
<TableCell className="px-2 py-2">
|
|
290
|
+
<Badge variant="outline" className="font-normal">
|
|
291
|
+
{list?.name ?? tc('untitled')}
|
|
292
|
+
</Badge>
|
|
293
|
+
</TableCell>
|
|
294
|
+
<TableCell className="px-2 py-2">
|
|
295
|
+
{task.priority && (
|
|
296
|
+
<Badge variant="secondary" className="font-normal">
|
|
297
|
+
{t(`tasks.priority_${task.priority}`)}
|
|
298
|
+
</Badge>
|
|
299
|
+
)}
|
|
300
|
+
</TableCell>
|
|
301
|
+
<TableCell className="px-2 py-2">
|
|
302
|
+
{task.end_date && (
|
|
303
|
+
<span className="text-muted-foreground text-xs">
|
|
304
|
+
{formatDate(task.end_date)}
|
|
305
|
+
</span>
|
|
306
|
+
)}
|
|
307
|
+
</TableCell>
|
|
308
|
+
{showAssignees && (
|
|
309
|
+
<TableCell className="px-2 py-2">
|
|
310
|
+
{task.assignees && task.assignees.length > 0 && (
|
|
311
|
+
<div className="flex flex-wrap gap-1">
|
|
312
|
+
{task.assignees.slice(0, 2).map((assignee) => (
|
|
313
|
+
<Badge
|
|
314
|
+
key={assignee.id}
|
|
315
|
+
variant="secondary"
|
|
316
|
+
className="font-normal"
|
|
317
|
+
>
|
|
318
|
+
{assignee.display_name ||
|
|
319
|
+
assignee.email ||
|
|
320
|
+
assignee.handle ||
|
|
321
|
+
tc('assignee')}
|
|
322
|
+
</Badge>
|
|
323
|
+
))}
|
|
324
|
+
</div>
|
|
325
|
+
)}
|
|
326
|
+
</TableCell>
|
|
327
|
+
)}
|
|
328
|
+
</TableRow>
|
|
329
|
+
);
|
|
330
|
+
})}
|
|
331
|
+
</TableBody>
|
|
332
|
+
</Table>
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function InteractiveListView({
|
|
109
340
|
workspaceId,
|
|
110
341
|
boardId,
|
|
111
342
|
tasks,
|
|
112
343
|
lists,
|
|
113
344
|
isPersonalWorkspace = false,
|
|
345
|
+
canUseBoardAssignees,
|
|
346
|
+
assigneeMemberSource,
|
|
114
347
|
preserveTaskOrder = false,
|
|
115
348
|
searchQuery,
|
|
116
349
|
weekStartsOn = 0,
|
|
@@ -134,6 +367,9 @@ export function ListView({
|
|
|
134
367
|
const previousWorkspaceIdRef = useRef(workspaceId);
|
|
135
368
|
const previousBoardIdRef = useRef(boardId);
|
|
136
369
|
const { openTask, openTaskById } = useTaskDialog();
|
|
370
|
+
const showAssignees = canUseBoardAssignees ?? !isPersonalWorkspace;
|
|
371
|
+
const effectiveAssigneeMemberSource =
|
|
372
|
+
assigneeMemberSource ?? (isPersonalWorkspace ? 'board' : 'workspace');
|
|
137
373
|
|
|
138
374
|
// Infinite scroll
|
|
139
375
|
const [displayCount, setDisplayCount] = useState(50);
|
|
@@ -146,7 +382,7 @@ export function ListView({
|
|
|
146
382
|
priority: true,
|
|
147
383
|
start_date: false,
|
|
148
384
|
end_date: true,
|
|
149
|
-
assignees:
|
|
385
|
+
assignees: showAssignees,
|
|
150
386
|
actions: true,
|
|
151
387
|
});
|
|
152
388
|
|
|
@@ -290,6 +526,10 @@ export function ListView({
|
|
|
290
526
|
availableLists: lists,
|
|
291
527
|
effectiveWorkspaceId: workspaceId,
|
|
292
528
|
isPersonalWorkspace,
|
|
529
|
+
canUseBoardAssignees: task.source_workspace_id ? true : showAssignees,
|
|
530
|
+
assigneeMemberSource: task.source_workspace_id
|
|
531
|
+
? 'workspace'
|
|
532
|
+
: effectiveAssigneeMemberSource,
|
|
293
533
|
})
|
|
294
534
|
);
|
|
295
535
|
return;
|
|
@@ -298,6 +538,8 @@ export function ListView({
|
|
|
298
538
|
openTask(task, boardId, lists, false, {
|
|
299
539
|
taskWsId: workspaceId,
|
|
300
540
|
taskWorkspacePersonal: isPersonalWorkspace,
|
|
541
|
+
canUseBoardAssignees: showAssignees,
|
|
542
|
+
assigneeMemberSource: effectiveAssigneeMemberSource,
|
|
301
543
|
});
|
|
302
544
|
}
|
|
303
545
|
|
|
@@ -746,6 +988,8 @@ export function ListView({
|
|
|
746
988
|
workspaceId={workspaceId}
|
|
747
989
|
lists={lists}
|
|
748
990
|
isPersonalWorkspace={isPersonalWorkspace}
|
|
991
|
+
canUseBoardAssignees={showAssignees}
|
|
992
|
+
assigneeMemberSource={effectiveAssigneeMemberSource}
|
|
749
993
|
onUpdate={() => {
|
|
750
994
|
void queryClient.invalidateQueries({
|
|
751
995
|
queryKey: ['tasks', boardId],
|
|
@@ -73,14 +73,24 @@ interface RecycleBinPanelProps {
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
open
|
|
78
|
-
|
|
76
|
+
interface RecycleBinContentProps
|
|
77
|
+
extends Omit<RecycleBinPanelProps, 'open' | 'onOpenChange'> {
|
|
78
|
+
active?: boolean;
|
|
79
|
+
className?: string;
|
|
80
|
+
onParentOpenChange?: (open: boolean) => void;
|
|
81
|
+
showHeader?: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function RecycleBinContent({
|
|
85
|
+
active = true,
|
|
86
|
+
className,
|
|
87
|
+
onParentOpenChange,
|
|
88
|
+
showHeader = true,
|
|
79
89
|
wsId,
|
|
80
90
|
boardId,
|
|
81
91
|
lists,
|
|
82
92
|
translations,
|
|
83
|
-
}:
|
|
93
|
+
}: RecycleBinContentProps) {
|
|
84
94
|
// Use provided translations or fall back to English defaults
|
|
85
95
|
const t = useMemo(
|
|
86
96
|
() => ({
|
|
@@ -143,7 +153,7 @@ export function RecycleBinPanel({
|
|
|
143
153
|
boardId,
|
|
144
154
|
wsId,
|
|
145
155
|
{
|
|
146
|
-
enabled:
|
|
156
|
+
enabled: active,
|
|
147
157
|
staleTime: 60000,
|
|
148
158
|
}
|
|
149
159
|
);
|
|
@@ -193,20 +203,20 @@ export function RecycleBinPanel({
|
|
|
193
203
|
(isOpen: boolean) => {
|
|
194
204
|
setRestoreDialogOpen(isOpen);
|
|
195
205
|
if (isOpen) {
|
|
196
|
-
|
|
206
|
+
onParentOpenChange?.(false); // Close Sheet when dialog opens
|
|
197
207
|
}
|
|
198
208
|
},
|
|
199
|
-
[
|
|
209
|
+
[onParentOpenChange]
|
|
200
210
|
);
|
|
201
211
|
|
|
202
212
|
const handleDeleteDialogOpenChange = useCallback(
|
|
203
213
|
(isOpen: boolean) => {
|
|
204
214
|
setDeleteDialogOpen(isOpen);
|
|
205
215
|
if (isOpen) {
|
|
206
|
-
|
|
216
|
+
onParentOpenChange?.(false); // Close Sheet when dialog opens
|
|
207
217
|
}
|
|
208
218
|
},
|
|
209
|
-
[
|
|
219
|
+
[onParentOpenChange]
|
|
210
220
|
);
|
|
211
221
|
|
|
212
222
|
const handleRestore = useCallback(async () => {
|
|
@@ -254,97 +264,96 @@ export function RecycleBinPanel({
|
|
|
254
264
|
|
|
255
265
|
return (
|
|
256
266
|
<>
|
|
257
|
-
<
|
|
258
|
-
|
|
259
|
-
<
|
|
260
|
-
<
|
|
267
|
+
<div className={cn('flex min-h-0 flex-1 flex-col', className)}>
|
|
268
|
+
{showHeader && (
|
|
269
|
+
<div className="border-b p-4">
|
|
270
|
+
<h2 className="flex items-center gap-2 font-semibold text-lg">
|
|
261
271
|
<Trash2 className="h-5 w-5" />
|
|
262
272
|
{t.recycleBin}
|
|
263
|
-
</
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
273
|
+
</h2>
|
|
274
|
+
<p className="mt-1 text-muted-foreground text-sm">
|
|
275
|
+
{t.recycleBinDescription}
|
|
276
|
+
</p>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
{/* Header with select all */}
|
|
280
|
+
{deletedTasks.length > 0 && (
|
|
281
|
+
<div className="flex items-center gap-3 border-b p-3">
|
|
282
|
+
<Checkbox
|
|
283
|
+
checked={allSelected}
|
|
284
|
+
onCheckedChange={handleSelectAll}
|
|
285
|
+
aria-label={t.selectAllTasks}
|
|
286
|
+
/>
|
|
287
|
+
<span className="text-foreground text-sm">
|
|
288
|
+
{someSelected
|
|
289
|
+
? t.selectedOfTotal
|
|
290
|
+
.replace('{selected}', String(selectedTasks.size))
|
|
291
|
+
.replace('{total}', String(deletedTasks.length))
|
|
292
|
+
: t.deletedTasksCount.replace(
|
|
293
|
+
'{count}',
|
|
294
|
+
String(deletedTasks.length)
|
|
295
|
+
)}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{/* Task list */}
|
|
301
|
+
<div className="flex-1 overflow-y-auto p-3">
|
|
302
|
+
{isLoading ? (
|
|
303
|
+
<div className="flex items-center justify-center py-12">
|
|
304
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
305
|
+
</div>
|
|
306
|
+
) : deletedTasks.length === 0 ? (
|
|
307
|
+
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
308
|
+
<Trash2 className="mb-3 h-12 w-12 text-muted-foreground/50" />
|
|
309
|
+
<p className="text-muted-foreground">{t.noDeletedTasks}</p>
|
|
310
|
+
<p className="mt-1 text-muted-foreground/70 text-xs">
|
|
311
|
+
{t.deletedTasksWillAppearHere}
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
314
|
+
) : (
|
|
315
|
+
<div className="space-y-2">
|
|
316
|
+
{deletedTasks.map((task: Task) => (
|
|
317
|
+
<RecycleBinTaskRow
|
|
318
|
+
key={task.id}
|
|
319
|
+
task={task}
|
|
320
|
+
listName={listMap.get(task.list_id)}
|
|
321
|
+
isSelected={selectedTasks.has(task.id)}
|
|
322
|
+
onSelect={(checked) => handleSelectTask(task.id, checked)}
|
|
323
|
+
disabled={isWorking}
|
|
324
|
+
translations={t}
|
|
275
325
|
/>
|
|
276
|
-
|
|
277
|
-
{someSelected
|
|
278
|
-
? t.selectedOfTotal
|
|
279
|
-
.replace('{selected}', String(selectedTasks.size))
|
|
280
|
-
.replace('{total}', String(deletedTasks.length))
|
|
281
|
-
: t.deletedTasksCount.replace(
|
|
282
|
-
'{count}',
|
|
283
|
-
String(deletedTasks.length)
|
|
284
|
-
)}
|
|
285
|
-
</span>
|
|
286
|
-
</div>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
{/* Task list */}
|
|
290
|
-
<div className="flex-1 overflow-y-auto p-3">
|
|
291
|
-
{isLoading ? (
|
|
292
|
-
<div className="flex items-center justify-center py-12">
|
|
293
|
-
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
294
|
-
</div>
|
|
295
|
-
) : deletedTasks.length === 0 ? (
|
|
296
|
-
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
297
|
-
<Trash2 className="mb-3 h-12 w-12 text-muted-foreground/50" />
|
|
298
|
-
<p className="text-muted-foreground">{t.noDeletedTasks}</p>
|
|
299
|
-
<p className="mt-1 text-muted-foreground/70 text-xs">
|
|
300
|
-
{t.deletedTasksWillAppearHere}
|
|
301
|
-
</p>
|
|
302
|
-
</div>
|
|
303
|
-
) : (
|
|
304
|
-
<div className="space-y-2">
|
|
305
|
-
{deletedTasks.map((task: Task) => (
|
|
306
|
-
<RecycleBinTaskRow
|
|
307
|
-
key={task.id}
|
|
308
|
-
task={task}
|
|
309
|
-
listName={listMap.get(task.list_id)}
|
|
310
|
-
isSelected={selectedTasks.has(task.id)}
|
|
311
|
-
onSelect={(checked) => handleSelectTask(task.id, checked)}
|
|
312
|
-
disabled={isWorking}
|
|
313
|
-
translations={t}
|
|
314
|
-
/>
|
|
315
|
-
))}
|
|
316
|
-
</div>
|
|
317
|
-
)}
|
|
326
|
+
))}
|
|
318
327
|
</div>
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
319
330
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
</div>
|
|
344
|
-
)}
|
|
331
|
+
{/* Action bar */}
|
|
332
|
+
{someSelected && (
|
|
333
|
+
<div className="flex items-center gap-2 border-t px-3 py-4">
|
|
334
|
+
<Button
|
|
335
|
+
variant="outline"
|
|
336
|
+
size="sm"
|
|
337
|
+
className="flex-1"
|
|
338
|
+
onClick={() => handleRestoreDialogOpenChange(true)}
|
|
339
|
+
disabled={isWorking}
|
|
340
|
+
>
|
|
341
|
+
<RotateCcw className="mr-2 h-4 w-4" />
|
|
342
|
+
{t.restore} ({selectedTasks.size})
|
|
343
|
+
</Button>
|
|
344
|
+
<Button
|
|
345
|
+
variant="destructive"
|
|
346
|
+
size="sm"
|
|
347
|
+
className="flex-1"
|
|
348
|
+
onClick={() => handleDeleteDialogOpenChange(true)}
|
|
349
|
+
disabled={isWorking}
|
|
350
|
+
>
|
|
351
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
352
|
+
{t.delete} ({selectedTasks.size})
|
|
353
|
+
</Button>
|
|
345
354
|
</div>
|
|
346
|
-
|
|
347
|
-
</
|
|
355
|
+
)}
|
|
356
|
+
</div>
|
|
348
357
|
|
|
349
358
|
{/* Restore Confirmation Dialog */}
|
|
350
359
|
<AlertDialog
|
|
@@ -433,6 +442,45 @@ export function RecycleBinPanel({
|
|
|
433
442
|
);
|
|
434
443
|
}
|
|
435
444
|
|
|
445
|
+
export function RecycleBinPanel({
|
|
446
|
+
open,
|
|
447
|
+
onOpenChange,
|
|
448
|
+
wsId,
|
|
449
|
+
boardId,
|
|
450
|
+
lists,
|
|
451
|
+
translations,
|
|
452
|
+
}: RecycleBinPanelProps) {
|
|
453
|
+
const t = {
|
|
454
|
+
recycleBin: translations?.recycleBin ?? 'Recycle Bin',
|
|
455
|
+
recycleBinDescription:
|
|
456
|
+
translations?.recycleBinDescription ??
|
|
457
|
+
'Restore or permanently delete tasks that were previously removed.',
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
462
|
+
<SheetContent side="right" className="flex w-full flex-col sm:max-w-md">
|
|
463
|
+
<SheetHeader>
|
|
464
|
+
<SheetTitle className="flex items-center gap-2">
|
|
465
|
+
<Trash2 className="h-5 w-5" />
|
|
466
|
+
{t.recycleBin}
|
|
467
|
+
</SheetTitle>
|
|
468
|
+
<SheetDescription>{t.recycleBinDescription}</SheetDescription>
|
|
469
|
+
</SheetHeader>
|
|
470
|
+
<RecycleBinContent
|
|
471
|
+
active={open}
|
|
472
|
+
boardId={boardId}
|
|
473
|
+
lists={lists}
|
|
474
|
+
onParentOpenChange={onOpenChange}
|
|
475
|
+
showHeader={false}
|
|
476
|
+
translations={translations}
|
|
477
|
+
wsId={wsId}
|
|
478
|
+
/>
|
|
479
|
+
</SheetContent>
|
|
480
|
+
</Sheet>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
436
484
|
interface RecycleBinTaskRowProps {
|
|
437
485
|
task: Task;
|
|
438
486
|
listName?: string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type SpecialTaskListPin =
|
|
2
|
+
| 'closed_tasks'
|
|
3
|
+
| 'external_tasks'
|
|
4
|
+
| 'overdue'
|
|
5
|
+
| 'upcoming';
|
|
6
|
+
|
|
7
|
+
export const SPECIAL_TASK_LIST_PIN_VALUES: readonly SpecialTaskListPin[] = [
|
|
8
|
+
'overdue',
|
|
9
|
+
'upcoming',
|
|
10
|
+
'external_tasks',
|
|
11
|
+
'closed_tasks',
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const SPECIAL_TASK_LIST_PIN_SET = new Set<string>(SPECIAL_TASK_LIST_PIN_VALUES);
|
|
15
|
+
|
|
16
|
+
export type SpecialTaskListPinState = Partial<
|
|
17
|
+
Record<SpecialTaskListPin, boolean>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
export function parseSpecialTaskListPins(
|
|
21
|
+
raw: string | null | undefined
|
|
22
|
+
): SpecialTaskListPinState {
|
|
23
|
+
if (!raw) return {};
|
|
24
|
+
|
|
25
|
+
const parseValues = (value: unknown) => {
|
|
26
|
+
if (!Array.isArray(value)) return [];
|
|
27
|
+
return value.filter((item): item is SpecialTaskListPin => {
|
|
28
|
+
return typeof item === 'string' && SPECIAL_TASK_LIST_PIN_SET.has(item);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
let values: SpecialTaskListPin[] = [];
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
values = parseValues(JSON.parse(raw));
|
|
36
|
+
} catch {
|
|
37
|
+
values = parseValues(raw.split(',').map((item) => item.trim()));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return values.reduce<SpecialTaskListPinState>((acc, pin) => {
|
|
41
|
+
acc[pin] = true;
|
|
42
|
+
return acc;
|
|
43
|
+
}, {});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function serializeSpecialTaskListPins(
|
|
47
|
+
state: SpecialTaskListPinState
|
|
48
|
+
): string | null {
|
|
49
|
+
const pins = SPECIAL_TASK_LIST_PIN_VALUES.filter((pin) => state[pin]);
|
|
50
|
+
return pins.length > 0 ? JSON.stringify(pins) : null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
4
|
+
import { KanbanSkeleton } from '../boards/boardId/kanban/rendering/kanban-skeleton';
|
|
5
|
+
|
|
6
|
+
export function TaskBoardLoadingState({
|
|
7
|
+
className,
|
|
8
|
+
root = false,
|
|
9
|
+
}: {
|
|
10
|
+
className?: string;
|
|
11
|
+
root?: boolean;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
aria-busy="true"
|
|
16
|
+
className={cn(
|
|
17
|
+
'overflow-hidden bg-transparent',
|
|
18
|
+
root
|
|
19
|
+
? '-m-4 h-[calc(100dvh+2rem)] min-h-[calc(32rem+2rem)] w-[calc(100%+2rem)] min-w-[calc(100%+2rem)]'
|
|
20
|
+
: 'h-[calc(100dvh-1rem)] min-h-[32rem] w-full',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
data-testid="task-board-loading-state"
|
|
24
|
+
>
|
|
25
|
+
<KanbanSkeleton root={root} />
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|