/** * Features API * * API-Schicht für das Multi-Tenant Feature-System. * Hauptendpoint: GET /features/my - Lädt alle Mandate + Features + Instanzen + Permissions */ import api from '../api'; import type { FeaturesMyResponse, Mandate, MandateFeature, FeatureInstance, InstancePermissions, AccessLevel, } from '../types/mandate'; // ============================================================================= // MOCK DATA (Temporär bis Backend bereit) // ============================================================================= const MOCK_PERMISSIONS: InstancePermissions = { tables: { TrusteeOrganisation: { view: true, read: 'g', create: 'g', update: 'g', delete: 'n' }, TrusteeContract: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' }, TrusteeDocument: { view: true, read: 'g', create: 'g', update: 'm', delete: 'm' }, TrusteePosition: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' }, }, views: { 'trustee-dashboard': true, 'trustee-organisations': true, 'trustee-contracts': true, 'trustee-documents': true, 'trustee-positions': true, 'trustee-roles': true, 'trustee-access': true, }, }; const MOCK_CUSTOMER_PERMISSIONS: InstancePermissions = { tables: { TrusteeOrganisation: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' }, TrusteeContract: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' }, TrusteeDocument: { view: true, read: 'm', create: 'm', update: 'm', delete: 'n' }, TrusteePosition: { view: true, read: 'm', create: 'n', update: 'n', delete: 'n' }, }, views: { 'trustee-dashboard': true, 'trustee-contracts': true, 'trustee-documents': true, 'trustee-positions': true, 'trustee-organisations': false, 'trustee-roles': false, 'trustee-access': false, }, }; const MOCK_WORKFLOW_PERMISSIONS: InstancePermissions = { tables: { WorkflowRun: { view: true, read: 'g', create: 'g', update: 'm', delete: 'n' }, WorkflowFile: { view: true, read: 'g', create: 'g', update: 'm', delete: 'm' }, }, views: { 'chatworkflow-dashboard': true, 'chatworkflow-runs': true, 'chatworkflow-files': true, }, }; const MOCK_RESPONSE: FeaturesMyResponse = { mandates: [ { id: 'mand-soha', name: 'Soha Treuhand', code: 'soha', features: [ { code: 'trustee', label: { de: 'Treuhand', en: 'Trustee' }, icon: 'briefcase', instances: [ { id: 'inst-soha-pamo', featureCode: 'trustee', mandateId: 'mand-soha', mandateName: 'Soha Treuhand', instanceLabel: 'PamoCreate AG', userRoles: ['admin'], permissions: MOCK_PERMISSIONS, }, { id: 'inst-soha-valueon', featureCode: 'trustee', mandateId: 'mand-soha', mandateName: 'Soha Treuhand', instanceLabel: 'ValueOn AG', userRoles: ['customer'], permissions: MOCK_CUSTOMER_PERMISSIONS, }, ], }, { code: 'chatworkflow', label: { de: 'Workflow', en: 'Workflow' }, icon: 'play_circle', instances: [ { id: 'inst-soha-workflow', featureCode: 'chatworkflow', mandateId: 'mand-soha', mandateName: 'Soha Treuhand', instanceLabel: 'Beratung Dynamic', userRoles: ['user'], permissions: MOCK_WORKFLOW_PERMISSIONS, }, ], }, ], }, { id: 'mand-swiss', name: 'SwissTreu', code: 'swisstreu', features: [ { code: 'trustee', label: { de: 'Treuhand', en: 'Trustee' }, icon: 'briefcase', instances: [ { id: 'inst-swiss-firma-x', featureCode: 'trustee', mandateId: 'mand-swiss', mandateName: 'SwissTreu', instanceLabel: 'Firma X', userRoles: ['customer'], permissions: MOCK_CUSTOMER_PERMISSIONS, }, ], }, ], }, ], }; // Flag für Mock-Modus (auf false setzen wenn Backend bereit) const USE_MOCK = false; // ============================================================================= // API FUNCTIONS // ============================================================================= /** * Lädt alle Mandate + Features + Instanzen + Permissions für den aktuellen User * * Endpoint: GET /api/features/my * * Response enthält: * - Alle Mandanten zu denen der User Zugriff hat * - Pro Mandant: Alle Features mit deren Instanzen * - Pro Instanz: Summarische Berechtigungen (tables, views) */ export async function fetchMyFeatures(): Promise { if (USE_MOCK) { console.log('📦 featuresApi: Using MOCK data'); // Simuliere Netzwerk-Latenz await new Promise(resolve => setTimeout(resolve, 300)); return MOCK_RESPONSE; } try { console.log('📡 featuresApi: Fetching /api/features/my'); const response = await api.get('/api/features/my'); // Get the actual data (response.data contains the FeaturesMyResponse) const data = response.data; // DEBUG: Log all chatbot instances and their permissions console.log('🔍 [DEBUG] featuresApi: Full response received', { response, data, hasMandates: !!data?.mandates, mandateCount: data?.mandates?.length || 0, }); if (data?.mandates) { data.mandates.forEach(mandate => { mandate.features.forEach(feature => { if (feature.code === 'chatbot') { console.log('🔍 [DEBUG] featuresApi: Found chatbot feature', { mandateId: mandate.id, mandateName: mandate.label || mandate.name, featureCode: feature.code, instanceCount: feature.instances.length, }); feature.instances.forEach(instance => { console.log('🔍 [DEBUG] featuresApi: Chatbot Instance Details:', { instanceId: instance.id, instanceLabel: instance.instanceLabel, featureCode: instance.featureCode, userRoles: instance.userRoles, permissions: instance.permissions, views: instance.permissions?.views, viewKeys: instance.permissions?.views ? Object.keys(instance.permissions.views) : [], hasConversationsView: instance.permissions?.views?.['chatbot-conversations'] || instance.permissions?.views?.['ui.feature.chatbot.conversations'] || instance.permissions?.views?.['_all'], }); }); } }); }); } console.log('✅ featuresApi: Loaded features:', { mandateCount: data?.mandates?.length || 0, totalInstances: data?.mandates ?.flatMap(m => m.features) ?.flatMap(f => f.instances) ?.length || 0, }); return data; } catch (error) { console.error('❌ featuresApi: Error fetching features:', error); throw error; } } /** * Lädt die verfügbaren Features (für Admin - Feature-Instanz erstellen) * * Endpoint: GET /api/features/available */ export async function fetchAvailableFeatures(): Promise { if (USE_MOCK) { return [ { code: 'trustee', label: { de: 'Treuhand', en: 'Trustee' }, icon: 'briefcase', instances: [] }, { code: 'chatworkflow', label: { de: 'Workflow', en: 'Workflow' }, icon: 'play_circle', instances: [] }, { code: 'chatbot', label: { de: 'Chatbot', en: 'Chatbot' }, icon: 'chat', instances: [] }, ]; } const response = await api.get('/api/features/available'); return response.data; } // ============================================================================= // TYPE GUARDS // ============================================================================= export function isValidAccessLevel(value: string): value is AccessLevel { return ['n', 'm', 'g', 'a'].includes(value); } export function isValidMandate(obj: unknown): obj is Mandate { if (!obj || typeof obj !== 'object') return false; const mandate = obj as Record; return ( typeof mandate.id === 'string' && typeof mandate.name === 'string' && Array.isArray(mandate.features) ); } export function isValidFeatureInstance(obj: unknown): obj is FeatureInstance { if (!obj || typeof obj !== 'object') return false; const instance = obj as Record; return ( typeof instance.id === 'string' && typeof instance.featureCode === 'string' && typeof instance.mandateId === 'string' && typeof instance.instanceLabel === 'string' ); }