fixed stricts

This commit is contained in:
ValueOn AG 2026-02-13 11:25:37 +01:00
parent 97a164f682
commit 92bf11c809
14 changed files with 556 additions and 316 deletions

View file

@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import { IoIosDownload } from 'react-icons/io'; import { IoIosDownload } from 'react-icons/io';
import { Popup, PopupAction } from '../UiComponents/Popup/Popup'; import { Popup, PopupAction } from '../UiComponents/Popup/Popup';
import { useLanguage } from '../../providers/language/LanguageContext'; import { useLanguage } from '../../providers/language/LanguageContext';
import { PdfRenderer, PdfJsRenderer, LoadingRenderer, ErrorRenderer } from './renderers'; import { PdfRenderer, LoadingRenderer } from './renderers';
import styles from './ContentPreview.module.css'; import styles from './ContentPreview.module.css';
export interface UrlContentPreviewProps { export interface UrlContentPreviewProps {
@ -64,11 +64,8 @@ export function UrlContentPreview({
} }
}; };
const handlePdfLoad = () => { // PDF load is handled by the PdfRenderer's onError callback;
setIsLoading(false); // successful load is implicit when no error occurs.
setHasLoaded(true);
setError(null);
};
const handlePdfError = () => { const handlePdfError = () => {
// Try PDF.js as fallback instead of showing error immediately // Try PDF.js as fallback instead of showing error immediately
@ -208,7 +205,6 @@ export function UrlContentPreview({
previewUrl={url} previewUrl={url}
fileName={fileName} fileName={fileName}
onError={handlePdfError} onError={handlePdfError}
onLoad={handlePdfLoad}
/> />
</div> </div>
</div> </div>

View file

@ -1,3 +1,5 @@
export { ContentPreview } from './ContentPreview'; export { ContentPreview } from './ContentPreview';
export type { ContentPreviewProps } from './ContentPreview'; export type { ContentPreviewProps } from './ContentPreview';
export { UrlContentPreview } from './UrlContentPreview';
export type { UrlContentPreviewProps } from './UrlContentPreview';

View file

@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
// @ts-ignore
import * as pdfjsLib from 'pdfjs-dist'; import * as pdfjsLib from 'pdfjs-dist';
import styles from '../ContentPreview.module.css'; import styles from '../ContentPreview.module.css';
@ -23,7 +24,7 @@ interface PdfJsRendererProps {
onLoad?: () => void; onLoad?: () => void;
} }
export function PdfJsRenderer({ previewUrl, fileName, onError, onLoad }: PdfJsRendererProps) { export function PdfJsRenderer({ previewUrl, fileName: _fileName, onError, onLoad }: PdfJsRendererProps) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);

View file

@ -1,10 +1,12 @@
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import TextField, { BaseTextFieldProps } from '../TextField/TextField'; import TextField from '../TextField/TextField';
import { BaseTextFieldProps } from '../TextField/TextFieldTypes';
import { autocompleteAddress, AddressSuggestion } from '../../../api/realEstateApi'; import { autocompleteAddress, AddressSuggestion } from '../../../api/realEstateApi';
import styles from './AddressAutocomplete.module.css'; import styles from './AddressAutocomplete.module.css';
interface AddressAutocompleteProps extends BaseTextFieldProps { interface AddressAutocompleteProps extends BaseTextFieldProps {
onSelect?: (suggestion: AddressSuggestion) => void; onSelect?: (suggestion: AddressSuggestion) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
debounceMs?: number; debounceMs?: number;
minQueryLength?: number; minQueryLength?: number;
maxSuggestions?: number; maxSuggestions?: number;

View file

@ -0,0 +1 @@
export { OerebSection } from './OerebSection';

View file

@ -414,7 +414,7 @@ const ParcelInfoPanel: React.FC<ParcelInfoPanelProps> = ({
<div className={styles.documentsSection}> <div className={styles.documentsSection}>
<h4 className={styles.documentsSectionTitle}>Dokumente ({parcelData.documents.length})</h4> <h4 className={styles.documentsSectionTitle}>Dokumente ({parcelData.documents.length})</h4>
<div className={styles.documentsList}> <div className={styles.documentsList}>
{parcelData.documents.map((document) => ( {parcelData.documents.map((document: any) => (
<button <button
key={document.id} key={document.id}
className={styles.documentLink} className={styles.documentLink}

View file

@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchAccess as fetchAccessApi, fetchAccess as fetchAccessApi,
fetchAccessById as fetchAccessByIdApi, fetchAccessById as fetchAccessByIdApi,
@ -10,15 +10,38 @@ import {
updateAccess as updateAccessApi, updateAccess as updateAccessApi,
deleteAccess as deleteAccessApi, deleteAccess as deleteAccessApi,
type TrusteeAccess, type TrusteeAccess,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteeAccess, AttributeDefinition, PaginationParams }; export type { TrusteeAccess, PaginationParams };
// Access list hook // Access list hook
export function useTrusteeAccess() { export function useTrusteeAccess() {
const instanceId = useInstanceId();
const [accessRecords, setAccessRecords] = useState<TrusteeAccess[]>([]); const [accessRecords, setAccessRecords] = useState<TrusteeAccess[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -33,8 +56,10 @@ export function useTrusteeAccess() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteeAccess'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeAccess`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -58,12 +83,13 @@ export function useTrusteeAccess() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.access'); const objectKey = 'data.feature.trustee.TrusteeAccess';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -81,8 +107,13 @@ export function useTrusteeAccess() {
}, [checkPermission]); }, [checkPermission]);
const fetchAccess = useCallback(async (params?: PaginationParams) => { const fetchAccess = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setAccessRecords([]);
return;
}
try { try {
const data = await fetchAccessApi(request, params); const data = await fetchAccessApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -99,7 +130,7 @@ export function useTrusteeAccess() {
setAccessRecords([]); setAccessRecords([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove an access record // Optimistically remove an access record
const removeOptimistically = (accessId: string) => { const removeOptimistically = (accessId: string) => {
@ -119,8 +150,9 @@ export function useTrusteeAccess() {
// Fetch a single access record by ID // Fetch a single access record by ID
const fetchAccessById = useCallback(async (accessId: string): Promise<TrusteeAccess | null> => { const fetchAccessById = useCallback(async (accessId: string): Promise<TrusteeAccess | null> => {
return await fetchAccessByIdApi(request, accessId); if (!instanceId) return null;
}, [request]); return await fetchAccessByIdApi(request, instanceId, accessId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -161,7 +193,7 @@ export function useTrusteeAccess() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -223,16 +255,14 @@ export function useTrusteeAccess() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchAccess();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchAccess]);
fetchAccess();
}, [fetchAccess]);
return { return {
accessRecords, accessRecords,
@ -246,12 +276,15 @@ export function useTrusteeAccess() {
pagination, pagination,
fetchAccessById, fetchAccessById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Access operations hook // Access operations hook
export function useTrusteeAccessOperations() { export function useTrusteeAccessOperations() {
const instanceId = useInstanceId();
const [deletingAccess, setDeletingAccess] = useState<Set<string>>(new Set()); const [deletingAccess, setDeletingAccess] = useState<Set<string>>(new Set());
const [creatingAccess, setCreatingAccess] = useState(false); const [creatingAccess, setCreatingAccess] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -260,11 +293,16 @@ export function useTrusteeAccessOperations() {
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const handleAccessDelete = async (accessId: string) => { const handleAccessDelete = async (accessId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingAccess(prev => new Set(prev).add(accessId)); setDeletingAccess(prev => new Set(prev).add(accessId));
try { try {
await deleteAccessApi(request, accessId); await deleteAccessApi(request, instanceId, accessId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -280,20 +318,16 @@ export function useTrusteeAccessOperations() {
}; };
const handleAccessCreate = async (accessData: Partial<TrusteeAccess>) => { const handleAccessCreate = async (accessData: Partial<TrusteeAccess>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingAccess(true); setCreatingAccess(true);
try { try {
const currentUserData = getUserDataCache(); const newAccess = await createAccessApi(request, instanceId, accessData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...accessData,
mandate: mandateId
};
const newAccess = await createAccessApi(request, requestBody);
return { success: true, accessData: newAccess }; return { success: true, accessData: newAccess };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -308,19 +342,15 @@ export function useTrusteeAccessOperations() {
updateData: Partial<TrusteeAccess>, updateData: Partial<TrusteeAccess>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedAccess = await updateAccessApi(request, instanceId, accessId, updateData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedAccess = await updateAccessApi(request, accessId, requestBody);
return { success: true, accessData: updatedAccess }; return { success: true, accessData: updatedAccess };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update access'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update access';
@ -347,6 +377,7 @@ export function useTrusteeAccessOperations() {
handleAccessDelete, handleAccessDelete,
handleAccessCreate, handleAccessCreate,
handleAccessUpdate, handleAccessUpdate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchContracts as fetchContractsApi, fetchContracts as fetchContractsApi,
fetchContractById as fetchContractByIdApi, fetchContractById as fetchContractByIdApi,
@ -10,15 +10,38 @@ import {
updateContract as updateContractApi, updateContract as updateContractApi,
deleteContract as deleteContractApi, deleteContract as deleteContractApi,
type TrusteeContract, type TrusteeContract,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteeContract, AttributeDefinition, PaginationParams }; export type { TrusteeContract, PaginationParams };
// Contracts list hook // Contracts list hook
export function useTrusteeContracts() { export function useTrusteeContracts() {
const instanceId = useInstanceId();
const [contracts, setContracts] = useState<TrusteeContract[]>([]); const [contracts, setContracts] = useState<TrusteeContract[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -33,8 +56,10 @@ export function useTrusteeContracts() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteeContract'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeContract`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -58,12 +83,13 @@ export function useTrusteeContracts() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.contract'); const objectKey = 'data.feature.trustee.TrusteeContract';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -81,8 +107,13 @@ export function useTrusteeContracts() {
}, [checkPermission]); }, [checkPermission]);
const fetchContracts = useCallback(async (params?: PaginationParams) => { const fetchContracts = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setContracts([]);
return;
}
try { try {
const data = await fetchContractsApi(request, params); const data = await fetchContractsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -99,7 +130,7 @@ export function useTrusteeContracts() {
setContracts([]); setContracts([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove a contract // Optimistically remove a contract
const removeOptimistically = (contractId: string) => { const removeOptimistically = (contractId: string) => {
@ -119,8 +150,9 @@ export function useTrusteeContracts() {
// Fetch a single contract by ID // Fetch a single contract by ID
const fetchContractById = useCallback(async (contractId: string): Promise<TrusteeContract | null> => { const fetchContractById = useCallback(async (contractId: string): Promise<TrusteeContract | null> => {
return await fetchContractByIdApi(request, contractId); if (!instanceId) return null;
}, [request]); return await fetchContractByIdApi(request, instanceId, contractId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -161,7 +193,7 @@ export function useTrusteeContracts() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -231,16 +263,14 @@ export function useTrusteeContracts() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchContracts();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchContracts]);
fetchContracts();
}, [fetchContracts]);
return { return {
contracts, contracts,
@ -254,12 +284,15 @@ export function useTrusteeContracts() {
pagination, pagination,
fetchContractById, fetchContractById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Contract operations hook // Contract operations hook
export function useTrusteeContractOperations() { export function useTrusteeContractOperations() {
const instanceId = useInstanceId();
const [deletingContracts, setDeletingContracts] = useState<Set<string>>(new Set()); const [deletingContracts, setDeletingContracts] = useState<Set<string>>(new Set());
const [creatingContract, setCreatingContract] = useState(false); const [creatingContract, setCreatingContract] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -268,11 +301,16 @@ export function useTrusteeContractOperations() {
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const handleContractDelete = async (contractId: string) => { const handleContractDelete = async (contractId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingContracts(prev => new Set(prev).add(contractId)); setDeletingContracts(prev => new Set(prev).add(contractId));
try { try {
await deleteContractApi(request, contractId); await deleteContractApi(request, instanceId, contractId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -288,20 +326,16 @@ export function useTrusteeContractOperations() {
}; };
const handleContractCreate = async (contractData: Partial<TrusteeContract>) => { const handleContractCreate = async (contractData: Partial<TrusteeContract>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingContract(true); setCreatingContract(true);
try { try {
const currentUserData = getUserDataCache(); const newContract = await createContractApi(request, instanceId, contractData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...contractData,
mandate: mandateId
};
const newContract = await createContractApi(request, requestBody);
return { success: true, contractData: newContract }; return { success: true, contractData: newContract };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -316,21 +350,15 @@ export function useTrusteeContractOperations() {
updateData: Partial<TrusteeContract>, updateData: Partial<TrusteeContract>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedContract = await updateContractApi(request, instanceId, contractId, updateData);
const mandateId = currentUserData?.mandateId || '';
// Note: organisationId should NOT be included in update if immutable
// Backend will reject if organisationId is changed
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedContract = await updateContractApi(request, contractId, requestBody);
return { success: true, contractData: updatedContract }; return { success: true, contractData: updatedContract };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update contract'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update contract';
@ -357,6 +385,7 @@ export function useTrusteeContractOperations() {
handleContractDelete, handleContractDelete,
handleContractCreate, handleContractCreate,
handleContractUpdate, handleContractUpdate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,25 +1,47 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchDocuments as fetchDocumentsApi, fetchDocuments as fetchDocumentsApi,
fetchDocumentById as fetchDocumentByIdApi, fetchDocumentById as fetchDocumentByIdApi,
createDocument as createDocumentApi, createDocument as createDocumentApi,
updateDocument as updateDocumentApi, updateDocument as updateDocumentApi,
deleteDocument as deleteDocumentApi, deleteDocument as deleteDocumentApi,
downloadDocumentData as downloadDocumentDataApi,
type TrusteeDocument, type TrusteeDocument,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteeDocument, AttributeDefinition, PaginationParams }; export type { TrusteeDocument, PaginationParams };
// Documents list hook // Documents list hook
export function useTrusteeDocuments() { export function useTrusteeDocuments() {
const instanceId = useInstanceId();
const [documents, setDocuments] = useState<TrusteeDocument[]>([]); const [documents, setDocuments] = useState<TrusteeDocument[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -34,8 +56,10 @@ export function useTrusteeDocuments() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteeDocument'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeDocument`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -59,12 +83,13 @@ export function useTrusteeDocuments() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.document'); const objectKey = 'data.feature.trustee.TrusteeDocument';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -82,8 +107,13 @@ export function useTrusteeDocuments() {
}, [checkPermission]); }, [checkPermission]);
const fetchDocuments = useCallback(async (params?: PaginationParams) => { const fetchDocuments = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setDocuments([]);
return;
}
try { try {
const data = await fetchDocumentsApi(request, params); const data = await fetchDocumentsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -100,7 +130,7 @@ export function useTrusteeDocuments() {
setDocuments([]); setDocuments([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove a document // Optimistically remove a document
const removeOptimistically = (documentId: string) => { const removeOptimistically = (documentId: string) => {
@ -120,8 +150,9 @@ export function useTrusteeDocuments() {
// Fetch a single document by ID // Fetch a single document by ID
const fetchDocumentById = useCallback(async (documentId: string): Promise<TrusteeDocument | null> => { const fetchDocumentById = useCallback(async (documentId: string): Promise<TrusteeDocument | null> => {
return await fetchDocumentByIdApi(request, documentId); if (!instanceId) return null;
}, [request]); return await fetchDocumentByIdApi(request, instanceId, documentId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -163,7 +194,7 @@ export function useTrusteeDocuments() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -222,16 +253,14 @@ export function useTrusteeDocuments() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchDocuments();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchDocuments]);
fetchDocuments();
}, [fetchDocuments]);
return { return {
documents, documents,
@ -245,12 +274,15 @@ export function useTrusteeDocuments() {
pagination, pagination,
fetchDocumentById, fetchDocumentById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Document operations hook // Document operations hook
export function useTrusteeDocumentOperations() { export function useTrusteeDocumentOperations() {
const instanceId = useInstanceId();
const [deletingDocuments, setDeletingDocuments] = useState<Set<string>>(new Set()); const [deletingDocuments, setDeletingDocuments] = useState<Set<string>>(new Set());
const [creatingDocument, setCreatingDocument] = useState(false); const [creatingDocument, setCreatingDocument] = useState(false);
const [downloadingDocuments, setDownloadingDocuments] = useState<Set<string>>(new Set()); const [downloadingDocuments, setDownloadingDocuments] = useState<Set<string>>(new Set());
@ -261,11 +293,16 @@ export function useTrusteeDocumentOperations() {
const [downloadError, setDownloadError] = useState<string | null>(null); const [downloadError, setDownloadError] = useState<string | null>(null);
const handleDocumentDelete = async (documentId: string) => { const handleDocumentDelete = async (documentId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingDocuments(prev => new Set(prev).add(documentId)); setDeletingDocuments(prev => new Set(prev).add(documentId));
try { try {
await deleteDocumentApi(request, documentId); await deleteDocumentApi(request, instanceId, documentId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -280,18 +317,17 @@ export function useTrusteeDocumentOperations() {
} }
}; };
const handleDocumentCreate = async (documentData: FormData) => { const handleDocumentCreate = async (documentData: Partial<TrusteeDocument>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingDocument(true); setCreatingDocument(true);
try { try {
const currentUserData = getUserDataCache(); const newDocument = await createDocumentApi(request, instanceId, documentData);
const mandateId = currentUserData?.mandateId || '';
documentData.append('mandate', mandateId);
const newDocument = await createDocumentApi(request, documentData);
return { success: true, documentData: newDocument }; return { success: true, documentData: newDocument };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -306,19 +342,15 @@ export function useTrusteeDocumentOperations() {
updateData: Partial<TrusteeDocument>, updateData: Partial<TrusteeDocument>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedDocument = await updateDocumentApi(request, instanceId, documentId, updateData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedDocument = await updateDocumentApi(request, documentId, requestBody);
return { success: true, documentData: updatedDocument }; return { success: true, documentData: updatedDocument };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update document'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update document';
@ -337,11 +369,28 @@ export function useTrusteeDocumentOperations() {
}; };
const handleDocumentDownload = async (documentId: string, documentName: string) => { const handleDocumentDownload = async (documentId: string, documentName: string) => {
if (!instanceId) {
setDownloadError('No instance context');
return false;
}
setDownloadError(null); setDownloadError(null);
setDownloadingDocuments(prev => new Set(prev).add(documentId)); setDownloadingDocuments(prev => new Set(prev).add(documentId));
try { try {
const blob = await downloadDocumentDataApi(request, documentId); const doc = await fetchDocumentByIdApi(request, instanceId, documentId);
if (!doc || !doc.documentData) {
throw new Error('Document data not found');
}
// Convert base64 to blob
const byteCharacters = atob(doc.documentData);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: doc.documentMimeType || 'application/octet-stream' });
// Create download link // Create download link
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
@ -379,6 +428,7 @@ export function useTrusteeDocumentOperations() {
handleDocumentCreate, handleDocumentCreate,
handleDocumentUpdate, handleDocumentUpdate,
handleDocumentDownload, handleDocumentDownload,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchOrganisations as fetchOrganisationsApi, fetchOrganisations as fetchOrganisationsApi,
fetchOrganisationById as fetchOrganisationByIdApi, fetchOrganisationById as fetchOrganisationByIdApi,
@ -10,15 +10,38 @@ import {
updateOrganisation as updateOrganisationApi, updateOrganisation as updateOrganisationApi,
deleteOrganisation as deleteOrganisationApi, deleteOrganisation as deleteOrganisationApi,
type TrusteeOrganisation, type TrusteeOrganisation,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteeOrganisation, AttributeDefinition, PaginationParams }; export type { TrusteeOrganisation, PaginationParams };
// Organisations list hook // Organisations list hook
export function useTrusteeOrganisations() { export function useTrusteeOrganisations() {
const instanceId = useInstanceId();
const [organisations, setOrganisations] = useState<TrusteeOrganisation[]>([]); const [organisations, setOrganisations] = useState<TrusteeOrganisation[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -33,8 +56,10 @@ export function useTrusteeOrganisations() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteeOrganisation'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeOrganisation`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -58,12 +83,13 @@ export function useTrusteeOrganisations() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.organisation'); const objectKey = 'data.feature.trustee.TrusteeOrganisation';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -81,8 +107,13 @@ export function useTrusteeOrganisations() {
}, [checkPermission]); }, [checkPermission]);
const fetchOrganisations = useCallback(async (params?: PaginationParams) => { const fetchOrganisations = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setOrganisations([]);
return;
}
try { try {
const data = await fetchOrganisationsApi(request, params); const data = await fetchOrganisationsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -99,7 +130,7 @@ export function useTrusteeOrganisations() {
setOrganisations([]); setOrganisations([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove an organisation // Optimistically remove an organisation
const removeOptimistically = (organisationId: string) => { const removeOptimistically = (organisationId: string) => {
@ -119,8 +150,9 @@ export function useTrusteeOrganisations() {
// Fetch a single organisation by ID // Fetch a single organisation by ID
const fetchOrganisationById = useCallback(async (organisationId: string): Promise<TrusteeOrganisation | null> => { const fetchOrganisationById = useCallback(async (organisationId: string): Promise<TrusteeOrganisation | null> => {
return await fetchOrganisationByIdApi(request, organisationId); if (!instanceId) return null;
}, [request]); return await fetchOrganisationByIdApi(request, instanceId, organisationId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -159,7 +191,7 @@ export function useTrusteeOrganisations() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -174,7 +206,7 @@ export function useTrusteeOrganisations() {
} else if (attr.type === 'multiselect') { } else if (attr.type === 'multiselect') {
fieldType = 'multiselect'; fieldType = 'multiselect';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -239,16 +271,14 @@ export function useTrusteeOrganisations() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchOrganisations();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchOrganisations]);
fetchOrganisations();
}, [fetchOrganisations]);
return { return {
organisations, organisations,
@ -262,12 +292,15 @@ export function useTrusteeOrganisations() {
pagination, pagination,
fetchOrganisationById, fetchOrganisationById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Organisation operations hook // Organisation operations hook
export function useTrusteeOrganisationOperations() { export function useTrusteeOrganisationOperations() {
const instanceId = useInstanceId();
const [deletingOrganisations, setDeletingOrganisations] = useState<Set<string>>(new Set()); const [deletingOrganisations, setDeletingOrganisations] = useState<Set<string>>(new Set());
const [creatingOrganisation, setCreatingOrganisation] = useState(false); const [creatingOrganisation, setCreatingOrganisation] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -276,11 +309,16 @@ export function useTrusteeOrganisationOperations() {
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const handleOrganisationDelete = async (organisationId: string) => { const handleOrganisationDelete = async (organisationId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingOrganisations(prev => new Set(prev).add(organisationId)); setDeletingOrganisations(prev => new Set(prev).add(organisationId));
try { try {
await deleteOrganisationApi(request, organisationId); await deleteOrganisationApi(request, instanceId, organisationId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -296,20 +334,16 @@ export function useTrusteeOrganisationOperations() {
}; };
const handleOrganisationCreate = async (organisationData: Partial<TrusteeOrganisation>) => { const handleOrganisationCreate = async (organisationData: Partial<TrusteeOrganisation>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingOrganisation(true); setCreatingOrganisation(true);
try { try {
const currentUserData = getUserDataCache(); const newOrganisation = await createOrganisationApi(request, instanceId, organisationData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...organisationData,
mandate: mandateId
};
const newOrganisation = await createOrganisationApi(request, requestBody);
return { success: true, organisationData: newOrganisation }; return { success: true, organisationData: newOrganisation };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -324,19 +358,15 @@ export function useTrusteeOrganisationOperations() {
updateData: Partial<TrusteeOrganisation>, updateData: Partial<TrusteeOrganisation>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedOrganisation = await updateOrganisationApi(request, instanceId, organisationId, updateData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedOrganisation = await updateOrganisationApi(request, organisationId, requestBody);
return { success: true, organisationData: updatedOrganisation }; return { success: true, organisationData: updatedOrganisation };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update organisation'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update organisation';
@ -363,6 +393,7 @@ export function useTrusteeOrganisationOperations() {
handleOrganisationDelete, handleOrganisationDelete,
handleOrganisationCreate, handleOrganisationCreate,
handleOrganisationUpdate, handleOrganisationUpdate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,23 +1,46 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchPositionDocuments as fetchPositionDocumentsApi, fetchPositionDocuments as fetchPositionDocumentsApi,
fetchPositionDocumentById as fetchPositionDocumentByIdApi, fetchPositionDocumentById as fetchPositionDocumentByIdApi,
createPositionDocument as createPositionDocumentApi, createPositionDocument as createPositionDocumentApi,
deletePositionDocument as deletePositionDocumentApi, deletePositionDocument as deletePositionDocumentApi,
type TrusteePositionDocument, type TrusteePositionDocument,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteePositionDocument, AttributeDefinition, PaginationParams }; export type { TrusteePositionDocument, PaginationParams };
// Position-Documents list hook // Position-Documents list hook
export function useTrusteePositionDocuments() { export function useTrusteePositionDocuments() {
const instanceId = useInstanceId();
const [positionDocuments, setPositionDocuments] = useState<TrusteePositionDocument[]>([]); const [positionDocuments, setPositionDocuments] = useState<TrusteePositionDocument[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -32,8 +55,10 @@ export function useTrusteePositionDocuments() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteePositionDocument'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteePositionDocument`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -57,12 +82,13 @@ export function useTrusteePositionDocuments() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.xpositiondocument'); const objectKey = 'data.feature.trustee.TrusteePositionDocument';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -80,8 +106,13 @@ export function useTrusteePositionDocuments() {
}, [checkPermission]); }, [checkPermission]);
const fetchPositionDocuments = useCallback(async (params?: PaginationParams) => { const fetchPositionDocuments = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setPositionDocuments([]);
return;
}
try { try {
const data = await fetchPositionDocumentsApi(request, params); const data = await fetchPositionDocumentsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -98,7 +129,7 @@ export function useTrusteePositionDocuments() {
setPositionDocuments([]); setPositionDocuments([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove a position-document link // Optimistically remove a position-document link
const removeOptimistically = (positionDocumentId: string) => { const removeOptimistically = (positionDocumentId: string) => {
@ -107,8 +138,9 @@ export function useTrusteePositionDocuments() {
// Fetch a single position-document by ID // Fetch a single position-document by ID
const fetchPositionDocumentById = useCallback(async (positionDocumentId: string): Promise<TrusteePositionDocument | null> => { const fetchPositionDocumentById = useCallback(async (positionDocumentId: string): Promise<TrusteePositionDocument | null> => {
return await fetchPositionDocumentByIdApi(request, positionDocumentId); if (!instanceId) return null;
}, [request]); return await fetchPositionDocumentByIdApi(request, instanceId, positionDocumentId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -149,7 +181,7 @@ export function useTrusteePositionDocuments() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -212,16 +244,14 @@ export function useTrusteePositionDocuments() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchPositionDocuments();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchPositionDocuments]);
fetchPositionDocuments();
}, [fetchPositionDocuments]);
return { return {
positionDocuments, positionDocuments,
@ -234,12 +264,15 @@ export function useTrusteePositionDocuments() {
pagination, pagination,
fetchPositionDocumentById, fetchPositionDocumentById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Position-Document operations hook // Position-Document operations hook
export function useTrusteePositionDocumentOperations() { export function useTrusteePositionDocumentOperations() {
const instanceId = useInstanceId();
const [deletingPositionDocuments, setDeletingPositionDocuments] = useState<Set<string>>(new Set()); const [deletingPositionDocuments, setDeletingPositionDocuments] = useState<Set<string>>(new Set());
const [creatingPositionDocument, setCreatingPositionDocument] = useState(false); const [creatingPositionDocument, setCreatingPositionDocument] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -247,11 +280,16 @@ export function useTrusteePositionDocumentOperations() {
const [createError, setCreateError] = useState<string | null>(null); const [createError, setCreateError] = useState<string | null>(null);
const handlePositionDocumentDelete = async (positionDocumentId: string) => { const handlePositionDocumentDelete = async (positionDocumentId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingPositionDocuments(prev => new Set(prev).add(positionDocumentId)); setDeletingPositionDocuments(prev => new Set(prev).add(positionDocumentId));
try { try {
await deletePositionDocumentApi(request, positionDocumentId); await deletePositionDocumentApi(request, instanceId, positionDocumentId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -267,20 +305,16 @@ export function useTrusteePositionDocumentOperations() {
}; };
const handlePositionDocumentCreate = async (positionDocumentData: Partial<TrusteePositionDocument>) => { const handlePositionDocumentCreate = async (positionDocumentData: Partial<TrusteePositionDocument>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingPositionDocument(true); setCreatingPositionDocument(true);
try { try {
const currentUserData = getUserDataCache(); const newPositionDocument = await createPositionDocumentApi(request, instanceId, positionDocumentData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...positionDocumentData,
mandate: mandateId
};
const newPositionDocument = await createPositionDocumentApi(request, requestBody);
return { success: true, positionDocumentData: newPositionDocument }; return { success: true, positionDocumentData: newPositionDocument };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -297,6 +331,7 @@ export function useTrusteePositionDocumentOperations() {
createError, createError,
handlePositionDocumentDelete, handlePositionDocumentDelete,
handlePositionDocumentCreate, handlePositionDocumentCreate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchPositions as fetchPositionsApi, fetchPositions as fetchPositionsApi,
fetchPositionById as fetchPositionByIdApi, fetchPositionById as fetchPositionByIdApi,
@ -10,15 +10,38 @@ import {
updatePosition as updatePositionApi, updatePosition as updatePositionApi,
deletePosition as deletePositionApi, deletePosition as deletePositionApi,
type TrusteePosition, type TrusteePosition,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteePosition, AttributeDefinition, PaginationParams }; export type { TrusteePosition, PaginationParams };
// Positions list hook // Positions list hook
export function useTrusteePositions() { export function useTrusteePositions() {
const instanceId = useInstanceId();
const [positions, setPositions] = useState<TrusteePosition[]>([]); const [positions, setPositions] = useState<TrusteePosition[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -33,8 +56,10 @@ export function useTrusteePositions() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteePosition'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteePosition`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -58,12 +83,13 @@ export function useTrusteePositions() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.position'); const objectKey = 'data.feature.trustee.TrusteePosition';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -81,8 +107,13 @@ export function useTrusteePositions() {
}, [checkPermission]); }, [checkPermission]);
const fetchPositions = useCallback(async (params?: PaginationParams) => { const fetchPositions = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setPositions([]);
return;
}
try { try {
const data = await fetchPositionsApi(request, params); const data = await fetchPositionsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -99,7 +130,7 @@ export function useTrusteePositions() {
setPositions([]); setPositions([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove a position // Optimistically remove a position
const removeOptimistically = (positionId: string) => { const removeOptimistically = (positionId: string) => {
@ -119,8 +150,9 @@ export function useTrusteePositions() {
// Fetch a single position by ID // Fetch a single position by ID
const fetchPositionById = useCallback(async (positionId: string): Promise<TrusteePosition | null> => { const fetchPositionById = useCallback(async (positionId: string): Promise<TrusteePosition | null> => {
return await fetchPositionByIdApi(request, positionId); if (!instanceId) return null;
}, [request]); return await fetchPositionByIdApi(request, instanceId, positionId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically with MwSt calculation logic // Generate edit fields from attributes dynamically with MwSt calculation logic
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -175,7 +207,7 @@ export function useTrusteePositions() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -288,16 +320,14 @@ export function useTrusteePositions() {
return fetchedAttributes; return fetchedAttributes;
}, [attributes, fetchAttributes]); }, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount // Fetch data when instanceId is available
useEffect(() => { useEffect(() => {
fetchAttributes(); if (instanceId) {
fetchPermissions(); fetchAttributes();
}, [fetchAttributes, fetchPermissions]); fetchPermissions();
fetchPositions();
// Initial fetch }
useEffect(() => { }, [instanceId, fetchAttributes, fetchPermissions, fetchPositions]);
fetchPositions();
}, [fetchPositions]);
return { return {
positions, positions,
@ -311,12 +341,15 @@ export function useTrusteePositions() {
pagination, pagination,
fetchPositionById, fetchPositionById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Position operations hook // Position operations hook
export function useTrusteePositionOperations() { export function useTrusteePositionOperations() {
const instanceId = useInstanceId();
const [deletingPositions, setDeletingPositions] = useState<Set<string>>(new Set()); const [deletingPositions, setDeletingPositions] = useState<Set<string>>(new Set());
const [creatingPosition, setCreatingPosition] = useState(false); const [creatingPosition, setCreatingPosition] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -325,11 +358,16 @@ export function useTrusteePositionOperations() {
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const handlePositionDelete = async (positionId: string) => { const handlePositionDelete = async (positionId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingPositions(prev => new Set(prev).add(positionId)); setDeletingPositions(prev => new Set(prev).add(positionId));
try { try {
await deletePositionApi(request, positionId); await deletePositionApi(request, instanceId, positionId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -345,20 +383,16 @@ export function useTrusteePositionOperations() {
}; };
const handlePositionCreate = async (positionData: Partial<TrusteePosition>) => { const handlePositionCreate = async (positionData: Partial<TrusteePosition>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingPosition(true); setCreatingPosition(true);
try { try {
const currentUserData = getUserDataCache(); const newPosition = await createPositionApi(request, instanceId, positionData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...positionData,
mandate: mandateId
};
const newPosition = await createPositionApi(request, requestBody);
return { success: true, positionData: newPosition }; return { success: true, positionData: newPosition };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -373,19 +407,15 @@ export function useTrusteePositionOperations() {
updateData: Partial<TrusteePosition>, updateData: Partial<TrusteePosition>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedPosition = await updatePositionApi(request, instanceId, positionId, updateData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedPosition = await updatePositionApi(request, positionId, requestBody);
return { success: true, positionData: updatedPosition }; return { success: true, positionData: updatedPosition };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update position'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update position';
@ -412,6 +442,7 @@ export function useTrusteePositionOperations() {
handlePositionDelete, handlePositionDelete,
handlePositionCreate, handlePositionCreate,
handlePositionUpdate, handlePositionUpdate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -1,8 +1,8 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi'; import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api'; import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions'; import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import { import {
fetchRoles as fetchRolesApi, fetchRoles as fetchRolesApi,
fetchRoleById as fetchRoleByIdApi, fetchRoleById as fetchRoleByIdApi,
@ -10,15 +10,38 @@ import {
updateRole as updateRoleApi, updateRole as updateRoleApi,
deleteRole as deleteRoleApi, deleteRole as deleteRoleApi,
type TrusteeRole, type TrusteeRole,
type AttributeDefinition,
type PaginationParams type PaginationParams
} from '../api/trusteeApi'; } from '../api/trusteeApi';
export interface AttributeDefinition {
name: string;
type: 'text' | 'email' | 'date' | 'checkbox' | 'select' | 'multiselect' | 'number' | 'textarea' | 'timestamp' | 'file';
label: string;
description?: string;
required?: boolean;
default?: any;
options?: any[] | string;
readonly?: boolean;
editable?: boolean;
visible?: boolean;
order?: number;
sortable?: boolean;
filterable?: boolean;
searchable?: boolean;
width?: number;
minWidth?: number;
maxWidth?: number;
filterOptions?: string[];
dependsOn?: string;
}
// Re-export types // Re-export types
export type { TrusteeRole, AttributeDefinition, PaginationParams }; export type { TrusteeRole, PaginationParams };
// Roles list hook // Roles list hook
export function useTrusteeRoles() { export function useTrusteeRoles() {
const instanceId = useInstanceId();
const [roles, setRoles] = useState<TrusteeRole[]>([]); const [roles, setRoles] = useState<TrusteeRole[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]); const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null); const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -33,8 +56,10 @@ export function useTrusteeRoles() {
// Fetch attributes from backend // Fetch attributes from backend
const fetchAttributes = useCallback(async () => { const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try { try {
const response = await api.get('/api/attributes/TrusteeRole'); const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeRole`);
let attrs: AttributeDefinition[] = []; let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) { if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -58,12 +83,13 @@ export function useTrusteeRoles() {
setAttributes([]); setAttributes([]);
return []; return [];
} }
}, []); }, [instanceId]);
// Fetch permissions from backend // Fetch permissions from backend
const fetchPermissions = useCallback(async () => { const fetchPermissions = useCallback(async () => {
try { try {
const perms = await checkPermission('DATA', 'trustee.role'); const objectKey = 'data.feature.trustee.TrusteeRole';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms); setPermissions(perms);
return perms; return perms;
} catch (error: any) { } catch (error: any) {
@ -81,8 +107,13 @@ export function useTrusteeRoles() {
}, [checkPermission]); }, [checkPermission]);
const fetchRoles = useCallback(async (params?: PaginationParams) => { const fetchRoles = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setRoles([]);
return;
}
try { try {
const data = await fetchRolesApi(request, params); const data = await fetchRolesApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) { if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : []; const items = Array.isArray(data.items) ? data.items : [];
@ -99,7 +130,7 @@ export function useTrusteeRoles() {
setRoles([]); setRoles([]);
setPagination(null); setPagination(null);
} }
}, [request]); }, [request, instanceId]);
// Optimistically remove a role // Optimistically remove a role
const removeOptimistically = (roleId: string) => { const removeOptimistically = (roleId: string) => {
@ -119,30 +150,11 @@ export function useTrusteeRoles() {
// Fetch a single role by ID // Fetch a single role by ID
const fetchRoleById = useCallback(async (roleId: string): Promise<TrusteeRole | null> => { const fetchRoleById = useCallback(async (roleId: string): Promise<TrusteeRole | null> => {
return await fetchRoleByIdApi(request, roleId); if (!instanceId) return null;
}, [request]); return await fetchRoleByIdApi(request, instanceId, roleId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically // Generate edit fields from attributes dynamically
// Ensure attributes are loaded
const ensureAttributesLoaded = useCallback(async () => {
if (attributes && attributes.length > 0) {
return attributes;
}
const fetchedAttributes = await fetchAttributes();
return fetchedAttributes;
}, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount
useEffect(() => {
fetchAttributes();
fetchPermissions();
}, [fetchAttributes, fetchPermissions]);
// Initial fetch
useEffect(() => {
fetchRoles();
}, [fetchRoles]);
const generateEditFieldsFromAttributes = useCallback((): Array<{ const generateEditFieldsFromAttributes = useCallback((): Array<{
key: string; key: string;
label: string; label: string;
@ -189,7 +201,7 @@ export function useTrusteeRoles() {
} else if (attr.type === 'select') { } else if (attr.type === 'select') {
fieldType = 'enum'; fieldType = 'enum';
if (Array.isArray(attr.options)) { if (Array.isArray(attr.options)) {
options = attr.options.map(opt => { options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string' const labelValue = typeof opt.label === 'string'
? opt.label ? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value); : opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -248,6 +260,24 @@ export function useTrusteeRoles() {
return editableFields; return editableFields;
}, [attributes]); }, [attributes]);
// Ensure attributes are loaded
const ensureAttributesLoaded = useCallback(async () => {
if (attributes && attributes.length > 0) {
return attributes;
}
const fetchedAttributes = await fetchAttributes();
return fetchedAttributes;
}, [attributes, fetchAttributes]);
// Fetch data when instanceId is available
useEffect(() => {
if (instanceId) {
fetchAttributes();
fetchPermissions();
fetchRoles();
}
}, [instanceId, fetchAttributes, fetchPermissions, fetchRoles]);
return { return {
roles, roles,
loading, loading,
@ -260,12 +290,15 @@ export function useTrusteeRoles() {
pagination, pagination,
fetchRoleById, fetchRoleById,
generateEditFieldsFromAttributes, generateEditFieldsFromAttributes,
ensureAttributesLoaded ensureAttributesLoaded,
instanceId
}; };
} }
// Role operations hook // Role operations hook
export function useTrusteeRoleOperations() { export function useTrusteeRoleOperations() {
const instanceId = useInstanceId();
const [deletingRoles, setDeletingRoles] = useState<Set<string>>(new Set()); const [deletingRoles, setDeletingRoles] = useState<Set<string>>(new Set());
const [creatingRole, setCreatingRole] = useState(false); const [creatingRole, setCreatingRole] = useState(false);
const { request, isLoading } = useApiRequest(); const { request, isLoading } = useApiRequest();
@ -274,11 +307,16 @@ export function useTrusteeRoleOperations() {
const [updateError, setUpdateError] = useState<string | null>(null); const [updateError, setUpdateError] = useState<string | null>(null);
const handleRoleDelete = async (roleId: string) => { const handleRoleDelete = async (roleId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null); setDeleteError(null);
setDeletingRoles(prev => new Set(prev).add(roleId)); setDeletingRoles(prev => new Set(prev).add(roleId));
try { try {
await deleteRoleApi(request, roleId); await deleteRoleApi(request, instanceId, roleId);
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
return true; return true;
} catch (error: any) { } catch (error: any) {
@ -296,20 +334,16 @@ export function useTrusteeRoleOperations() {
}; };
const handleRoleCreate = async (roleData: Partial<TrusteeRole>) => { const handleRoleCreate = async (roleData: Partial<TrusteeRole>) => {
if (!instanceId) {
setCreateError('No instance context');
return { success: false, error: 'No instance context' };
}
setCreateError(null); setCreateError(null);
setCreatingRole(true); setCreatingRole(true);
try { try {
const currentUserData = getUserDataCache(); const newRole = await createRoleApi(request, instanceId, roleData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...roleData,
mandate: mandateId
};
const newRole = await createRoleApi(request, requestBody);
return { success: true, roleData: newRole }; return { success: true, roleData: newRole };
} catch (error: any) { } catch (error: any) {
setCreateError(error.message); setCreateError(error.message);
@ -324,19 +358,15 @@ export function useTrusteeRoleOperations() {
updateData: Partial<TrusteeRole>, updateData: Partial<TrusteeRole>,
_originalData?: any _originalData?: any
) => { ) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null); setUpdateError(null);
try { try {
const currentUserData = getUserDataCache(); const updatedRole = await updateRoleApi(request, instanceId, roleId, updateData);
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedRole = await updateRoleApi(request, roleId, requestBody);
return { success: true, roleData: updatedRole }; return { success: true, roleData: updatedRole };
} catch (error: any) { } catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || 'Failed to update role'; const errorMessage = error.response?.data?.message || error.message || 'Failed to update role';
@ -363,6 +393,7 @@ export function useTrusteeRoleOperations() {
handleRoleDelete, handleRoleDelete,
handleRoleCreate, handleRoleCreate,
handleRoleUpdate, handleRoleUpdate,
isLoading isLoading,
instanceId
}; };
} }

View file

@ -11,7 +11,7 @@ export const TeamsbotSettingsView: React.FC = () => {
const { instance } = useCurrentInstance(); const { instance } = useCurrentInstance();
const instanceId = instance?.id || ''; const instanceId = instance?.id || '';
const [config, setConfig] = useState<TeamsbotConfig | null>(null); const [_config, setConfig] = useState<TeamsbotConfig | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);