feat(#359): W4a Phase 2 — zentraler Import/Export-Dialog (Frontend)

- ImportExportDialog.tsx (neu): Modal lg/xl nach bestehendem ui/Modal-Muster
- Export-Tab: Formatauswahl (csv/xlsx/json), Download, Fehler-Handling
- Import-Tab: 4 Schritte (Datei -> Mapping -> Dry-Run -> Ausführung+Report),
  Mapping-Vorschau mit Modul-Heuristik, Background-Job-Polling ab 1000 Zeilen
- i18n: 24 importexport.*-Keys in de.json + en.json (keine hardcoded Strings)
- Integration: ContactsList Toolbar-Button (contacts:read-Gate, Upload-Icon,
  entityType=contacts vorgewählt) über bestehendes pluginToolbarStore-Muster

Gates: Vitest 12/12 (routePermissions + importExportDialog), tsc exit 0,
Production-Build exit 0 (vor Commit). 6 Failures in contacts/shell Suiten
als Vorbestand bewiesen (Stash-Test: identisch auf clean HEAD f27f047).

fixes #359 (Phase 2)
This commit is contained in:
Agent Zero
2026-08-27 21:34:13 +02:00
parent cd8ef7500c
commit 38df597f11
5 changed files with 558 additions and 1 deletions
+22 -1
View File
@@ -19,7 +19,8 @@ import { SavedFilters } from '@/components/SavedFilters';
import { SavedFilterBar } from '@/components/common/SavedFilterBar';
import { TagSelector } from '@/components/tags/TagSelector';
import type { Tag } from '@/api/tags';
import { ArrowDownAZ, ArrowUpZA, Bookmark, ChevronLeft, ExternalLink, LayoutGrid, List, Plus, Printer, Table2, X } from 'lucide-react';
import { ArrowDownAZ, ArrowUpZA, Bookmark, ChevronLeft, ExternalLink, LayoutGrid, List, Plus, Printer, Table2, Upload, X } from 'lucide-react';
import { ImportExportDialog } from '@/components/importexport/ImportExportDialog';
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';
@@ -222,6 +223,9 @@ export function ContactsListPage() {
setActiveView('detail');
}, []);
// Import/Export dialog (W4a #359)
const [importExportOpen, setImportExportOpen] = useState(false);
// Handle create
const handleCreate = useCallback(() => {
const windowId = openWindow({
@@ -374,6 +378,16 @@ export function ContactsListPage() {
icon: <Plus className="w-3.5 h-3.5" strokeWidth={2} />,
onClick: handleCreate,
}] : []),
// Import/Export dialog (W4a #359)
...(canAccess('contacts:read') ? [{
id: 'import-export',
plugin: 'contacts',
label: t('importexport.dialogTitle'),
type: 'button' as const,
group: 'create',
icon: <Upload className="w-3.5 h-3.5" strokeWidth={2} />,
onClick: () => setImportExportOpen(true),
}] : []),
// View mode dropdown (list / table / cards)
{
id: 'view-mode',
@@ -782,6 +796,13 @@ export function ContactsListPage() {
hasSort={sortState.conditions.length > 0}
hasFolder={selectedFilter !== 'all' || multiSelectFolders.length > 0}
/>
{/* Import/Export Dialog (W4a #359) */}
<ImportExportDialog
open={importExportOpen}
onClose={() => setImportExportOpen(false)}
entityType="contacts"
/>
</div>
);
}