fix:user invitations
This commit is contained in:
parent
7366da06ac
commit
1fe4f4cad9
1 changed files with 22 additions and 32 deletions
|
|
@ -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} />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue