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:
@@ -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 ? (
|
||||
|
||||
Reference in New Issue
Block a user