From 2b96ab7b665980eb6f9572950a196d3f095264f8 Mon Sep 17 00:00:00 2001
From: ValueOn AG
Date: Tue, 13 Jan 2026 23:16:37 +0100
Subject: [PATCH] fixed trustee access
---
.../FormGeneratorForm/FormGeneratorForm.tsx | 51 +++--------
.../FormGeneratorTable/FormGeneratorTable.tsx | 3 +-
src/core/PageManager/PageRenderer.tsx | 19 +++-
.../PageManager/data/pages/trustee/access.ts | 2 +
.../data/pages/trustee/contracts.ts | 2 +
.../data/pages/trustee/documents.ts | 2 +
.../data/pages/trustee/organisations.ts | 2 +
.../data/pages/trustee/positions.ts | 2 +
.../PageManager/data/pages/trustee/roles.ts | 2 +
src/hooks/useTrustee.ts | 89 ++++++++++++++++++-
src/hooks/useUsers.ts | 19 +++-
11 files changed, 148 insertions(+), 45 deletions(-)
diff --git a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx
index 46b12a4..2e9e7a7 100644
--- a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx
+++ b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.tsx
@@ -26,36 +26,8 @@ const isTextMultilingual = (value: any): boolean => {
return 'en' in value && typeof value.en === 'string';
};
-// Helper function to check if a field name suggests it's a multilingual field
-// Only specific fields should be multilingual, not fields that just contain these words
-const isMultilingualFieldName = (fieldName: string): boolean => {
- const lowerFieldName = fieldName.toLowerCase();
-
- // Exact matches for multilingual fields
- const exactMultilingualFields = ['description'];
-
- // Fields that end with these patterns (but not roleLabel, etc.)
- // Note: "name" is NOT multilingual - Mandate.name and other name fields are plain strings
- const multilingualPatterns = [
- /^description$/i,
- /^label$/i, // Only exact "label", not "roleLabel"
- /^title$/i // Only exact "title"
- ];
-
- // Check exact matches first
- if (exactMultilingualFields.includes(lowerFieldName)) {
- return true;
- }
-
- // Check patterns - but exclude fields like "roleLabel" which should be strings
- const excludedFields = ['rolelabel', 'role_label', 'rolename', 'role_name', 'username', 'user_name'];
- if (excludedFields.includes(lowerFieldName)) {
- return false;
- }
-
- // Check if it matches multilingual patterns (exact match, not contains)
- return multilingualPatterns.some(pattern => pattern.test(fieldName));
-};
+// Note: Field types are determined ONLY by the explicit 'type' property.
+// FormGeneratorForm does NOT infer types from field names.
// Attribute definition interface (matches backend structure)
export interface AttributeDefinition {
@@ -222,8 +194,10 @@ export function FormGeneratorForm>({
// Initialize form data with defaults
useEffect(() => {
// Helper to check if a field should be treated as multilingual
- const isMultilingual = (attr: AttributeDefinition) =>
- isMultilingualType(attr.type as AttributeType) || isMultilingualFieldName(attr.name);
+ // Only use explicit type - do NOT infer from field name
+ const isMultilingual = (attr: AttributeDefinition) => {
+ return isMultilingualType(attr.type as AttributeType);
+ };
if (data) {
// Ensure TextMultilingual fields are properly initialized
@@ -385,9 +359,9 @@ export function FormGeneratorForm>({
// Check required fields
if (attr.required) {
- // Special handling for TextMultilingual fields (by type or field name)
- const isMultilingual = isMultilingualType(attr.type as AttributeType) || isMultilingualFieldName(attr.name);
- if (isMultilingual && isTextMultilingual(value)) {
+ // Special handling for TextMultilingual fields (by explicit type only)
+ const isMultilingualField = isMultilingualType(attr.type as AttributeType);
+ if (isMultilingualField && isTextMultilingual(value)) {
if (!value.en || typeof value.en !== 'string' || value.en.trim() === '') {
newErrors[attr.name] = t('formgen.form.required', `${attr.label} (English) is required`);
return;
@@ -637,9 +611,10 @@ export function FormGeneratorForm>({
const hasError = errors[attr.name];
const isReadonly = mode === 'display' || attr.readonly || attr.editable === false;
- // Check if this is a multilingual field - either by type or by field name convention
- if ((isMultilingualType(attr.type as AttributeType) || isMultilingualFieldName(attr.name)) &&
- (isTextMultilingual(value) || value === undefined || value === null || value === '')) {
+ // Check if this is a multilingual field - by explicit type ONLY
+ const shouldRenderAsMultilingual = isMultilingualType(attr.type as AttributeType) &&
+ (isTextMultilingual(value) || value === undefined || value === null || value === '');
+ if (shouldRenderAsMultilingual) {
return renderMultilingualField(attr);
}
diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
index 5976703..2fbabc1 100644
--- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
+++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx
@@ -806,7 +806,8 @@ export function FormGeneratorTable>({
if (onInlineUpdate) {
await onInlineUpdate(row, column.key, newValue);
} else if (hookData?.handleInlineUpdate) {
- await hookData.handleInlineUpdate(rowId, { [column.key]: newValue });
+ // Pass row as third parameter for hooks that need to merge changes with existing data
+ await hookData.handleInlineUpdate(rowId, { [column.key]: newValue }, row);
}
// Only refetch if we DON'T have optimistic update (to get fresh data)
diff --git a/src/core/PageManager/PageRenderer.tsx b/src/core/PageManager/PageRenderer.tsx
index 692a0d1..9fb2e01 100644
--- a/src/core/PageManager/PageRenderer.tsx
+++ b/src/core/PageManager/PageRenderer.tsx
@@ -2182,6 +2182,9 @@ const PageRenderer: React.FC = ({
// Create a wrapper for onCreate that ensures attributes are loaded (define before use)
const wrappedCreateOperation = async (formData: any) => {
+ // Debug: Log form data being submitted
+ console.warn('🔧 wrappedCreateOperation - formData:', formData);
+
// Ensure attributes are loaded before creating (if function exists)
if (hookDataAny.ensureAttributesLoaded && typeof hookDataAny.ensureAttributesLoaded === 'function') {
await hookDataAny.ensureAttributesLoaded();
@@ -2189,8 +2192,20 @@ const PageRenderer: React.FC = ({
return await createOperation(formData);
};
- // Use dynamic fields from backend attributes
- const generatedFields = generateFieldsFunction();
+ // Prefer custom formConfig.fields if defined, otherwise use dynamic fields from backend attributes
+ const customFields = button.formConfig.fields;
+ const generatedFields = customFields && customFields.length > 0
+ ? customFields
+ : generateFieldsFunction();
+
+ // Debug: Log which function is used and what fields are generated
+ console.log('🔧 CreateButton fields generation:', {
+ hasCustomFields: !!customFields && customFields.length > 0,
+ hasCreateFields: !!hookDataAny.generateCreateFieldsFromAttributes,
+ hasEditFields: !!hookDataAny.generateEditFieldsFromAttributes,
+ generatedFieldsCount: generatedFields?.length || 0,
+ generatedFieldKeys: generatedFields?.map((f: any) => f.key) || []
+ });
// Check if attributes are still loading
const attributes = hookDataAny.attributes;
diff --git a/src/core/PageManager/data/pages/trustee/access.ts b/src/core/PageManager/data/pages/trustee/access.ts
index c28457a..5a8f7be 100644
--- a/src/core/PageManager/data/pages/trustee/access.ts
+++ b/src/core/PageManager/data/pages/trustee/access.ts
@@ -36,6 +36,7 @@ const createAccessHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteeAccess();
const {
@@ -96,6 +97,7 @@ const createAccessHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/core/PageManager/data/pages/trustee/contracts.ts b/src/core/PageManager/data/pages/trustee/contracts.ts
index ac0c03a..f540e12 100644
--- a/src/core/PageManager/data/pages/trustee/contracts.ts
+++ b/src/core/PageManager/data/pages/trustee/contracts.ts
@@ -36,6 +36,7 @@ const createContractsHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteeContracts();
const {
@@ -96,6 +97,7 @@ const createContractsHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/core/PageManager/data/pages/trustee/documents.ts b/src/core/PageManager/data/pages/trustee/documents.ts
index f12089e..4ce603d 100644
--- a/src/core/PageManager/data/pages/trustee/documents.ts
+++ b/src/core/PageManager/data/pages/trustee/documents.ts
@@ -41,6 +41,7 @@ const createDocumentsHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteeDocuments();
const {
@@ -101,6 +102,7 @@ const createDocumentsHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/core/PageManager/data/pages/trustee/organisations.ts b/src/core/PageManager/data/pages/trustee/organisations.ts
index d850e5f..e28ec2c 100644
--- a/src/core/PageManager/data/pages/trustee/organisations.ts
+++ b/src/core/PageManager/data/pages/trustee/organisations.ts
@@ -38,6 +38,7 @@ const createOrganisationsHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteeOrganisations();
const {
@@ -98,6 +99,7 @@ const createOrganisationsHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/core/PageManager/data/pages/trustee/positions.ts b/src/core/PageManager/data/pages/trustee/positions.ts
index e927f0f..aebf24c 100644
--- a/src/core/PageManager/data/pages/trustee/positions.ts
+++ b/src/core/PageManager/data/pages/trustee/positions.ts
@@ -36,6 +36,7 @@ const createPositionsHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteePositions();
const {
@@ -100,6 +101,7 @@ const createPositionsHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/core/PageManager/data/pages/trustee/roles.ts b/src/core/PageManager/data/pages/trustee/roles.ts
index 79b02eb..2a20d20 100644
--- a/src/core/PageManager/data/pages/trustee/roles.ts
+++ b/src/core/PageManager/data/pages/trustee/roles.ts
@@ -36,6 +36,7 @@ const createRolesHook = () => {
permissions,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
} = useTrusteeRoles();
const {
@@ -96,6 +97,7 @@ const createRolesHook = () => {
columns: generatedColumns,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
diff --git a/src/hooks/useTrustee.ts b/src/hooks/useTrustee.ts
index 9d68038..490d69b 100644
--- a/src/hooks/useTrustee.ts
+++ b/src/hooks/useTrustee.ts
@@ -198,9 +198,14 @@ function _createTrusteeEntityHook(config: TrusteeEntit
return attributes
.filter(attr => {
+ // For EDIT mode: filter out readonly fields and system fields
if (attr.readonly === true || attr.editable === false) {
return false;
}
+ // Also filter out 'id' for edit mode (id cannot be changed)
+ if (attr.name === 'id') {
+ return false;
+ }
const nonEditableFields = ['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt'];
return !nonEditableFields.includes(attr.name);
})
@@ -260,6 +265,75 @@ function _createTrusteeEntityHook(config: TrusteeEntit
});
}, [attributes]);
+ // Generate fields for CREATE forms - includes all required fields like 'id'
+ const generateCreateFieldsFromAttributes = useCallback(() => {
+ if (!attributes || attributes.length === 0) {
+ return [];
+ }
+
+ return attributes
+ .filter(attr => {
+ // For CREATE mode: include all user-editable fields including 'id'
+ // Only filter out system-generated fields
+ const systemFields = ['_createdBy', '_createdAt', '_modifiedBy', '_modifiedAt', 'mandateId'];
+ return !systemFields.includes(attr.name);
+ })
+ .map(attr => {
+ let fieldType: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'multiselect' | 'readonly' | 'number' = 'string';
+ let options: Array<{ value: string | number; label: string }> | undefined = undefined;
+ let optionsReference: string | undefined = undefined;
+
+ if (attr.type === 'checkbox') {
+ fieldType = 'boolean';
+ } else if (attr.type === 'email') {
+ fieldType = 'email';
+ } else if (attr.type === 'date') {
+ fieldType = 'date';
+ } else if (attr.type === 'number') {
+ fieldType = 'number';
+ } else if (attr.type === 'select') {
+ fieldType = 'enum';
+ if (Array.isArray(attr.options)) {
+ 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);
+ return { value: opt.value, label: labelValue };
+ });
+ } else if (typeof attr.options === 'string') {
+ optionsReference = attr.options;
+ }
+ } else if (attr.type === 'multiselect') {
+ fieldType = 'multiselect';
+ if (Array.isArray(attr.options)) {
+ 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);
+ return { value: opt.value, label: labelValue };
+ });
+ } else if (typeof attr.options === 'string') {
+ optionsReference = attr.options;
+ }
+ } else if (attr.type === 'textarea') {
+ fieldType = 'textarea';
+ } else if (attr.type === 'timestamp') {
+ fieldType = 'readonly';
+ }
+
+ return {
+ key: attr.name,
+ label: attr.label || attr.name,
+ type: fieldType,
+ editable: true, // All fields are editable in create mode
+ required: attr.required === true,
+ options,
+ optionsReference,
+ dependsOn: attr.dependsOn
+ };
+ });
+ }, [attributes]);
+
const ensureAttributesLoaded = useCallback(async () => {
if (attributes && attributes.length > 0) {
return attributes;
@@ -288,6 +362,7 @@ function _createTrusteeEntityHook(config: TrusteeEntit
pagination,
fetchById,
generateEditFieldsFromAttributes,
+ generateCreateFieldsFromAttributes,
ensureAttributesLoaded
};
};
@@ -326,12 +401,22 @@ function _createTrusteeOperationsHook(config: TrusteeE
setCreateError(null);
setCreatingItem(true);
+ // Debug: Log what data is being sent to the backend
+ console.warn('🔧 handleCreate called with itemData:', itemData);
+
try {
const newItem = await config.create(request, itemData);
return { success: true, data: newItem };
} catch (error: any) {
- setCreateError(error.message);
- return { success: false, error: error.message };
+ // Debug: Log full error details
+ console.error('🔧 handleCreate error:', {
+ message: error.message,
+ response: error.response?.data,
+ status: error.response?.status
+ });
+ const errorMessage = error.response?.data?.detail || error.message;
+ setCreateError(errorMessage);
+ return { success: false, error: errorMessage };
} finally {
setCreatingItem(false);
}
diff --git a/src/hooks/useUsers.ts b/src/hooks/useUsers.ts
index c174040..263bba7 100644
--- a/src/hooks/useUsers.ts
+++ b/src/hooks/useUsers.ts
@@ -923,8 +923,23 @@ export function useUserOperations() {
};
// Generic inline update handler for FormGeneratorTable
- const handleInlineUpdate = async (userId: string, changes: Partial) => {
- const result = await handleUserUpdate(userId, changes as UserUpdateData);
+ // Must merge changes with existing row data because backend requires full object
+ // The existingRow parameter is passed from FormGeneratorTable which has access to row data
+ const handleInlineUpdate = async (userId: string, changes: Partial, existingRow?: any) => {
+ if (!existingRow) {
+ throw new Error(`Existing row data required for inline update`);
+ }
+
+ // Merge changes with existing row data (backend requires full object with required fields)
+ const mergedData: UserUpdateData = {
+ username: existingRow.username,
+ email: existingRow.email,
+ enabled: existingRow.enabled,
+ roleLabels: existingRow.roleLabels,
+ ...changes
+ };
+
+ const result = await handleUserUpdate(userId, mergedData);
if (!result.success) {
throw new Error(result.error || 'Failed to update');
}