/** * RbacExportImport * * Component for exporting and importing RBAC configurations. * Supports mandate-level and global exports with different import modes. */ import React, { useState, useRef, useCallback, useEffect } from 'react'; import { FaDownload, FaUpload, FaFileExport, FaFileImport, FaSpinner, FaCheckCircle, FaExclamationTriangle, FaInfoCircle, FaTrash, FaEye, } from 'react-icons/fa'; import { useRbacExportImport, type RbacExport, type ImportMode, type RbacImportResult, } from '../../hooks/useRbacExportImport'; import styles from './RbacExportImport.module.css'; // ============================================================================= // TYPES // ============================================================================= interface RbacExportImportProps { mandateId?: string; mandateName?: string; isGlobal?: boolean; featureCode?: string; } // ============================================================================= // IMPORT MODE OPTIONS // ============================================================================= const IMPORT_MODES: { value: ImportMode; label: string; description: string; icon: React.ReactNode }[] = [ { value: 'merge', label: 'Zusammenführen', description: 'Bestehende Regeln aktualisieren, neue hinzufügen', icon: , }, { value: 'add_only', label: 'Nur hinzufügen', description: 'Nur neue Regeln hinzufügen, bestehende nicht ändern', icon: , }, { value: 'replace', label: 'Ersetzen', description: 'Alle bestehenden Regeln löschen und ersetzen', icon: , }, ]; // ============================================================================= // PREVIEW COMPONENT // ============================================================================= interface PreviewProps { data: RbacExport; onClose: () => void; } const ExportPreview: React.FC = ({ data, onClose }) => { return (

Export-Vorschau

Scope
  • Typ: {data.scope.type}
  • {data.scope.mandateName &&
  • Mandant: {data.scope.mandateName}
  • } {data.scope.featureCode &&
  • Feature: {data.scope.featureCode}
  • }
Rollen ({data.roles.length})
    {data.roles.slice(0, 5).map((role, i) => (
  • {role.roleLabel} {role.featureCode && {role.featureCode}}
  • ))} {data.roles.length > 5 && (
  • ... und {data.roles.length - 5} weitere
  • )}
Regeln ({data.accessRules.length})
    {data.accessRules.slice(0, 5).map((rule, i) => (
  • {rule.context} {rule.item || '(global)'}
  • ))} {data.accessRules.length > 5 && (
  • ... und {data.accessRules.length - 5} weitere
  • )}
); }; // ============================================================================= // IMPORT RESULT COMPONENT // ============================================================================= interface ImportResultProps { result: RbacImportResult; onClose: () => void; } const ImportResult: React.FC = ({ result, onClose }) => { const isSuccess = result.status === 'success'; return (
{isSuccess ? ( ) : ( )}

{isSuccess ? 'Import erfolgreich' : 'Import fehlgeschlagen'}

  • Modus: {IMPORT_MODES.find(m => m.value === result.mode)?.label}
  • Rollen erstellt: {result.rolesCreated}
  • Rollen aktualisiert: {result.rolesUpdated}
  • Regeln erstellt: {result.rulesCreated}
  • Regeln aktualisiert: {result.rulesUpdated}
{result.errors && result.errors.length > 0 && (
Fehler:
    {result.errors.map((err, i) => (
  • {err}
  • ))}
)}
); }; // ============================================================================= // MAIN COMPONENT // ============================================================================= export const RbacExportImport: React.FC = ({ mandateId, mandateName, isGlobal = false, featureCode, }) => { const { exporting, importing, error, lastExport, lastImportResult, exportMandateRbac, exportGlobalRbac, importMandateRbac, importGlobalRbac, downloadExport, parseImportFile, reset, } = useRbacExportImport(); const [importMode, setImportMode] = useState('merge'); const [importFile, setImportFile] = useState(null); const [importData, setImportData] = useState(null); const [parseError, setParseError] = useState(null); const [showPreview, setShowPreview] = useState(false); const [showResult, setShowResult] = useState(false); const fileInputRef = useRef(null); // Handle export const handleExport = async () => { let result; if (isGlobal) { result = await exportGlobalRbac(featureCode); } else if (mandateId) { result = await exportMandateRbac(mandateId, featureCode); } else { return; } if (result.success && result.data) { downloadExport(result.data); } }; // Handle file selection const handleFileSelect = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setImportFile(file); setParseError(null); const result = await parseImportFile(file); if (result.success && result.data) { setImportData(result.data); } else { setParseError(result.error || 'Fehler beim Parsen'); setImportData(null); } }; // Handle import const handleImport = async () => { if (!importData) return; let result; if (isGlobal) { result = await importGlobalRbac(importData, importMode); } else if (mandateId) { result = await importMandateRbac(mandateId, importData, importMode); } else { return; } if (result.success) { setShowResult(true); // Clear import state setImportFile(null); setImportData(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; // Clear import state const handleClearImport = () => { setImportFile(null); setImportData(null); setParseError(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; // Handle close result const handleCloseResult = () => { setShowResult(false); reset(); }; return (
{/* Export Section */}

Export

Exportiert alle Rollen und Berechtigungen {isGlobal ? ' der globalen Templates' : ` des Mandanten "${mandateName || mandateId}"`} {featureCode ? ` für Feature "${featureCode}"` : ''} als JSON-Datei.

{/* Import Section */}

Import

{/* File Upload */}
{importFile && ( )}
{/* Parse Error */} {parseError && (
{parseError}
)} {/* Import Data Info */} {importData && (
Rollen: {importData.roles.length} Regeln: {importData.accessRules.length} Quelle: {importData.scope.type}
)} {/* Import Mode Selection */} {importData && (

Import-Modus

{IMPORT_MODES.map(mode => ( ))}
)} {/* Import Button */} {importData && ( )} {/* Warning for replace mode */} {importMode === 'replace' && importData && (
Achtung: Im Modus "Ersetzen" werden alle bestehenden Rollen und Regeln gelöscht!
)}
{/* Error Message */} {error && (
{error}
)} {/* Preview Modal */} {showPreview && importData && (
setShowPreview(false)}>
e.stopPropagation()}> setShowPreview(false)} />
)} {/* Result Modal */} {showResult && lastImportResult && (
e.stopPropagation()}>
)}
); }; export default RbacExportImport;