diff --git a/frontend/src/components/layout/PluginToolbar.tsx b/frontend/src/components/layout/PluginToolbar.tsx index c6ae9ff..d7a24d6 100644 --- a/frontend/src/components/layout/PluginToolbar.tsx +++ b/frontend/src/components/layout/PluginToolbar.tsx @@ -88,65 +88,99 @@ function ToolbarSearch({ item }: { item: ToolbarItem }) { function ToolbarDropdown({ item }: { item: ToolbarItem }) { const [open, setOpen] = React.useState(false); const ref = React.useRef(null); + const menuRef = React.useRef(null); + const [menuPos, setMenuPos] = React.useState({ top: 0, left: 0 }); React.useEffect(() => { if (!open) return; const handler = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + const target = e.target as Node; + if (ref.current?.contains(target) || menuRef.current?.contains(target)) return; + setOpen(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [open]); + const handleToggle = () => { + if (!open && ref.current) { + const rect = ref.current.getBoundingClientRect(); + setMenuPos({ top: rect.bottom + 4, left: rect.left }); + } + setOpen(!open); + }; + const activeOption = item.menuOptions?.find((o) => o.active); + let lastSection: string | undefined = undefined; return ( -
- + <> +
+ +
{open && (
- {item.menuOptions?.map((opt) => ( - - ))} + {item.menuOptions?.map((opt) => { + const showSection = opt.section && opt.section !== lastSection; + if (showSection) lastSection = opt.section; + return ( + + {showSection && ( +
+ {opt.section} +
+ )} + {opt.separator &&
} + + + ); + })}
)} -
+ ); } diff --git a/frontend/src/pages/ContactsList.tsx b/frontend/src/pages/ContactsList.tsx index 8d84eab..e88cedc 100644 --- a/frontend/src/pages/ContactsList.tsx +++ b/frontend/src/pages/ContactsList.tsx @@ -209,43 +209,35 @@ export function ContactsListPage() { onSearch: handleSearch, onClick: () => {}, }, - // Sort by + // Unified filter dropdown — filter + sort in one panel { - id: 'sort-by', - plugin: 'contacts', - label: t('common.sort'), - type: 'select' as const, - group: 'sort', - selectOptions: sortOptions, - selectValue: sortBy, - onSelect: (val: string) => { setSortBy(val); setPage(1); }, - onClick: () => {}, - }, - // Sort order toggle - { - id: 'sort-order', - plugin: 'contacts', - label: sortOrder === 'asc' ? t('common.sortDesc') : t('common.sortAsc'), - type: 'button' as const, - group: 'sort', - icon: sortOrder === 'asc' - ? - : , - onClick: () => handleSortChange(sortBy, sortOrder === 'asc' ? 'desc' : 'asc'), - }, - // Type filter dropdown (all / companies / persons) - { - id: 'filter-type', + id: 'filter-sort', plugin: 'contacts', label: t('common.filter', 'Filter'), type: 'dropdown' as const, group: 'filter', icon: , - menuWidth: '200px', + iconOnly: true, + menuWidth: '240px', menuOptions: [ - { value: 'all', label: t('common.all'), active: selectedFilter === 'all', onClick: () => handleSelectFilter('all') }, + // Section: Filter + { value: 'all', label: t('common.all'), section: t('common.filter', 'Filter'), active: selectedFilter === 'all', onClick: () => handleSelectFilter('all') }, { value: 'company', label: t('contacts.companies'), active: selectedFilter === 'company', onClick: () => handleSelectFilter('company') }, { value: 'person', label: t('contacts.persons'), active: selectedFilter === 'person', onClick: () => handleSelectFilter('person') }, + // Separator + { value: 'sep-sort', label: '', separator: true, onClick: () => {} }, + // Section: Sort by + ...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); }, + })), + // Separator + { value: 'sep-order', label: '', separator: true, onClick: () => {} }, + // Section: Sort order + { value: 'sort-asc', label: t('common.sortAsc', 'Aufsteigend'), section: t('common.sortOrder', 'Sortierreihenfolge'), + icon: , active: sortOrder === 'asc', onClick: () => handleSortChange(sortBy, 'asc') }, + { value: 'sort-desc', label: t('common.sortDesc', 'Absteigend'), + icon: , active: sortOrder === 'desc', onClick: () => handleSortChange(sortBy, 'desc') }, ], onClick: () => {}, }, @@ -256,7 +248,7 @@ export function ContactsListPage() { label: t('common.view', 'Ansicht'), type: 'dropdown' as const, group: 'view', - icon: , + icon: , menuWidth: '180px', menuOptions: [ { value: 'list', label: t('contacts.viewList'), icon: , active: viewMode === 'list', onClick: () => setViewMode('list') }, diff --git a/frontend/src/store/pluginToolbarStore.ts b/frontend/src/store/pluginToolbarStore.ts index b8e6c1b..bde9f3a 100644 --- a/frontend/src/store/pluginToolbarStore.ts +++ b/frontend/src/store/pluginToolbarStore.ts @@ -15,8 +15,9 @@ export interface ToolbarItem { selectOptions?: { value: string; label: string }[]; selectValue?: string; onSelect?: (value: string) => void; - menuOptions?: { value: string; label: string; icon?: React.ReactNode; active?: boolean; onClick: () => void }[]; + menuOptions?: { value: string; label: string; icon?: React.ReactNode; active?: boolean; onClick: () => void; section?: string; separator?: boolean; disabled?: boolean }[]; menuWidth?: string; + iconOnly?: boolean; } interface PluginToolbarState {