feat(#248): admin tag page complete overhaul — tree panel, merge, subtree delete, new edit components #249
@@ -61,6 +61,11 @@ function handleTextSearch() {
|
||||
searchTimer = setTimeout(() => triggerSearch(), 500);
|
||||
}
|
||||
|
||||
function handleImmediateSearch() {
|
||||
clearTimeout(searchTimer);
|
||||
triggerSearch();
|
||||
}
|
||||
|
||||
// Trigger search when tags change
|
||||
let prevTagStr = untrack(() => tagNames.map((t) => t.name).join(','));
|
||||
$effect(() => {
|
||||
@@ -114,6 +119,7 @@ const showRightColumn = $derived(data.canWrite || (data.incompleteDocs?.length ?
|
||||
initialReceiverName={data.initialValues?.receiverName}
|
||||
isLoading={navigating.to !== null}
|
||||
onSearch={handleTextSearch}
|
||||
onSearchImmediate={handleImmediateSearch}
|
||||
onfocus={() => (qFocused = true)}
|
||||
onblur={() => (qFocused = false)}
|
||||
/>
|
||||
|
||||
@@ -22,6 +22,7 @@ let {
|
||||
initialReceiverName = '',
|
||||
isLoading = false,
|
||||
onSearch,
|
||||
onSearchImmediate,
|
||||
onfocus,
|
||||
onblur
|
||||
}: {
|
||||
@@ -40,6 +41,7 @@ let {
|
||||
initialReceiverName?: string;
|
||||
isLoading?: boolean;
|
||||
onSearch: () => void;
|
||||
onSearchImmediate?: () => void;
|
||||
onfocus?: () => void;
|
||||
onblur?: () => void;
|
||||
} = $props();
|
||||
@@ -162,7 +164,7 @@ $effect(() => {
|
||||
class="rounded px-2 py-0.5 text-xs font-bold tracking-widest uppercase transition-colors {tagOperator === 'AND' ? 'bg-primary text-primary-fg' : 'bg-muted text-ink-2 hover:bg-line'}"
|
||||
onclick={() => {
|
||||
tagOperator = 'AND';
|
||||
onSearch();
|
||||
(onSearchImmediate ?? onSearch)();
|
||||
}}>AND</button
|
||||
>
|
||||
<button
|
||||
@@ -170,7 +172,7 @@ $effect(() => {
|
||||
class="rounded px-2 py-0.5 text-xs font-bold tracking-widest uppercase transition-colors {tagOperator === 'OR' ? 'bg-primary text-primary-fg' : 'bg-muted text-ink-2 hover:bg-line'}"
|
||||
onclick={() => {
|
||||
tagOperator = 'OR';
|
||||
onSearch();
|
||||
(onSearchImmediate ?? onSearch)();
|
||||
}}>OR</button
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -103,6 +103,29 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => {
|
||||
await expect.poll(() => onSearch.mock.calls.length).toBeGreaterThan(0);
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('calls onSearchImmediate instead of onSearch when operator is toggled and onSearchImmediate is provided', async () => {
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue([]) })
|
||||
);
|
||||
const onSearch = vi.fn();
|
||||
const onSearchImmediate = vi.fn();
|
||||
render(SearchFilterBar, {
|
||||
...defaultProps,
|
||||
onSearch,
|
||||
onSearchImmediate,
|
||||
sort: 'DATE',
|
||||
dir: 'desc',
|
||||
tagNames: [{ name: 'Tag1' }, { name: 'Tag2' }]
|
||||
});
|
||||
await openAdvanced();
|
||||
const toggle = page.getByTestId('and-or-toggle');
|
||||
await toggle.getByRole('button', { name: 'OR' }).click();
|
||||
await expect.poll(() => onSearchImmediate.mock.calls.length).toBeGreaterThan(0);
|
||||
expect(onSearch).not.toHaveBeenCalled();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SearchFilterBar – tagQ live filter', () => {
|
||||
|
||||
Reference in New Issue
Block a user