feat: SmartSuite-style SortPanel with multi-field priority sorting, all contact fields

This commit is contained in:
Agent Zero
2026-07-28 10:26:22 +02:00
parent e95875464b
commit 11ffffcb44
2 changed files with 445 additions and 19 deletions
+17 -19
View File
@@ -21,6 +21,7 @@ import { TagSelector } from '@/components/tags/TagSelector';
import type { Tag } from '@/api/tags';
import { ArrowDownAZ, ArrowUpZA, Bookmark, ChevronLeft, ExternalLink, LayoutGrid, List, Plus, Printer, Table2 } from 'lucide-react';
import { FilterPanel, applyFilters, emptyFilterState, type FilterState } from '@/components/contacts/FilterPanel';
import { SortPanel, applySorting, emptySortState, type SortState } from '@/components/contacts/SortPanel';
import {
useUnifiedContacts,
useUnifiedContact,
@@ -44,6 +45,7 @@ export function ContactsListPage() {
const [savedFiltersOpen, setSavedFiltersOpen] = useState(false);
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
const [filterState, setFilterState] = useState<FilterState>(emptyFilterState);
const [sortState, setSortState] = useState<SortState>(emptySortState);
const openWindow = useWindowStore((s) => s.openWindow);
// Debounce search
@@ -113,8 +115,10 @@ export function ContactsListPage() {
}
// Apply FilterPanel conditions
result = applyFilters(result, filterState);
// Apply SortPanel sorting
result = applySorting(result, sortState);
return result;
}, [contacts, tagFilter, filterState]);
}, [contacts, tagFilter, filterState, sortState]);
// Handle folder selection
const handleSelectFilter = useCallback((filter: ContactFilter) => {
@@ -232,26 +236,20 @@ export function ContactsListPage() {
),
onClick: () => {},
},
// Sort dropdown
// SortPanel — SmartSuite-style multi-field sort
{
id: 'sort-by',
id: 'sort-panel',
plugin: 'contacts',
label: t('common.sort', 'Sortieren'),
type: 'dropdown' as const,
label: 'Sortieren',
type: 'custom' as const,
group: 'sort',
icon: <ArrowDownAZ className="w-3.5 h-3.5" strokeWidth={2} />,
menuWidth: '200px',
menuOptions: [
...sortOptions.map((opt) => ({
value: `sort-${opt.value}`, label: opt.label, section: t('common.sortBy', 'Sortieren nach'),
active: sortBy === opt.value, onClick: () => { setSortBy(opt.value); setPage(1); },
})),
{ value: 'sep-order', label: '', separator: true, onClick: () => {} },
{ value: 'sort-asc', label: t('common.sortAsc', 'Aufsteigend'), section: t('common.sortOrder', 'Reihenfolge'),
icon: <ArrowDownAZ className="w-3.5 h-3.5" strokeWidth={2} />, active: sortOrder === 'asc', onClick: () => handleSortChange(sortBy, 'asc') },
{ value: 'sort-desc', label: t('common.sortDesc', 'Absteigend'),
icon: <ArrowUpZA className="w-3.5 h-3.5" strokeWidth={2} />, active: sortOrder === 'desc', onClick: () => handleSortChange(sortBy, 'desc') },
],
customComponent: (
<SortPanel
sortState={sortState}
onSortChange={setSortState}
contactType={contactType}
/>
),
onClick: () => {},
},
// View mode dropdown (list / table / cards)
@@ -313,7 +311,7 @@ export function ContactsListPage() {
];
registerItems('contacts', items);
return () => unregisterPlugin('contacts');
}, [handleSearch, handleCreate, handleSelectFilter, handleSortChange, t, registerItems, unregisterPlugin, sortBy, sortOrder, selectedFilter, viewMode, sortOptions, viewModeOptions, filterState, contactType]);
}, [handleSearch, handleCreate, handleSelectFilter, t, registerItems, unregisterPlugin, selectedFilter, viewMode, sortOptions, viewModeOptions, filterState, contactType, sortState]);
return (
<div className="flex flex-col h-full" data-testid="contacts-list-page">