-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFilterBar.tsx
More file actions
88 lines (77 loc) · 2.74 KB
/
Copy pathFilterBar.tsx
File metadata and controls
88 lines (77 loc) · 2.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useState, useTransition } from 'react';
import { BOARD_COLUMN_STATUSES, PRIORITY_DISPLAY_ORDER } from '@/constants/task';
import { messages } from '@/i18n';
import { Select } from './ui/Select';
const TAG_DEBOUNCE_MS = 400;
const STATUS_OPTIONS = [
{ value: '', label: messages.filter.allStatuses },
...BOARD_COLUMN_STATUSES.map((s) => ({
value: s as string,
label: messages.columns[s],
})),
];
const PRIORITY_OPTIONS = [
{ value: '', label: messages.filter.allPriorities },
...PRIORITY_DISPLAY_ORDER.map((p) => ({
value: String(p),
label: messages.priorities[p],
})),
];
/**
* Bonus: filter controls that write to the URL search params and let Next.js
* re-run the server component (a fresh SSR fetch) on navigation — the URL is
* the single source of truth, so filters are shareable and survive reload.
*/
export function FilterBar() {
const router = useRouter();
const params = useSearchParams();
const [isPending, startTransition] = useTransition();
const setParam = useCallback(
(key: string, value: string) => {
const next = new URLSearchParams(params.toString());
if (value) next.set(key, value);
else next.delete(key);
const qs = next.toString();
// Transition keeps the current board visible (no loading-skeleton flash)
// while the server refetches for the new filters.
startTransition(() => router.push(qs ? `/?${qs}` : '/'));
},
[params, router],
);
// Tag filter: controlled input with debounced live search — no Enter needed.
const currentTag = params.get('tag') ?? '';
const [tagInput, setTagInput] = useState(currentTag);
useEffect(() => {
if (tagInput.trim() === currentTag) return; // already in sync
const id = setTimeout(() => setParam('tag', tagInput.trim()), TAG_DEBOUNCE_MS);
return () => clearTimeout(id);
}, [tagInput, currentTag, setParam]);
return (
<div className="filter-bar" data-pending={isPending || undefined}>
<Select
value={params.get('status') ?? ''}
options={STATUS_OPTIONS}
onChange={(v) => setParam('status', v)}
ariaLabel={messages.filter.ariaStatus}
width="auto"
/>
<Select
value={params.get('priority') ?? ''}
options={PRIORITY_OPTIONS}
onChange={(v) => setParam('priority', v)}
ariaLabel={messages.filter.ariaPriority}
width="auto"
/>
<input
className="input filter-tag"
type="search"
placeholder={messages.filter.tagPlaceholder}
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
aria-label={messages.filter.ariaTag}
/>
</div>
);
}