fix:user invitations

This commit is contained in:
Ida Dittrich 2026-02-16 09:31:26 +01:00
parent 7366da06ac
commit 1fe4f4cad9

View file

@ -8,7 +8,6 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { useInvitations, type Invitation, type InvitationCreate } from '../../hooks/useInvitations'; import { useInvitations, type Invitation, type InvitationCreate } from '../../hooks/useInvitations';
import { useUserMandates, type Mandate, type Role } from '../../hooks/useUserMandates'; import { useUserMandates, type Mandate, type Role } from '../../hooks/useUserMandates';
import { useFeatureAccess, type FeatureInstance } from '../../hooks/useFeatureAccess';
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
import { FaPlus, FaSync, FaEnvelopeOpenText, FaBuilding, FaCopy, FaLink } from 'react-icons/fa'; import { FaPlus, FaSync, FaEnvelopeOpenText, FaBuilding, FaCopy, FaLink } from 'react-icons/fa';
@ -29,12 +28,10 @@ export const AdminInvitationsPage: React.FC = () => {
} = useInvitations(); } = useInvitations();
const { fetchMandates, fetchRoles } = useUserMandates(); const { fetchMandates, fetchRoles } = useUserMandates();
const { fetchInstances } = useFeatureAccess();
// State // State
const [mandates, setMandates] = useState<Mandate[]>([]); const [mandates, setMandates] = useState<Mandate[]>([]);
const [selectedMandateId, setSelectedMandateId] = useState<string>(''); const [selectedMandateId, setSelectedMandateId] = useState<string>('');
const [featureInstances, setFeatureInstances] = useState<FeatureInstance[]>([]);
const [roles, setRoles] = useState<Role[]>([]); const [roles, setRoles] = useState<Role[]>([]);
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const [showUrlModal, setShowUrlModal] = useState<Invitation | null>(null); const [showUrlModal, setShowUrlModal] = useState<Invitation | null>(null);
@ -61,18 +58,13 @@ export const AdminInvitationsPage: React.FC = () => {
}).catch(() => setBackendAttributes([])); }).catch(() => setBackendAttributes([]));
}, [fetchMandates]); }, [fetchMandates]);
// Load invitations, feature instances, and roles when mandate changes // Load invitations and roles when mandate changes (same roles as AdminUserMandatesPage: user, viewer, admin)
useEffect(() => { useEffect(() => {
if (selectedMandateId) { if (selectedMandateId) {
fetchInvitations(selectedMandateId, { includeExpired: showExpired, includeUsed: showUsed }); fetchInvitations(selectedMandateId, { includeExpired: showExpired, includeUsed: showUsed });
fetchInstances(selectedMandateId).then(instances => { fetchRoles(selectedMandateId).then(setRoles);
setFeatureInstances(instances);
});
fetchRoles(selectedMandateId).then(fetchedRoles => {
setRoles(fetchedRoles);
});
} }
}, [selectedMandateId, showExpired, showUsed, fetchInvitations, fetchInstances, fetchRoles]); }, [selectedMandateId, showExpired, showUsed, fetchInvitations, fetchRoles]);
// Format timestamp // Format timestamp
const formatDate = (timestamp: number) => { const formatDate = (timestamp: number) => {
@ -164,29 +156,20 @@ export const AdminInvitationsPage: React.FC = () => {
}, },
], [roles]); ], [roles]);
// Form attributes from backend - merge with dynamic instance and role options // Form attributes - same role options as AdminUserMandatesPage (user, viewer, admin)
const createFields: AttributeDefinition[] = useMemo(() => { const createFields: AttributeDefinition[] = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'token', 'createdBy', 'createdAt', 'expiresAt', 'currentUses', 'inviteUrl']; const excludedFields = ['id', 'mandateId', 'token', 'createdBy', 'createdAt', 'expiresAt', 'currentUses', 'inviteUrl', 'featureInstanceId'];
// Feature instance options // Mandate-level roles (user, viewer, admin) - same as when adding mandate members
const instanceOptions = featureInstances.map(i => ({
value: i.id,
label: i.label || `${i.featureCode} (${i.id.slice(0, 8)}...)`
}));
// Instance-level roles (with featureInstanceId)
const roleOptions = roles const roleOptions = roles
.filter(r => !!r.featureInstanceId) // Only instance-level roles .filter(r => !r.featureInstanceId)
.map(r => ({ value: r.id, label: `${r.roleLabel} (${featureInstances.find(i => i.id === r.featureInstanceId)?.label || r.featureCode || ''})` })); .map(r => ({ value: r.id, label: r.roleLabel }));
const fields = backendAttributes const fields = backendAttributes
.filter(attr => !excludedFields.includes(attr.name)) .filter(attr => !excludedFields.includes(attr.name))
.map(attr => ({ .map(attr => ({
...attr, ...attr,
// Override options with dynamic data options: attr.name === 'roleIds' ? roleOptions : attr.options,
options: attr.name === 'roleIds' ? roleOptions
: attr.name === 'featureInstanceId' ? instanceOptions
: attr.options,
})) as AttributeDefinition[]; })) as AttributeDefinition[];
// Add helper field expiresInHours if not in model but fields exist // Add helper field expiresInHours if not in model but fields exist
@ -194,8 +177,14 @@ export const AdminInvitationsPage: React.FC = () => {
fields.push({ name: 'expiresInHours', label: 'Gültigkeitsdauer (Stunden)', type: 'number', fields.push({ name: 'expiresInHours', label: 'Gültigkeitsdauer (Stunden)', type: 'number',
required: true, default: 72 } as any); required: true, default: 72 } as any);
} }
return fields; // Override required for targetUsername and email (both required for invitations)
}, [roles, featureInstances, backendAttributes]); return fields.map(f => {
if (f.name === 'targetUsername' || f.name === 'email') {
return { ...f, required: true };
}
return f;
});
}, [roles, backendAttributes]);
// Handle create invitation // Handle create invitation
const handleCreateInvitation = async (data: InvitationCreate) => { const handleCreateInvitation = async (data: InvitationCreate) => {
@ -326,7 +315,6 @@ export const AdminInvitationsPage: React.FC = () => {
<button <button
className={styles.primaryButton} className={styles.primaryButton}
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
disabled={roles.length === 0}
> >
<FaPlus /> Neue Einladung <FaPlus /> Neue Einladung
</button> </button>
@ -358,7 +346,6 @@ export const AdminInvitationsPage: React.FC = () => {
<button <button
className={styles.primaryButton} className={styles.primaryButton}
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
disabled={roles.length === 0}
> >
<FaPlus /> Erste Einladung erstellen <FaPlus /> Erste Einladung erstellen
</button> </button>
@ -414,8 +401,11 @@ export const AdminInvitationsPage: React.FC = () => {
</button> </button>
</div> </div>
<div className={styles.modalContent}> <div className={styles.modalContent}>
{roles.length === 0 ? ( {roles.filter(r => !r.featureInstanceId).length === 0 ? (
<p>Keine Rollen verfügbar. Erstellen Sie zuerst Rollen für diesen Mandanten.</p> <div className={styles.loadingContainer}>
<div className={styles.spinner} />
<span>Lade Rollen...</span>
</div>
) : createFields.length === 0 ? ( ) : createFields.length === 0 ? (
<div className={styles.loadingContainer}> <div className={styles.loadingContainer}>
<div className={styles.spinner} /> <div className={styles.spinner} />