frontend_nyla/src/api/featuresApi.ts
2026-02-10 01:44:28 +01:00

274 lines
8.8 KiB
TypeScript

/**
* 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<FeaturesMyResponse> {
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<FeaturesMyResponse>('/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<MandateFeature[]> {
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<MandateFeature[]>('/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<string, unknown>;
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<string, unknown>;
return (
typeof instance.id === 'string' &&
typeof instance.featureCode === 'string' &&
typeof instance.mandateId === 'string' &&
typeof instance.instanceLabel === 'string'
);
}