From 69c19629952daa710631fecdf0c91a7659f15526 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Tue, 28 Jul 2026 14:09:15 +0200 Subject: [PATCH] feat: SaveViewDialog with selectable components (folder, view, filter, group, sort) --- .../components/contacts/SaveViewDialog.tsx | 146 ++++++++++++++++++ frontend/src/pages/ContactsList.tsx | 47 ++++-- 2 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/contacts/SaveViewDialog.tsx diff --git a/frontend/src/components/contacts/SaveViewDialog.tsx b/frontend/src/components/contacts/SaveViewDialog.tsx new file mode 100644 index 0000000..cbb8823 --- /dev/null +++ b/frontend/src/components/contacts/SaveViewDialog.tsx @@ -0,0 +1,146 @@ +/** + * Dialog for saving a custom view with selectable components. + */ + +import React, { useState } from 'react'; +import { Bookmark, Check, Folder, Filter, Group as GroupIcon, ArrowDownAZ, LayoutGrid } from 'lucide-react'; + +export interface SaveViewSelection { + folder: boolean; + viewMode: boolean; + filter: boolean; + group: boolean; + sort: boolean; +} + +interface SaveViewDialogProps { + open: boolean; + onClose: () => void; + onSave: (name: string, selection: SaveViewSelection) => void; + hasFilter: boolean; + hasGroup: boolean; + hasSort: boolean; + hasFolder: boolean; +} + +const defaultSelection: SaveViewSelection = { + folder: true, + viewMode: true, + filter: true, + group: true, + sort: true, +}; + +export function SaveViewDialog({ open, onClose, onSave, hasFilter, hasGroup, hasSort, hasFolder }: SaveViewDialogProps) { + const [name, setName] = useState(''); + const [selection, setSelection] = useState(defaultSelection); + + if (!open) return null; + + const handleSave = () => { + if (!name.trim()) return; + onSave(name.trim(), selection); + setName(''); + setSelection(defaultSelection); + onClose(); + }; + + const toggle = (key: keyof SaveViewSelection) => { + setSelection((prev) => ({ ...prev, [key]: !prev[key] })); + }; + + const options: { key: keyof SaveViewSelection; label: string; icon: React.ReactNode; available: boolean; description: string }[] = [ + { key: 'folder', label: 'Ordner-Auswahl', icon: , available: hasFolder, description: 'Aktuell ausgewählte Ordner' }, + { key: 'viewMode', label: 'Ansichtsmodus', icon: , available: true, description: 'Liste, Tabelle oder Karten' }, + { key: 'filter', label: 'Filter', icon: , available: hasFilter, description: 'Aktive Filterbedingungen' }, + { key: 'group', label: 'Gruppierung', icon: , available: hasGroup, description: 'Aktive Gruppierungen' }, + { key: 'sort', label: 'Sortierung', icon: , available: hasSort, description: 'Aktive Sortierungen' }, + ]; + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+ +

Ansicht speichern

+
+ + {/* Body */} +
+ {/* Name input */} +
+ + setName(e.target.value)} + placeholder="z.B. Meine Firmen-Kontakte" + autoFocus + onKeyDown={(e) => { if (e.key === 'Enter') handleSave(); }} + className="w-full px-3 py-2 text-sm border border-secondary-200 rounded-md focus:outline-none focus:border-primary-400 focus:ring-1 focus:ring-primary-300" + /> +
+ + {/* Component selection */} +
+ +
+ {options.map((opt) => ( + + ))} +
+
+
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/frontend/src/pages/ContactsList.tsx b/frontend/src/pages/ContactsList.tsx index c978475..f6b8e29 100644 --- a/frontend/src/pages/ContactsList.tsx +++ b/frontend/src/pages/ContactsList.tsx @@ -23,6 +23,7 @@ import { ArrowDownAZ, ArrowUpZA, Bookmark, ChevronLeft, ExternalLink, LayoutGrid import { FilterPanel, applyFilters, emptyFilterState, type FilterState, type SavedFilter } from '@/components/contacts/FilterPanel'; import { SortPanel, applySorting, emptySortState, type SortState } from '@/components/contacts/SortPanel'; import { GroupPanel, applyGrouping, emptyGroupState, type GroupState, type GroupedContacts } from '@/components/contacts/GroupPanel'; +import { SaveViewDialog, type SaveViewSelection } from '@/components/contacts/SaveViewDialog'; import { useUnifiedContacts, useUnifiedContact, @@ -51,9 +52,10 @@ export function ContactsListPage() { const [viewsOpen, setViewsOpen] = useState(true); const [foldersOpen, setFoldersOpen] = useState(true); const [multiSelectFolders, setMultiSelectFolders] = useState([]); - const [savedViews, setSavedViews] = useState<{ id: string; name: string; filterState: FilterState; sortState: SortState; groupState: GroupState; selectedFilter: ContactFilter; viewMode: ContactViewMode }[]>([]); + const [savedViews, setSavedViews] = useState<{ id: string; name: string; filterState: FilterState; sortState: SortState; groupState: GroupState; selectedFilter: ContactFilter; viewMode: ContactViewMode; multiSelectFolders: string[]; selection: SaveViewSelection }[]>([]); const [activeViewId, setActiveViewId] = useState(null); const [savedFilters, setSavedFilters] = useState([]); + const [saveViewDialogOpen, setSaveViewDialogOpen] = useState(false); const openWindow = useWindowStore((s) => s.openWindow); // Debounce search @@ -202,10 +204,13 @@ export function ContactsListPage() { setActiveView('list'); }, []); - // Save current view configuration as a custom view + // Save current view configuration as a custom view — opens dialog const handleSaveView = useCallback(() => { - const name = window.prompt('Name für diese Ansicht:', 'Meine Ansicht'); - if (!name) return; + setSaveViewDialogOpen(true); + }, []); + + // Actually save the view with selection + const handleSaveViewConfirm = useCallback((name: string, selection: SaveViewSelection) => { const id = `view-${Date.now()}`; setSavedViews((prev) => [...prev, { id, @@ -215,17 +220,22 @@ export function ContactsListPage() { groupState: { ...groupState }, selectedFilter, viewMode, + multiSelectFolders: [...multiSelectFolders], + selection, }]); setActiveViewId(id); - }, [filterState, sortState, groupState, selectedFilter, viewMode]); + }, [filterState, sortState, groupState, selectedFilter, viewMode, multiSelectFolders]); - // Apply a saved custom view - const applySavedView = useCallback((view: { id: string; filterState: FilterState; sortState: SortState; groupState: GroupState; selectedFilter: ContactFilter; viewMode: ContactViewMode }) => { - setFilterState({ ...view.filterState }); - setSortState({ ...view.sortState }); - setGroupState({ ...view.groupState }); - setSelectedFilter(view.selectedFilter); - setViewMode(view.viewMode); + // Apply a saved custom view — only selected components + const applySavedView = useCallback((view: { id: string; filterState: FilterState; sortState: SortState; groupState: GroupState; selectedFilter: ContactFilter; viewMode: ContactViewMode; multiSelectFolders: string[]; selection: SaveViewSelection }) => { + if (view.selection.folder) { + setSelectedFilter(view.selectedFilter); + setMultiSelectFolders(view.multiSelectFolders || []); + } + if (view.selection.viewMode) setViewMode(view.viewMode); + if (view.selection.filter) setFilterState({ ...view.filterState }); + if (view.selection.group) setGroupState({ ...view.groupState }); + if (view.selection.sort) setSortState({ ...view.sortState }); setActiveViewId(view.id); setPage(1); }, []); @@ -398,7 +408,7 @@ export function ContactsListPage() { ]; registerItems('contacts', items); return () => unregisterPlugin('contacts'); - }, [handleSearch, handleCreate, handleSelectFilter, handleSaveView, applySavedView, deleteSavedView, handleSaveFilter, handleLoadFilter, handleDeleteFilter, t, registerItems, unregisterPlugin, selectedFilter, viewMode, sortOptions, viewModeOptions, filterState, contactType, sortState, groupState, savedViews, activeViewId, multiSelectFolders, savedFilters]); + }, [handleSearch, handleCreate, handleSelectFilter, handleSaveView, applySavedView, deleteSavedView, handleSaveViewConfirm, handleSaveFilter, handleLoadFilter, handleDeleteFilter, t, registerItems, unregisterPlugin, selectedFilter, viewMode, sortOptions, viewModeOptions, filterState, contactType, sortState, groupState, savedViews, activeViewId, multiSelectFolders, savedFilters, saveViewDialogOpen]); return (
@@ -666,6 +676,17 @@ export function ContactsListPage() { /> )} + + {/* Save View Dialog */} + setSaveViewDialogOpen(false)} + onSave={handleSaveViewConfirm} + hasFilter={filterState.conditions.length > 0} + hasGroup={groupState.conditions.length > 0} + hasSort={sortState.conditions.length > 0} + hasFolder={selectedFilter !== 'all' || multiSelectFolders.length > 0} + />
); }