feat(documents): dynamic type-filter chips + move-to-folder row action

Type-filter chip cloud sourced from the documentTypes seen in the
current result set, replacing the static dropdown over the whole
DOCUMENT_TYPES enum. New "Move to folder…" entry on the per-row
action menu (gated on documents.manage_folders) opens the
MoveToFolderDialog Combobox.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-10 12:21:14 +02:00
parent 4556a03b8b
commit 433ab3bf75
2 changed files with 109 additions and 63 deletions

View File

@@ -7,13 +7,7 @@ import { ChevronDown, ChevronRight, FileText, Plus } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { EmptyState } from '@/components/ui/empty-state';
@@ -96,7 +90,7 @@ interface DocumentsHubProps {
export function DocumentsHub({ portSlug, initialTab = 'all' }: DocumentsHubProps) {
const [tab, setTab] = useState<DocumentsHubTab>(initialTab);
const [search, setSearch] = useState('');
const [typeFilter, setTypeFilter] = useState<string>('all');
const [typeFilter, setTypeFilter] = useState<string | undefined>(undefined);
// undefined = "All documents" (no folder filter), null = root only,
// string = a specific folder id.
const [selectedFolderId, setSelectedFolderId] = useState<string | null | undefined>(undefined);
@@ -106,7 +100,7 @@ export function DocumentsHub({ portSlug, initialTab = 'all' }: DocumentsHubProps
const params = new URLSearchParams();
params.set('tab', tab);
if (search) params.set('search', search);
if (typeFilter && typeFilter !== 'all') params.set('documentType', typeFilter);
if (typeFilter) params.set('documentType', typeFilter);
if (selectedFolderId !== undefined) {
params.set('folderId', selectedFolderId ?? '');
}
@@ -290,19 +284,39 @@ export function DocumentsHub({ portSlug, initialTab = 'all' }: DocumentsHubProps
onChange={(e) => setSearch(e.target.value)}
className="max-w-xs h-9"
/>
<Select value={typeFilter} onValueChange={setTypeFilter}>
<SelectTrigger className="w-44">
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All types</SelectItem>
{Object.entries(TYPE_LABELS).map(([k, v]) => (
<SelectItem key={k} value={k}>
{v}
</SelectItem>
))}
</SelectContent>
</Select>
{(() => {
const seenTypes = Array.from(
new Set(documents.map((d) => d.documentType)),
).sort();
if (seenTypes.length === 0) return null;
return (
<div className="flex flex-wrap gap-1.5">
<button
type="button"
className={cn(
'rounded-full border px-2.5 py-0.5 text-xs',
typeFilter === undefined ? 'bg-foreground text-background' : 'hover:bg-accent',
)}
onClick={() => setTypeFilter(undefined)}
>
All types
</button>
{seenTypes.map((t) => (
<button
type="button"
key={t}
className={cn(
'rounded-full border px-2.5 py-0.5 text-xs',
typeFilter === t ? 'bg-foreground text-background' : 'hover:bg-accent',
)}
onClick={() => setTypeFilter(t)}
>
{t}
</button>
))}
</div>
);
})()}
</div>
{isLoading ? (