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

View file

@ -1,3 +1,5 @@
export { ContentPreview } 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';
// @ts-ignore
import * as pdfjsLib from 'pdfjs-dist';
import styles from '../ContentPreview.module.css';
@ -23,7 +24,7 @@ interface PdfJsRendererProps {
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 containerRef = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(true);

View file

@ -1,10 +1,12 @@
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 styles from './AddressAutocomplete.module.css';
interface AddressAutocompleteProps extends BaseTextFieldProps {
onSelect?: (suggestion: AddressSuggestion) => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
debounceMs?: number;
minQueryLength?: 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}>
<h4 className={styles.documentsSectionTitle}>Dokumente ({parcelData.documents.length})</h4>
<div className={styles.documentsList}>
{parcelData.documents.map((document) => (
{parcelData.documents.map((document: any) => (
<button
key={document.id}
className={styles.documentLink}

View file

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

View file

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

View file

@ -1,25 +1,47 @@
import { useState, useEffect, useCallback } from 'react';
import { useApiRequest } from './useApi';
import { getUserDataCache } from '../utils/userCache';
import api from '../api';
import { usePermissions, type UserPermissions } from './usePermissions';
import { useInstanceId } from './useCurrentInstance';
import {
fetchDocuments as fetchDocumentsApi,
fetchDocumentById as fetchDocumentByIdApi,
createDocument as createDocumentApi,
updateDocument as updateDocumentApi,
deleteDocument as deleteDocumentApi,
downloadDocumentData as downloadDocumentDataApi,
type TrusteeDocument,
type AttributeDefinition,
type PaginationParams
} 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
export type { TrusteeDocument, AttributeDefinition, PaginationParams };
export type { TrusteeDocument, PaginationParams };
// Documents list hook
export function useTrusteeDocuments() {
const instanceId = useInstanceId();
const [documents, setDocuments] = useState<TrusteeDocument[]>([]);
const [attributes, setAttributes] = useState<AttributeDefinition[]>([]);
const [permissions, setPermissions] = useState<UserPermissions | null>(null);
@ -34,8 +56,10 @@ export function useTrusteeDocuments() {
// Fetch attributes from backend
const fetchAttributes = useCallback(async () => {
if (!instanceId) return [];
try {
const response = await api.get('/api/attributes/TrusteeDocument');
const response = await api.get(`/api/trustee/${instanceId}/attributes/TrusteeDocument`);
let attrs: AttributeDefinition[] = [];
if (response.data?.attributes && Array.isArray(response.data.attributes)) {
@ -59,12 +83,13 @@ export function useTrusteeDocuments() {
setAttributes([]);
return [];
}
}, []);
}, [instanceId]);
// Fetch permissions from backend
const fetchPermissions = useCallback(async () => {
try {
const perms = await checkPermission('DATA', 'trustee.document');
const objectKey = 'data.feature.trustee.TrusteeDocument';
const perms = await checkPermission('DATA', objectKey);
setPermissions(perms);
return perms;
} catch (error: any) {
@ -82,8 +107,13 @@ export function useTrusteeDocuments() {
}, [checkPermission]);
const fetchDocuments = useCallback(async (params?: PaginationParams) => {
if (!instanceId) {
setDocuments([]);
return;
}
try {
const data = await fetchDocumentsApi(request, params);
const data = await fetchDocumentsApi(request, instanceId, params);
if (data && typeof data === 'object' && 'items' in data) {
const items = Array.isArray(data.items) ? data.items : [];
@ -100,7 +130,7 @@ export function useTrusteeDocuments() {
setDocuments([]);
setPagination(null);
}
}, [request]);
}, [request, instanceId]);
// Optimistically remove a document
const removeOptimistically = (documentId: string) => {
@ -120,8 +150,9 @@ export function useTrusteeDocuments() {
// Fetch a single document by ID
const fetchDocumentById = useCallback(async (documentId: string): Promise<TrusteeDocument | null> => {
return await fetchDocumentByIdApi(request, documentId);
}, [request]);
if (!instanceId) return null;
return await fetchDocumentByIdApi(request, instanceId, documentId);
}, [request, instanceId]);
// Generate edit fields from attributes dynamically
const generateEditFieldsFromAttributes = useCallback((): Array<{
@ -163,7 +194,7 @@ export function useTrusteeDocuments() {
} else if (attr.type === 'select') {
fieldType = 'enum';
if (Array.isArray(attr.options)) {
options = attr.options.map(opt => {
options = attr.options.map((opt: any) => {
const labelValue = typeof opt.label === 'string'
? opt.label
: opt.label?.en || opt.label?.[Object.keys(opt.label)[0]] || String(opt.value);
@ -222,16 +253,14 @@ export function useTrusteeDocuments() {
return fetchedAttributes;
}, [attributes, fetchAttributes]);
// Fetch attributes and permissions on mount
// Fetch data when instanceId is available
useEffect(() => {
fetchAttributes();
fetchPermissions();
}, [fetchAttributes, fetchPermissions]);
// Initial fetch
useEffect(() => {
fetchDocuments();
}, [fetchDocuments]);
if (instanceId) {
fetchAttributes();
fetchPermissions();
fetchDocuments();
}
}, [instanceId, fetchAttributes, fetchPermissions, fetchDocuments]);
return {
documents,
@ -245,12 +274,15 @@ export function useTrusteeDocuments() {
pagination,
fetchDocumentById,
generateEditFieldsFromAttributes,
ensureAttributesLoaded
ensureAttributesLoaded,
instanceId
};
}
// Document operations hook
export function useTrusteeDocumentOperations() {
const instanceId = useInstanceId();
const [deletingDocuments, setDeletingDocuments] = useState<Set<string>>(new Set());
const [creatingDocument, setCreatingDocument] = useState(false);
const [downloadingDocuments, setDownloadingDocuments] = useState<Set<string>>(new Set());
@ -261,11 +293,16 @@ export function useTrusteeDocumentOperations() {
const [downloadError, setDownloadError] = useState<string | null>(null);
const handleDocumentDelete = async (documentId: string) => {
if (!instanceId) {
setDeleteError('No instance context');
return false;
}
setDeleteError(null);
setDeletingDocuments(prev => new Set(prev).add(documentId));
try {
await deleteDocumentApi(request, documentId);
await deleteDocumentApi(request, instanceId, documentId);
await new Promise(resolve => setTimeout(resolve, 300));
return true;
} 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);
setCreatingDocument(true);
try {
const currentUserData = getUserDataCache();
const mandateId = currentUserData?.mandateId || '';
documentData.append('mandate', mandateId);
const newDocument = await createDocumentApi(request, documentData);
const newDocument = await createDocumentApi(request, instanceId, documentData);
return { success: true, documentData: newDocument };
} catch (error: any) {
setCreateError(error.message);
@ -306,19 +342,15 @@ export function useTrusteeDocumentOperations() {
updateData: Partial<TrusteeDocument>,
_originalData?: any
) => {
if (!instanceId) {
setUpdateError('No instance context');
return { success: false, error: 'No instance context' };
}
setUpdateError(null);
try {
const currentUserData = getUserDataCache();
const mandateId = currentUserData?.mandateId || '';
const requestBody = {
...updateData,
mandate: mandateId
};
const updatedDocument = await updateDocumentApi(request, documentId, requestBody);
const updatedDocument = await updateDocumentApi(request, instanceId, documentId, updateData);
return { success: true, documentData: updatedDocument };
} catch (error: any) {
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) => {
if (!instanceId) {
setDownloadError('No instance context');
return false;
}
setDownloadError(null);
setDownloadingDocuments(prev => new Set(prev).add(documentId));
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
const url = window.URL.createObjectURL(blob);
@ -379,6 +428,7 @@ export function useTrusteeDocumentOperations() {
handleDocumentCreate,
handleDocumentUpdate,
handleDocumentDownload,
isLoading
isLoading,
instanceId
};
}

View file

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

View file

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

View file

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

View file

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

View file

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