diff --git a/src/hooks/useMandateRoles.ts b/src/hooks/useMandateRoles.ts index bf6ccbc..ba425cb 100644 --- a/src/hooks/useMandateRoles.ts +++ b/src/hooks/useMandateRoles.ts @@ -107,8 +107,9 @@ export function useMandateRoles() { if (Object.keys(paginationWithoutScope).length > 0) { queryParams.pagination = JSON.stringify(paginationWithoutScope); } - // Include templates by default for mandate roles view - queryParams.includeTemplates = 'true'; + // Do NOT include feature template roles - they belong to Feature-Rollen page + // According to admin_ui_concept.md: Filter: featureCode=null AND featureInstanceId=null + queryParams.includeTemplates = 'false'; // Include mandate-specific roles for the selected mandate if (mandateId) { queryParams.mandateId = mandateId; diff --git a/src/pages/admin/AdminFeatureInstanceUsersPage.tsx b/src/pages/admin/AdminFeatureInstanceUsersPage.tsx index 5a520ab..e026b91 100644 --- a/src/pages/admin/AdminFeatureInstanceUsersPage.tsx +++ b/src/pages/admin/AdminFeatureInstanceUsersPage.tsx @@ -7,7 +7,7 @@ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { useFeatureAccess, type FeatureAccessUser, type FeatureInstanceRole, type PaginationParams, type PaginationMetadata } from '../../hooks/useFeatureAccess'; -import { useUserMandates, type Mandate } from '../../hooks/useUserMandates'; +import { useUserMandates } from '../../hooks/useUserMandates'; import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable'; import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm'; import { FaPlus, FaSync, FaUsers, FaBuilding, FaCube } from 'react-icons/fa'; @@ -22,7 +22,6 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { loading, error, fetchFeatures, - fetchInstances, fetchInstanceUsers, addUserToInstance, removeUserFromInstance, @@ -33,10 +32,19 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { const { fetchMandates } = useUserMandates(); const { showSuccess, showError } = useToast(); + // Combined instance option type + interface CombinedInstanceOption { + mandateId: string; + instanceId: string; + mandateName: string; + instanceLabel: string; + featureCode: string; + combinedKey: string; // mandateId:instanceId for unique identification + } + // State - const [mandates, setMandates] = useState([]); - const [selectedMandateId, setSelectedMandateId] = useState(''); - const [selectedInstanceId, setSelectedInstanceId] = useState(''); + const [combinedOptions, setCombinedOptions] = useState([]); + const [selectedCombinedKey, setSelectedCombinedKey] = useState(''); const [instanceUsers, setInstanceUsers] = useState([]); const [instanceRoles, setInstanceRoles] = useState([]); const [allUsers, setAllUsers] = useState>([]); @@ -46,22 +54,63 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { const [usersLoading, setUsersLoading] = useState(false); const [usersPagination, setUsersPagination] = useState(null); - // Load mandates and features on mount + // Extract mandateId and instanceId from combined key + const selectedMandateId = useMemo(() => { + if (!selectedCombinedKey) return ''; + return selectedCombinedKey.split(':')[0] || ''; + }, [selectedCombinedKey]); + + const selectedInstanceId = useMemo(() => { + if (!selectedCombinedKey) return ''; + return selectedCombinedKey.split(':')[1] || ''; + }, [selectedCombinedKey]); + + // Load mandates and features on mount, then build combined options useEffect(() => { fetchFeatures(); - fetchMandates().then(setMandates); + const loadCombinedOptions = async () => { + const loadedMandates = await fetchMandates(); + + // Load instances for all mandates in parallel + const allOptions: CombinedInstanceOption[] = []; + + for (const mandate of loadedMandates) { + try { + const response = await api.get('/api/features/instances', { + headers: { 'X-Mandate-Id': mandate.id } + }); + const instanceList = response.data?.items || response.data || []; + + if (Array.isArray(instanceList)) { + for (const inst of instanceList) { + allOptions.push({ + mandateId: mandate.id, + instanceId: inst.id, + mandateName: typeof mandate.name === 'string' ? mandate.name : (mandate.name?.de || mandate.name?.en || Object.values(mandate.name || {})[0] || mandate.id), + instanceLabel: inst.label || inst.id, + featureCode: inst.featureCode, + combinedKey: `${mandate.id}:${inst.id}`, + }); + } + } + } catch (err) { + console.error(`Error loading instances for mandate ${mandate.id}:`, err); + } + } + + // Sort by mandate name, then by instance label + allOptions.sort((a, b) => { + const mandateCompare = a.mandateName.localeCompare(b.mandateName); + if (mandateCompare !== 0) return mandateCompare; + return a.instanceLabel.localeCompare(b.instanceLabel); + }); + + setCombinedOptions(allOptions); + }; + + loadCombinedOptions(); }, [fetchFeatures, fetchMandates]); - // Load instances when mandate changes - useEffect(() => { - if (selectedMandateId) { - fetchInstances(selectedMandateId); - setSelectedInstanceId(''); - setInstanceUsers([]); - setInstanceRoles([]); - } - }, [selectedMandateId, fetchInstances]); - // Load users and roles when instance changes useEffect(() => { if (selectedMandateId && selectedInstanceId) { @@ -297,14 +346,6 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { setEditingUser(user); }; - // Get mandate name - const getMandateName = (mandate: Mandate) => { - if (typeof mandate.name === 'object') { - return mandate.name.de || mandate.name.en || Object.values(mandate.name)[0] || mandate.id; - } - return mandate.name || mandate.id; - }; - // Get feature label const getFeatureLabel = (code: string) => { const feature = features.find(f => f.code === code); @@ -316,12 +357,24 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { return code; }; - // Get selected instance info + // Get selected instance info from combined options const selectedInstance = useMemo(() => { - return instances.find(i => i.id === selectedInstanceId); - }, [instances, selectedInstanceId]); + const option = combinedOptions.find(o => o.combinedKey === selectedCombinedKey); + if (!option) return null; + return instances.find(i => i.id === option.instanceId) || { + id: option.instanceId, + label: option.instanceLabel, + featureCode: option.featureCode, + mandateId: option.mandateId, + }; + }, [combinedOptions, selectedCombinedKey, instances]); - if (error && !selectedMandateId) { + // Get selected combined option for display + const selectedOption = useMemo(() => { + return combinedOptions.find(o => o.combinedKey === selectedCombinedKey); + }, [combinedOptions, selectedCombinedKey]); + + if (error && !selectedCombinedKey) { return (
@@ -344,50 +397,44 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
- {/* Selectors */} + {/* Combined Selector: Mandate + Feature Instance */}
-
+
- - {selectedMandateId && ( -
- - -
- )} - {selectedInstanceId && ( + {selectedCombinedKey && (
{/* Info box when instance is selected */} + {selectedOption && ( +
+ + Mandant: {selectedOption.mandateName} + | + + Instanz: {selectedOption.instanceLabel} ({selectedOption.featureCode}) +
+ )} + + {/* Roles info box */} {selectedInstance && instanceRoles.length > 0 && (
- Verfügbare Rollen: {instanceRoles.map((r, i) => ( @@ -430,21 +487,13 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => { )} {/* Content */} - {!selectedMandateId ? ( -
- -

Kein Mandant ausgewählt

-

- Wählen Sie einen Mandanten aus, um dessen Feature-Instanzen zu sehen. -

-
- ) : !selectedInstanceId ? ( + {!selectedCombinedKey ? (

Keine Feature-Instanz ausgewählt

- {instances.length === 0 - ? 'Dieser Mandant hat noch keine Feature-Instanzen.' + {combinedOptions.length === 0 + ? 'Es gibt noch keine Feature-Instanzen. Erstellen Sie zuerst Feature-Instanzen unter "Feature-Instanzen".' : 'Wählen Sie eine Feature-Instanz aus, um deren Benutzer zu verwalten.'}

diff --git a/src/pages/admin/AdminFeatureRolesPage.tsx b/src/pages/admin/AdminFeatureRolesPage.tsx index 3bfa17f..c72fb28 100644 --- a/src/pages/admin/AdminFeatureRolesPage.tsx +++ b/src/pages/admin/AdminFeatureRolesPage.tsx @@ -18,10 +18,13 @@ import api from '../../api'; import styles from './Admin.module.css'; interface Feature { - id: string; - featureCode: string; - name: string | { [key: string]: string }; + id?: string; + code: string; // Backend uses 'code' not 'featureCode' + featureCode?: string; // Alias for backward compatibility + label: string | { [key: string]: string }; // Backend uses 'label' not 'name' + name?: string | { [key: string]: string }; // Alias for backward compatibility description?: string | { [key: string]: string }; + icon?: string; } interface FeatureRole { @@ -49,13 +52,13 @@ export const AdminFeatureRolesPage: React.FC = () => { useEffect(() => { const loadFeatures = async () => { try { - const response = await api.get('/api/features'); + const response = await api.get('/api/features/'); const featureList = response.data?.items || response.data || []; setFeatures(Array.isArray(featureList) ? featureList : []); // Auto-select first feature if available if (featureList.length > 0 && !selectedFeatureCode) { - setSelectedFeatureCode(featureList[0].featureCode); + setSelectedFeatureCode(featureList[0].code || featureList[0].featureCode || ''); } } catch (err: any) { console.error('Error loading features:', err); @@ -235,8 +238,8 @@ export const AdminFeatureRolesPage: React.FC = () => { setEditingRole(role); }; - // Get feature name - const getFeatureName = (feature: Feature) => getTextValue(feature.name); + // Get feature name - Backend uses 'label' field + const getFeatureName = (feature: Feature) => getTextValue(feature.label || feature.name); if (error && !selectedFeatureCode) { return ( @@ -274,11 +277,14 @@ export const AdminFeatureRolesPage: React.FC = () => { onChange={(e) => setSelectedFeatureCode(e.target.value)} > - {features.map(f => ( - - ))} + {features.map(f => { + const featureCode = f.code || f.featureCode || ''; + return ( + + ); + })}