/** * AdminMandateWizardPage (v4.0 - poweron port) * * 4-step wizard for mandate management: * 1. Select/Create Mandate * 2. Manage Mandate Users (add/remove users to/from mandate) * 3. Manage Feature Instances (CRUD) * 4. Manage Users per Feature Instance (CRUD + Roles) */ import React, { useState, useEffect, useCallback } from 'react'; import { useUserMandates, type MandateUser, type Mandate, type Role, } from '../../../hooks/useUserMandates'; import { useFeatureAccess, type FeatureInstance, type FeatureAccessUser, type FeatureInstanceRole, type Feature, } from '../../../hooks/useFeatureAccess'; import { useToast } from '../../../contexts/ToastContext'; import api from '../../../api'; import styles from '../Admin.module.css'; const TOTAL_STEPS = 4; const STEP_LABELS = ['Mandant', 'Benutzer', 'Instances', 'Feature-Benutzer']; interface RoleOption { id: string; roleLabel: string; } export const AdminMandateWizardPage: React.FC = () => { const { showSuccess } = useToast(); const { fetchMandateUsers, addUserToMandate, removeUserFromMandate, fetchMandates: fetchMandatesList, fetchRoles: fetchMandateRolesList, fetchAllUsers, } = useUserMandates(); const { fetchFeatures, fetchInstances, createInstance, deleteInstance, fetchInstanceUsers, addUserToInstance, removeUserFromInstance, fetchInstanceRoles: fetchInstanceRolesList, } = useFeatureAccess(); // Wizard state const [step, setStep] = useState(1); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); // Step 1: Mandate const [mandates, setMandates] = useState([]); const [selectedMandate, setSelectedMandate] = useState | null>(null); const [isCreatingMandate, setIsCreatingMandate] = useState(false); const [mandateForm, setMandateForm] = useState({ name: '' }); // Step 2: Mandate Users const [mandateUsers, setMandateUsers] = useState([]); const [allSystemUsers, setAllSystemUsers] = useState>([]); const [mandateRoles, setMandateRoles] = useState([]); const [isAddingMandateUser, setIsAddingMandateUser] = useState(false); const [addMandateUserForm, setAddMandateUserForm] = useState({ userId: '', roleIds: [] as string[] }); // Step 3: Instances const [features, setFeatures] = useState([]); const [instances, setInstances] = useState([]); const [selectedFeatureCode, setSelectedFeatureCode] = useState(''); const [instanceForm, setInstanceForm] = useState({ label: '', enabled: true }); const [isCreatingInstance, setIsCreatingInstance] = useState(false); // Step 4: Users per instance const [selectedInstance, setSelectedInstance] = useState(null); const [instanceUsers, setInstanceUsers] = useState([]); const [instanceRoles, setInstanceRoles] = useState([]); const [isAddingInstanceUser, setIsAddingInstanceUser] = useState(false); const [addInstanceUserForm, setAddInstanceUserForm] = useState({ userId: '', roleIds: [] as string[] }); // ───────────────────────────────────────────────────────────────────────── // HELPERS // ───────────────────────────────────────────────────────────────────────── const getMandateName = (m: Mandate | Record): string => { if (m.label) return m.label; if (typeof m.name === 'object') { return m.name.de || m.name.en || Object.values(m.name)[0] || m.id; } return m.name || m.id; }; const getFeatureLabel = (code: string): string => { const f = features.find(feat => feat.code === code); if (f) { return typeof f.label === 'object' ? (f.label.de || f.label.en || code) : (f.label || code); } return code; }; const getUserDisplayName = (u: { fullName?: string; firstname?: string | null; lastname?: string | null; username: string }): string => { if (u.fullName) return u.fullName; const parts = [u.firstname, u.lastname].filter(Boolean); return parts.length > 0 ? parts.join(' ') : u.username; }; // ───────────────────────────────────────────────────────────────────────── // DATA LOADING // ───────────────────────────────────────────────────────────────────────── const loadMandates = useCallback(async () => { try { const data = await fetchMandatesList(); setMandates(data); } catch { setError('Fehler beim Laden der Mandanten'); } }, [fetchMandatesList]); useEffect(() => { loadMandates(); }, [loadMandates]); useEffect(() => { fetchFeatures().then(setFeatures); }, [fetchFeatures]); // Step 2 const loadMandateUsers = useCallback(async () => { if (!selectedMandate) return; const data = await fetchMandateUsers(selectedMandate.id); setMandateUsers(data); }, [selectedMandate, fetchMandateUsers]); const loadAllSystemUsers = useCallback(async () => { const data = await fetchAllUsers(); setAllSystemUsers(data); }, [fetchAllUsers]); const loadMandateRoles = useCallback(async () => { if (!selectedMandate) return; const data = await fetchMandateRolesList(selectedMandate.id); setMandateRoles(data.map((r: Role) => ({ id: r.id, roleLabel: r.roleLabel }))); }, [selectedMandate, fetchMandateRolesList]); useEffect(() => { if (step === 2 && selectedMandate) { loadMandateUsers(); loadAllSystemUsers(); loadMandateRoles(); } }, [step, selectedMandate, loadMandateUsers, loadAllSystemUsers, loadMandateRoles]); // Step 3 const loadInstances = useCallback(async () => { if (!selectedMandate) return; const data = await fetchInstances(selectedMandate.id, selectedFeatureCode || undefined); setInstances(data); }, [selectedMandate, selectedFeatureCode, fetchInstances]); useEffect(() => { if (step === 3 && selectedMandate) loadInstances(); }, [step, selectedMandate, loadInstances]); // Step 4 const loadInstanceUsers = useCallback(async () => { if (!selectedInstance || !selectedMandate) return; const data = await fetchInstanceUsers(selectedMandate.id, selectedInstance.id); setInstanceUsers(data); }, [selectedInstance, selectedMandate, fetchInstanceUsers]); const loadInstanceRoles = useCallback(async () => { if (!selectedInstance || !selectedMandate) return; const data = await fetchInstanceRolesList(selectedMandate.id, selectedInstance.id); setInstanceRoles(data.map((r: FeatureInstanceRole) => ({ id: r.id, roleLabel: r.roleLabel }))); }, [selectedInstance, selectedMandate, fetchInstanceRolesList]); useEffect(() => { if (step === 4 && selectedInstance) { loadInstanceUsers(); loadInstanceRoles(); loadMandateUsers(); } }, [step, selectedInstance, loadInstanceUsers, loadInstanceRoles, loadMandateUsers]); // ───────────────────────────────────────────────────────────────────────── // HANDLERS // ───────────────────────────────────────────────────────────────────────── const handleCreateMandate = async () => { if (!mandateForm.name.trim()) { setError('Name ist erforderlich'); return; } setIsLoading(true); setError(null); try { const response = await api.post('/api/mandates/', { name: mandateForm.name, enabled: true, }); setSelectedMandate(response.data); setIsCreatingMandate(false); showSuccess('Erstellt', 'Mandant erstellt'); await loadMandates(); } catch (err: any) { setError(err?.response?.data?.detail || err?.message || 'Fehler beim Erstellen'); } finally { setIsLoading(false); } }; const handleAddMandateUser = async () => { if (!selectedMandate || !addMandateUserForm.userId) return; setIsLoading(true); setError(null); try { const result = await addUserToMandate(selectedMandate.id, { targetUserId: addMandateUserForm.userId, roleIds: addMandateUserForm.roleIds, }); if (result.success) { setIsAddingMandateUser(false); setAddMandateUserForm({ userId: '', roleIds: [] }); showSuccess('Hinzugefügt', 'Benutzer zum Mandanten hinzugefügt'); await loadMandateUsers(); } else { setError(result.error || 'Fehler beim Hinzufügen'); } } finally { setIsLoading(false); } }; const handleRemoveMandateUser = async (userId: string) => { if (!selectedMandate) return; const result = await removeUserFromMandate(selectedMandate.id, userId); if (result.success) { showSuccess('Entfernt', 'Benutzer aus Mandant entfernt'); await loadMandateUsers(); } else { setError(result.error || 'Fehler beim Entfernen'); } }; const handleCreateInstance = async () => { if (!instanceForm.label.trim() || !selectedMandate || !selectedFeatureCode) return; setIsLoading(true); setError(null); try { const result = await createInstance(selectedMandate.id, { featureCode: selectedFeatureCode, label: instanceForm.label, enabled: instanceForm.enabled, }); if (result.success) { setIsCreatingInstance(false); setInstanceForm({ label: '', enabled: true }); showSuccess('Erstellt', 'Instance erstellt'); await loadInstances(); } else { setError(result.error || 'Fehler beim Erstellen (Limit erreicht?)'); } } finally { setIsLoading(false); } }; const handleDeleteInstance = async (instanceId: string) => { if (!selectedMandate) return; const result = await deleteInstance(selectedMandate.id, instanceId); if (result.success) { showSuccess('Gelöscht', 'Instance gelöscht'); await loadInstances(); } else { setError(result.error || 'Fehler beim Löschen'); } }; const handleAddInstanceUser = async () => { if (!selectedInstance || !selectedMandate || !addInstanceUserForm.userId) return; setIsLoading(true); setError(null); try { const result = await addUserToInstance(selectedMandate.id, selectedInstance.id, { userId: addInstanceUserForm.userId, roleIds: addInstanceUserForm.roleIds, }); if (result.success) { setIsAddingInstanceUser(false); setAddInstanceUserForm({ userId: '', roleIds: [] }); showSuccess('Hinzugefügt', 'Benutzer zur Feature-Instanz hinzugefügt'); await loadInstanceUsers(); } else { setError(result.error || 'Fehler beim Hinzufügen'); } } finally { setIsLoading(false); } }; const handleRemoveInstanceUser = async (userId: string) => { if (!selectedInstance || !selectedMandate) return; const result = await removeUserFromInstance(selectedMandate.id, selectedInstance.id, userId); if (result.success) { showSuccess('Entfernt', 'Benutzer aus Feature-Instanz entfernt'); await loadInstanceUsers(); } else { setError(result.error || 'Fehler beim Entfernen'); } }; // ───────────────────────────────────────────────────────────────────────── // COMPUTED // ───────────────────────────────────────────────────────────────────────── const availableUsersForMandate = allSystemUsers.filter( u => !mandateUsers.some(mu => mu.userId === u.id) ); const availableUsersForInstance = mandateUsers.filter( mu => !instanceUsers.some(iu => iu.userId === mu.userId) ); // ───────────────────────────────────────────────────────────────────────── // SHARED UI // ───────────────────────────────────────────────────────────────────────── const renderUserTable = ( users: Array<{ userId?: string; id?: string; username: string; email?: string | null; fullName?: string; firstname?: string | null; lastname?: string | null; enabled?: boolean; roleLabels?: string[] }>, onRemove: (userId: string) => void, ) => ( users.length > 0 ? ( {users.map(u => { const uid = u.userId || u.id || ''; return ( ); })}
Benutzer E-Mail Rollen Status Aktion
{getUserDisplayName(u as any)} {u.email || '-'} {u.roleLabels?.join(', ') || '-'} {u.enabled !== false ? 'Aktiv' : 'Inaktiv'}
) : (
Noch keine Benutzer zugewiesen
) ); const renderAddUserForm = ( availableUsers: Array<{ id?: string; userId?: string; username: string; email?: string | null; fullName?: string; firstname?: string | null; lastname?: string | null }>, roles: RoleOption[], formValue: { userId: string; roleIds: string[] }, setFormValue: (fn: (prev: { userId: string; roleIds: string[] }) => { userId: string; roleIds: string[] }) => void, onSubmit: () => void, onCancel: () => void, ) => (
{roles.length > 0 && (
{roles.map(r => ( ))}
)}
); // ───────────────────────────────────────────────────────────────────────── // STEP INDICATOR // ───────────────────────────────────────────────────────────────────────── const renderStepIndicator = () => (
{Array.from({ length: TOTAL_STEPS }, (_, i) => i + 1).map(s => (
{ if (s < step) setStep(s); }} > {s < step ? '\u2713' : s} {STEP_LABELS[s - 1]}
))}
); // ───────────────────────────────────────────────────────────────────────── // CARD WRAPPER (reusable section container matching poweron theme) // ───────────────────────────────────────────────────────────────────────── const cardStyle: React.CSSProperties = { background: 'var(--surface-color, #fff)', border: '1px solid var(--border-color, #e5e7eb)', borderRadius: '12px', padding: '24px', }; // ───────────────────────────────────────────────────────────────────────── // RENDER // ───────────────────────────────────────────────────────────────────────── return (

Mandanten-Verwaltung

Schritt-für-Schritt Wizard zur Mandanten-Konfiguration

{error && (
{error}
)} {renderStepIndicator()} {/* ── STEP 1: MANDATE ── */} {step === 1 && (

Mandant auswählen oder erstellen

{!isCreatingMandate ? ( <>
{mandates.map(m => ( ))}
) : (
setMandateForm(p => ({ ...p, name: e.target.value }))} placeholder="z.B. Swiss Trust AG" />
)}
)} {/* ── STEP 2: MANDATE USERS ── */} {step === 2 && selectedMandate && (

Benutzer von «{getMandateName(selectedMandate)}»

Alle Systembenutzer können dem Mandanten zugewiesen werden.

{isAddingMandateUser && renderAddUserForm( availableUsersForMandate, mandateRoles, addMandateUserForm, setAddMandateUserForm, handleAddMandateUser, () => { setIsAddingMandateUser(false); setAddMandateUserForm({ userId: '', roleIds: [] }); }, )}
{renderUserTable(mandateUsers as any[], handleRemoveMandateUser)}
)} {/* ── STEP 3: INSTANCES ── */} {step === 3 && selectedMandate && (

Feature-Instances für «{getMandateName(selectedMandate)}»

{instances.length} Instances
{/* Feature Filter */}
{instances.map(inst => (
{inst.label} {getFeatureLabel(inst.featureCode)} | {inst.enabled ? 'Aktiv' : 'Inaktiv'}
))}
{!isCreatingInstance ? ( ) : (
setInstanceForm(p => ({ ...p, label: e.target.value }))} placeholder="z.B. Kunde A" />
)}
)} {/* ── STEP 4: FEATURE INSTANCE USERS ── */} {step === 4 && selectedMandate && selectedInstance && (

Feature-Benutzer für «{selectedInstance.label}»

Mandant: {getMandateName(selectedMandate)} | Mitglieder des Mandanten können der Feature-Instanz zugewiesen werden.

{isAddingInstanceUser && renderAddUserForm( availableUsersForInstance as any[], instanceRoles, addInstanceUserForm, setAddInstanceUserForm, handleAddInstanceUser, () => { setIsAddingInstanceUser(false); setAddInstanceUserForm({ userId: '', roleIds: [] }); }, )}
{renderUserTable( instanceUsers.map(u => ({ ...u, userId: u.userId || u.id })), handleRemoveInstanceUser, )}
)}
); }; export default AdminMandateWizardPage;