ui-nyla/src/api/featuresApi.ts
2026-01-29 10:14:33 +01:00

233 lines
7.2 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');
console.log('✅ featuresApi: Loaded features:', {
mandateCount: response.data.mandates.length,
totalInstances: response.data.mandates
.flatMap(m => m.features)
.flatMap(f => f.instances)
.length,
});
return response.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'
);
}