@sascha384/tic 1.34.0 → 1.35.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/.claude-plugin/plugin.json +1 -1
- package/dist/backends/local/config.d.ts +15 -0
- package/dist/backends/local/config.js.map +1 -1
- package/dist/commands.d.ts +2 -0
- package/dist/commands.js +39 -0
- package/dist/commands.js.map +1 -1
- package/dist/components/Header.js +6 -0
- package/dist/components/Header.js.map +1 -1
- package/dist/components/HelpScreen.js +3 -0
- package/dist/components/HelpScreen.js.map +1 -1
- package/dist/components/OverlayPanel.js +6 -0
- package/dist/components/OverlayPanel.js.map +1 -1
- package/dist/components/StatusScreen.js +28 -3
- package/dist/components/StatusScreen.js.map +1 -1
- package/dist/components/WorkItemList.js +271 -5
- package/dist/components/WorkItemList.js.map +1 -1
- package/dist/filters.d.ts +28 -0
- package/dist/filters.js +47 -0
- package/dist/filters.js.map +1 -0
- package/dist/implement.js +4 -56
- package/dist/implement.js.map +1 -1
- package/dist/stores/filterStore.d.ts +11 -0
- package/dist/stores/filterStore.js +33 -0
- package/dist/stores/filterStore.js.map +1 -0
- package/dist/stores/listViewStore.d.ts +1 -0
- package/dist/stores/listViewStore.js +1 -0
- package/dist/stores/listViewStore.js.map +1 -1
- package/dist/stores/uiStore.d.ts +10 -0
- package/dist/stores/uiStore.js.map +1 -1
- package/dist/sync/SyncManager.d.ts +2 -0
- package/dist/sync/SyncManager.js +52 -0
- package/dist/sync/SyncManager.js.map +1 -1
- package/dist/sync/types.d.ts +15 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
3
3
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
4
4
|
import { navigationStore, useNavigationStore, } from '../stores/navigationStore.js';
|
|
5
5
|
import { listViewStore, useListViewStore } from '../stores/listViewStore.js';
|
|
@@ -20,6 +20,9 @@ import { DetailPanel } from './DetailPanel.js';
|
|
|
20
20
|
import { undoStore } from '../stores/undoStore.js';
|
|
21
21
|
import { recentCommandsStore, useRecentCommandsStore, } from '../stores/recentCommandsStore.js';
|
|
22
22
|
import { isSoftDeleteBackend } from '../backends/types.js';
|
|
23
|
+
import { filterStore, useFilterStore } from '../stores/filterStore.js';
|
|
24
|
+
import { applyFilters, countActiveFilters, summarizeFilters, } from '../filters.js';
|
|
25
|
+
const EMPTY_VIEWS = [];
|
|
23
26
|
export function getTargetIds(markedIds, cursorItem) {
|
|
24
27
|
if (markedIds.size > 0) {
|
|
25
28
|
return [...markedIds];
|
|
@@ -77,6 +80,8 @@ export function WorkItemList() {
|
|
|
77
80
|
const branchCommand = useConfigStore((s) => s.config.branchCommand);
|
|
78
81
|
const copyToClipboard = useConfigStore((s) => s.config.copyToClipboard);
|
|
79
82
|
const showDetailPanel = useConfigStore((s) => s.config.showDetailPanel ?? true);
|
|
83
|
+
const savedViews = useConfigStore((s) => s.config.views ?? EMPTY_VIEWS);
|
|
84
|
+
const defaultView = useConfigStore((s) => s.config.defaultView);
|
|
80
85
|
const { exit } = useApp();
|
|
81
86
|
// Store selectors for persistent list view state
|
|
82
87
|
const { cursor, markedIds, expandedIds, rangeAnchor, sortStack } = useListViewStore(useShallow((s) => ({
|
|
@@ -86,6 +91,11 @@ export function WorkItemList() {
|
|
|
86
91
|
rangeAnchor: s.rangeAnchor,
|
|
87
92
|
sortStack: s.sortStack,
|
|
88
93
|
})));
|
|
94
|
+
const { activeFilters, activeViewName } = useFilterStore(useShallow((s) => ({
|
|
95
|
+
activeFilters: s.activeFilters,
|
|
96
|
+
activeViewName: s.activeViewName,
|
|
97
|
+
})));
|
|
98
|
+
const filterCount = useMemo(() => countActiveFilters(activeFilters), [activeFilters]);
|
|
89
99
|
const { setCursor, toggleExpanded, toggleMarked, clearMarked, setMarkedIds, setRangeAnchor, clampCursor, removeDeletedItem, toggleSortColumn, clearSort, } = listViewStore.getState();
|
|
90
100
|
// Local state for inputs and templates
|
|
91
101
|
const [allSearchItems, setAllSearchItems] = useState([]);
|
|
@@ -121,6 +131,22 @@ export function WorkItemList() {
|
|
|
121
131
|
void backend.listTemplates().then(setTemplates);
|
|
122
132
|
}
|
|
123
133
|
}, [backend, capabilities.templates]);
|
|
134
|
+
// Load default view on startup
|
|
135
|
+
const defaultViewLoadedRef = useRef(false);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (defaultViewLoadedRef.current)
|
|
138
|
+
return;
|
|
139
|
+
if (!defaultView)
|
|
140
|
+
return;
|
|
141
|
+
const view = savedViews.find((v) => v.name === defaultView);
|
|
142
|
+
if (view) {
|
|
143
|
+
defaultViewLoadedRef.current = true;
|
|
144
|
+
filterStore.getState().loadView(view);
|
|
145
|
+
if (view.sort) {
|
|
146
|
+
listViewStore.getState().setSortStack(view.sort);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, [savedViews, defaultView]);
|
|
124
150
|
const queueStore = useMemo(() => {
|
|
125
151
|
if (!syncManager)
|
|
126
152
|
return null;
|
|
@@ -155,13 +181,29 @@ export function WorkItemList() {
|
|
|
155
181
|
setActiveType(defaultType && types.includes(defaultType) ? defaultType : types[0]);
|
|
156
182
|
}
|
|
157
183
|
}, [activeType, types, setActiveType, defaultType]);
|
|
158
|
-
|
|
184
|
+
// Apply view filters to all items (used for children in tree view)
|
|
185
|
+
const viewFilteredItems = useMemo(() => applyFilters(allItems, activeFilters), [allItems, activeFilters]);
|
|
186
|
+
const unfilteredCount = useMemo(() => allItems.filter((item) => item.type === activeType).length, [allItems, activeType]);
|
|
187
|
+
const items = useMemo(() => {
|
|
188
|
+
const hasTypeFilter = (activeFilters.types?.length ?? 0) > 0;
|
|
189
|
+
let filtered = hasTypeFilter
|
|
190
|
+
? allItems
|
|
191
|
+
: allItems.filter((item) => item.type === activeType);
|
|
192
|
+
filtered = applyFilters(filtered, activeFilters);
|
|
193
|
+
return filtered;
|
|
194
|
+
}, [allItems, activeType, activeFilters]);
|
|
159
195
|
const fullTree = useMemo(() => {
|
|
160
196
|
const tree = capabilities.relationships
|
|
161
|
-
? buildTree(items,
|
|
197
|
+
? buildTree(items, viewFilteredItems, activeType ?? '')
|
|
162
198
|
: buildTree(items, items, activeType ?? '');
|
|
163
199
|
return sortTree(tree, sortStack);
|
|
164
|
-
}, [
|
|
200
|
+
}, [
|
|
201
|
+
items,
|
|
202
|
+
viewFilteredItems,
|
|
203
|
+
activeType,
|
|
204
|
+
capabilities.relationships,
|
|
205
|
+
sortStack,
|
|
206
|
+
]);
|
|
165
207
|
const parentSuggestions = useMemo(() => allItems.map((item) => `${item.id} - ${item.title}`), [allItems]);
|
|
166
208
|
// Collapse state: set of item IDs that are collapsed (collapsed by default)
|
|
167
209
|
// Track explicitly expanded items (inverse of collapsed).
|
|
@@ -434,6 +476,8 @@ export function WorkItemList() {
|
|
|
434
476
|
try {
|
|
435
477
|
const itemUrl = backend?.getItemUrl(item.id) || '';
|
|
436
478
|
const result = beginImplementation(item, comments, { branchMode, branchCommand, copyToClipboard }, process.cwd(), { itemUrl });
|
|
479
|
+
// Restore raw mode after interactive shell changed terminal settings
|
|
480
|
+
process.stdin.setRawMode?.(true);
|
|
437
481
|
let msg = result.resumed
|
|
438
482
|
? `Resumed work on #${item.id}`
|
|
439
483
|
: `Started work on #${item.id}`;
|
|
@@ -443,6 +487,7 @@ export function WorkItemList() {
|
|
|
443
487
|
setWarning(msg);
|
|
444
488
|
}
|
|
445
489
|
catch (e) {
|
|
490
|
+
process.stdin.setRawMode?.(true);
|
|
446
491
|
setWarning(e instanceof Error ? e.message : 'Failed to start implementation');
|
|
447
492
|
}
|
|
448
493
|
refreshData();
|
|
@@ -453,6 +498,16 @@ export function WorkItemList() {
|
|
|
453
498
|
if (input === 'O') {
|
|
454
499
|
openOverlay({ type: 'sort-picker' });
|
|
455
500
|
}
|
|
501
|
+
if (input === 'F') {
|
|
502
|
+
openOverlay({ type: 'filter-picker' });
|
|
503
|
+
}
|
|
504
|
+
if (input === 'V') {
|
|
505
|
+
openOverlay({ type: 'view-picker' });
|
|
506
|
+
}
|
|
507
|
+
if (input === 'X' && filterCount > 0) {
|
|
508
|
+
filterStore.getState().clearFilters();
|
|
509
|
+
setToast('Filters cleared');
|
|
510
|
+
}
|
|
456
511
|
if (input === 's' && treeItems.length > 0) {
|
|
457
512
|
const targetIds = getTargetIds(markedIds, treeItems[cursor]?.item);
|
|
458
513
|
if (targetIds.length > 0) {
|
|
@@ -551,6 +606,8 @@ export function WorkItemList() {
|
|
|
551
606
|
activeType,
|
|
552
607
|
hasSyncManager: syncManager !== null,
|
|
553
608
|
gitAvailable,
|
|
609
|
+
hasActiveFilters: filterCount > 0,
|
|
610
|
+
hasSavedViews: savedViews.length > 0,
|
|
554
611
|
};
|
|
555
612
|
const paletteCommands = useMemo(() => getVisibleCommands(commandContext), [
|
|
556
613
|
commandContext.markedCount,
|
|
@@ -618,6 +675,93 @@ export function WorkItemList() {
|
|
|
618
675
|
}
|
|
619
676
|
return items;
|
|
620
677
|
}, [sortStack, capabilities.fields.priority, capabilities.fields.assignee]);
|
|
678
|
+
const filterPickerItems = useMemo(() => {
|
|
679
|
+
const items = [];
|
|
680
|
+
if (filterCount > 0) {
|
|
681
|
+
items.push({
|
|
682
|
+
id: '__clear__',
|
|
683
|
+
label: 'Clear all filters',
|
|
684
|
+
value: '__clear__',
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
for (const s of statuses) {
|
|
688
|
+
items.push({
|
|
689
|
+
id: `status-${s}`,
|
|
690
|
+
label: s,
|
|
691
|
+
value: s,
|
|
692
|
+
category: 'Status',
|
|
693
|
+
selected: activeFilters.statuses?.includes(s),
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
for (const p of ['critical', 'high', 'medium', 'low']) {
|
|
697
|
+
if (!capabilities.fields.priority)
|
|
698
|
+
continue;
|
|
699
|
+
items.push({
|
|
700
|
+
id: `priority-${p}`,
|
|
701
|
+
label: p.charAt(0).toUpperCase() + p.slice(1),
|
|
702
|
+
value: p,
|
|
703
|
+
category: 'Priority',
|
|
704
|
+
selected: activeFilters.priorities?.includes(p),
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
for (const t of types) {
|
|
708
|
+
items.push({
|
|
709
|
+
id: `type-${t}`,
|
|
710
|
+
label: t.charAt(0).toUpperCase() + t.slice(1),
|
|
711
|
+
value: t,
|
|
712
|
+
category: 'Type',
|
|
713
|
+
selected: activeFilters.types?.includes(t),
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
for (const a of assignees) {
|
|
717
|
+
if (!capabilities.fields.assignee)
|
|
718
|
+
continue;
|
|
719
|
+
items.push({
|
|
720
|
+
id: `assignee-${a}`,
|
|
721
|
+
label: a,
|
|
722
|
+
value: a,
|
|
723
|
+
category: 'Assignee',
|
|
724
|
+
selected: activeFilters.assignees?.includes(a),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
for (const l of labelSuggestions) {
|
|
728
|
+
if (!capabilities.fields.labels)
|
|
729
|
+
continue;
|
|
730
|
+
items.push({
|
|
731
|
+
id: `label-${l}`,
|
|
732
|
+
label: l,
|
|
733
|
+
value: l,
|
|
734
|
+
category: 'Labels',
|
|
735
|
+
selected: activeFilters.labels?.includes(l),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
return items;
|
|
739
|
+
}, [
|
|
740
|
+
statuses,
|
|
741
|
+
types,
|
|
742
|
+
assignees,
|
|
743
|
+
labelSuggestions,
|
|
744
|
+
capabilities,
|
|
745
|
+
activeFilters,
|
|
746
|
+
filterCount,
|
|
747
|
+
]);
|
|
748
|
+
const viewPickerItems = useMemo(() => {
|
|
749
|
+
const noFilterLabel = !defaultView ? 'No filters (default)' : 'No filters';
|
|
750
|
+
return [
|
|
751
|
+
{
|
|
752
|
+
id: '__no-filters__',
|
|
753
|
+
label: noFilterLabel,
|
|
754
|
+
value: '__no-filters__',
|
|
755
|
+
hint: !activeViewName && filterCount === 0 ? '●' : '',
|
|
756
|
+
},
|
|
757
|
+
...savedViews.map((v) => ({
|
|
758
|
+
id: v.name,
|
|
759
|
+
label: v.name + (v.name === defaultView ? ' (default)' : ''),
|
|
760
|
+
value: v.name,
|
|
761
|
+
hint: summarizeFilters(v.filters) + (v.name === activeViewName ? ' ●' : ''),
|
|
762
|
+
})),
|
|
763
|
+
];
|
|
764
|
+
}, [savedViews, defaultView, activeViewName, filterCount]);
|
|
621
765
|
const handleCommandSelect = (command) => {
|
|
622
766
|
closeOverlay();
|
|
623
767
|
recentCommandsStore.getState().addRecent(command.id);
|
|
@@ -655,6 +799,7 @@ export function WorkItemList() {
|
|
|
655
799
|
try {
|
|
656
800
|
const itemUrl = backend?.getItemUrl(item.id) || '';
|
|
657
801
|
const result = beginImplementation(item, comments, { branchMode, branchCommand, copyToClipboard }, process.cwd(), { itemUrl });
|
|
802
|
+
process.stdin.setRawMode?.(true);
|
|
658
803
|
let msg = result.resumed
|
|
659
804
|
? `Resumed work on #${item.id}`
|
|
660
805
|
: `Started work on #${item.id}`;
|
|
@@ -664,6 +809,7 @@ export function WorkItemList() {
|
|
|
664
809
|
setWarning(msg);
|
|
665
810
|
}
|
|
666
811
|
catch (e) {
|
|
812
|
+
process.stdin.setRawMode?.(true);
|
|
667
813
|
setWarning(e instanceof Error ? e.message : 'Failed to start implementation');
|
|
668
814
|
}
|
|
669
815
|
refreshData();
|
|
@@ -733,6 +879,25 @@ export function WorkItemList() {
|
|
|
733
879
|
case 'sort':
|
|
734
880
|
openOverlay({ type: 'sort-picker' });
|
|
735
881
|
break;
|
|
882
|
+
case 'filter':
|
|
883
|
+
openOverlay({ type: 'filter-picker' });
|
|
884
|
+
break;
|
|
885
|
+
case 'clear-filters':
|
|
886
|
+
filterStore.getState().clearFilters();
|
|
887
|
+
setToast('Filters cleared');
|
|
888
|
+
break;
|
|
889
|
+
case 'load-view':
|
|
890
|
+
openOverlay({ type: 'view-picker' });
|
|
891
|
+
break;
|
|
892
|
+
case 'save-view':
|
|
893
|
+
openOverlay({ type: 'save-view-input' });
|
|
894
|
+
break;
|
|
895
|
+
case 'delete-view':
|
|
896
|
+
openOverlay({ type: 'delete-view-picker' });
|
|
897
|
+
break;
|
|
898
|
+
case 'set-default-view':
|
|
899
|
+
openOverlay({ type: 'set-default-view' });
|
|
900
|
+
break;
|
|
736
901
|
case 'quit':
|
|
737
902
|
exit();
|
|
738
903
|
break;
|
|
@@ -785,7 +950,7 @@ export function WorkItemList() {
|
|
|
785
950
|
const positionText = treeItems.length > viewport.maxVisible
|
|
786
951
|
? `${cursor + 1}/${treeItems.length}`
|
|
787
952
|
: '';
|
|
788
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { bold: true, color: "cyan", children: [typeLabel, " \u2014 ", iteration] }), _jsx(Text, { dimColor: true, children: ` (${items.length} items)` }), markedCount > 0 && (_jsx(Text, { color: "magenta", children: ` ● ${markedCount} marked` }))] }) }), _jsx(TableLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds, terminalWidth: terminalWidth, sortStack: sortStack }), treeItems.length === 0 && !loading && initError && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Failed to connect to backend:" }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "red", children: initError }) }), _jsx(Text, { dimColor: true, children: "Press , for settings or q to quit." })] })), treeItems.length === 0 && !loading && !initError && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["No ", activeType ?? 'item', "s in this iteration. Press c to create, / to search all."] }) })), loading && treeItems.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Loading..." }) })), showDetailPanel && treeItems.length > 0 && treeItems[cursor] && (_jsx(DetailPanel, { item: treeItems[cursor].item, terminalWidth: terminalWidth, showFullDescription: showFullDescription, descriptionScrollOffset: descriptionScrollOffset, maxDescriptionHeight: maxDescriptionHeight })), _jsx(Box, { marginTop: 1, children: showFullDescription ? (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 scroll space/esc close" }), positionText && _jsxs(Text, { dimColor: true, children: [" ", positionText] })] })) : activeOverlay?.type === 'search' ? ((() => {
|
|
953
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { bold: true, color: "cyan", children: [typeLabel, " \u2014 ", iteration] }), _jsx(Text, { dimColor: true, children: ` (${filterCount > 0 ? `${items.length}/${unfilteredCount}` : items.length} item${unfilteredCount === 1 ? '' : 's'})` }), markedCount > 0 && (_jsx(Text, { color: "magenta", children: ` ● ${markedCount} marked` })), filterCount > 0 && (_jsx(Text, { color: "yellow", children: ` [${filterCount} filter${filterCount === 1 ? '' : 's'}${activeViewName ? `: ${activeViewName}` : ''}]` }))] }) }), _jsx(TableLayout, { treeItems: visibleTreeItems, cursor: viewport.visibleCursor, capabilities: capabilities, collapsedIds: collapsedIds, markedIds: markedIds, terminalWidth: terminalWidth, sortStack: sortStack }), treeItems.length === 0 && !loading && initError && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "red", children: "Failed to connect to backend:" }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "red", children: initError }) }), _jsx(Text, { dimColor: true, children: "Press , for settings or q to quit." })] })), treeItems.length === 0 && !loading && !initError && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["No ", activeType ?? 'item', "s in this iteration. Press c to create, / to search all."] }) })), loading && treeItems.length === 0 && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Loading..." }) })), showDetailPanel && treeItems.length > 0 && treeItems[cursor] && (_jsx(Box, { marginTop: 1, children: _jsx(DetailPanel, { item: treeItems[cursor].item, terminalWidth: terminalWidth, showFullDescription: showFullDescription, descriptionScrollOffset: descriptionScrollOffset, maxDescriptionHeight: maxDescriptionHeight }) })), _jsx(Box, { marginTop: 1, children: showFullDescription ? (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "\u2191\u2193 scroll space/esc close" }), positionText && _jsxs(Text, { dimColor: true, children: [" ", positionText] })] })) : activeOverlay?.type === 'search' ? ((() => {
|
|
789
954
|
const searchItems = allSearchItems.map((item) => ({
|
|
790
955
|
id: item.id,
|
|
791
956
|
label: `#${item.id} ${item.title}`,
|
|
@@ -1099,6 +1264,107 @@ export function WorkItemList() {
|
|
|
1099
1264
|
else {
|
|
1100
1265
|
toggleSortColumn(item.value);
|
|
1101
1266
|
}
|
|
1267
|
+
}, onCancel: () => closeOverlay() })) : activeOverlay?.type === 'filter-picker' ? ((() => {
|
|
1268
|
+
const handleFilterConfirm = (selected) => {
|
|
1269
|
+
const newFilters = {};
|
|
1270
|
+
for (const item of selected) {
|
|
1271
|
+
const cat = item.category;
|
|
1272
|
+
if (cat === 'Status') {
|
|
1273
|
+
(newFilters.statuses ??= []).push(item.value);
|
|
1274
|
+
}
|
|
1275
|
+
else if (cat === 'Priority') {
|
|
1276
|
+
(newFilters.priorities ??= []).push(item.value);
|
|
1277
|
+
}
|
|
1278
|
+
else if (cat === 'Type') {
|
|
1279
|
+
(newFilters.types ??= []).push(item.value);
|
|
1280
|
+
}
|
|
1281
|
+
else if (cat === 'Assignee') {
|
|
1282
|
+
(newFilters.assignees ??= []).push(item.value);
|
|
1283
|
+
}
|
|
1284
|
+
else if (cat === 'Labels') {
|
|
1285
|
+
(newFilters.labels ??= []).push(item.value);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
filterStore.getState().setFilters(newFilters);
|
|
1289
|
+
closeOverlay();
|
|
1290
|
+
const count = countActiveFilters(newFilters);
|
|
1291
|
+
if (count > 0) {
|
|
1292
|
+
setToast(`${count} filter${count === 1 ? '' : 's'} applied`);
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
setToast('Filters cleared');
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
const handleFilterSelect = (item) => {
|
|
1299
|
+
if (item.value === '__clear__') {
|
|
1300
|
+
filterStore.getState().clearFilters();
|
|
1301
|
+
closeOverlay();
|
|
1302
|
+
setToast('Filters cleared');
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
return (_jsx(OverlayPanel, { title: filterCount > 0 ? `Filter [${filterCount} active]` : 'Filter', items: filterPickerItems, multiSelect: true, onSelect: handleFilterSelect, onConfirm: handleFilterConfirm, onCancel: () => closeOverlay(), placeholder: "Type to filter...", footer: "space toggle enter confirm esc cancel" }));
|
|
1306
|
+
})()) : activeOverlay?.type === 'view-picker' ? (_jsx(OverlayPanel, { title: "Load View", items: viewPickerItems, onSelect: (item) => {
|
|
1307
|
+
if (item.value === '__no-filters__') {
|
|
1308
|
+
filterStore.getState().clearFilters();
|
|
1309
|
+
listViewStore.getState().setSortStack([]);
|
|
1310
|
+
closeOverlay();
|
|
1311
|
+
setToast('Filters cleared');
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const view = savedViews.find((v) => v.name === item.value);
|
|
1315
|
+
if (view) {
|
|
1316
|
+
filterStore.getState().loadView(view);
|
|
1317
|
+
if (view.sort) {
|
|
1318
|
+
listViewStore
|
|
1319
|
+
.getState()
|
|
1320
|
+
.setSortStack(view.sort);
|
|
1321
|
+
}
|
|
1322
|
+
closeOverlay();
|
|
1323
|
+
setToast(`View "${view.name}" loaded`);
|
|
1324
|
+
}
|
|
1325
|
+
}, onCancel: () => closeOverlay(), footer: savedViews.length === 0
|
|
1326
|
+
? 'Tip: press F to filter, then :save-view to save | enter select esc cancel'
|
|
1327
|
+
: 'enter select esc cancel' })) : activeOverlay?.type === 'save-view-input' ? (_jsx(OverlayPanel, { title: "Save View", items: [], allowFreeform: true, onSelect: () => { }, onSubmitFreeform: (name) => {
|
|
1328
|
+
if (!name.trim()) {
|
|
1329
|
+
closeOverlay();
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const newView = {
|
|
1333
|
+
name: name.trim(),
|
|
1334
|
+
filters: { ...activeFilters },
|
|
1335
|
+
...(sortStack.length > 0 ? { sort: [...sortStack] } : {}),
|
|
1336
|
+
};
|
|
1337
|
+
const existing = savedViews.filter((v) => v.name !== name.trim());
|
|
1338
|
+
void configStore.getState().update({
|
|
1339
|
+
views: [...existing, newView],
|
|
1340
|
+
});
|
|
1341
|
+
filterStore.setState({ activeViewName: name.trim() });
|
|
1342
|
+
closeOverlay();
|
|
1343
|
+
setToast(`View "${name.trim()}" saved`);
|
|
1344
|
+
}, onCancel: () => closeOverlay(), placeholder: "Enter view name...", emptyMessage: "Type a name and press enter" })) : activeOverlay?.type === 'delete-view-picker' ? (_jsx(OverlayPanel, { title: "Delete View", items: viewPickerItems.filter((i) => i.id !== '__no-filters__'), onSelect: (item) => {
|
|
1345
|
+
const remaining = savedViews.filter((v) => v.name !== item.value);
|
|
1346
|
+
void configStore.getState().update({
|
|
1347
|
+
views: remaining,
|
|
1348
|
+
...(defaultView === item.value
|
|
1349
|
+
? { defaultView: undefined }
|
|
1350
|
+
: {}),
|
|
1351
|
+
});
|
|
1352
|
+
if (activeViewName === item.value) {
|
|
1353
|
+
filterStore.setState({ activeViewName: null });
|
|
1354
|
+
}
|
|
1355
|
+
closeOverlay();
|
|
1356
|
+
setToast(`View "${item.value}" deleted`);
|
|
1357
|
+
}, onCancel: () => closeOverlay() })) : activeOverlay?.type === 'set-default-view' ? (_jsx(OverlayPanel, { title: "Set Default View", items: viewPickerItems, onSelect: (item) => {
|
|
1358
|
+
if (item.value === '__no-filters__') {
|
|
1359
|
+
void configStore.getState().update({ defaultView: undefined });
|
|
1360
|
+
closeOverlay();
|
|
1361
|
+
setToast('Default view cleared');
|
|
1362
|
+
}
|
|
1363
|
+
else {
|
|
1364
|
+
void configStore.getState().update({ defaultView: item.value });
|
|
1365
|
+
closeOverlay();
|
|
1366
|
+
setToast(`View "${item.value}" set as default`);
|
|
1367
|
+
}
|
|
1102
1368
|
}, onCancel: () => closeOverlay() })) : activeOverlay?.type === 'delete-confirm' ? (_jsx(OverlayPanel, { title: `Delete ${activeOverlay.targetIds.length} item${activeOverlay.targetIds.length > 1 ? 's' : ''}?`, items: [
|
|
1103
1369
|
{ id: 'yes', label: 'Yes, delete', value: 'yes' },
|
|
1104
1370
|
{ id: 'no', label: 'Cancel', value: 'no' },
|