From f0e73b62d2ce4e03ba442d5f35e0c31419fbe574 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 26 Apr 2026 22:53:39 +0200 Subject: [PATCH] Graph and data class falignment strict --- src/api/workflowApi.ts | 4 +- .../editor/EditorWorkflowChatList.tsx | 4 +- .../FormGeneratorControls.tsx | 2 +- .../FormGeneratorTable/FormGeneratorTable.tsx | 45 +++++++++-- src/hooks/useInvitations.ts | 8 +- src/hooks/useTrustee.ts | 11 ++- src/pages/AutomationsDashboardPage.tsx | 51 +++++------- src/pages/ComplianceAuditPage.tsx | 1 - src/pages/admin/AdminDatabaseHealthPage.tsx | 27 ++++++- src/pages/admin/AdminInvitationsPage.tsx | 65 ++++------------ src/pages/admin/AdminLanguagesPage.tsx | 40 ++++++++++ src/pages/admin/AdminMandateRolesPage.tsx | 46 ++--------- .../wizards/AdminInvitationWizardPage.tsx | 6 +- .../GraphicalEditorTemplatesPage.tsx | 44 +++-------- .../GraphicalEditorWorkflowsPage.tsx | 43 ++++------ .../realestate/RealEstateParcelsView.tsx | 1 + .../realestate/RealEstateProjectsView.tsx | 1 + .../views/trustee/TrusteePositionsView.tsx | 78 ++----------------- src/utils/columnTypeResolver.ts | 28 ++++++- 19 files changed, 223 insertions(+), 282 deletions(-) diff --git a/src/api/workflowApi.ts b/src/api/workflowApi.ts index 76aa49a..47c33c9 100644 --- a/src/api/workflowApi.ts +++ b/src/api/workflowApi.ts @@ -156,8 +156,8 @@ export interface Automation2Workflow { stuckAtNodeId?: string; /** Enriched: human-readable label for stuck node */ stuckAtNodeLabel?: string; - /** Enriched: created timestamp (seconds) */ - createdAt?: number; + /** From PowerOnModel base — record creation timestamp (seconds) */ + sysCreatedAt?: number; /** Enriched: last run started timestamp (seconds) */ lastStartedAt?: number; } diff --git a/src/components/FlowEditor/editor/EditorWorkflowChatList.tsx b/src/components/FlowEditor/editor/EditorWorkflowChatList.tsx index 7709636..7973c98 100644 --- a/src/components/FlowEditor/editor/EditorWorkflowChatList.tsx +++ b/src/components/FlowEditor/editor/EditorWorkflowChatList.tsx @@ -48,7 +48,7 @@ export const EditorWorkflowChatList: React.FC = ({ const list = q ? workflows.filter((w) => (w.label || '').toLowerCase().includes(q)) : [...workflows]; - list.sort((a, b) => (b.lastStartedAt || b.createdAt || 0) - (a.lastStartedAt || a.createdAt || 0)); + list.sort((a, b) => (b.lastStartedAt || b.sysCreatedAt || 0) - (a.lastStartedAt || a.sysCreatedAt || 0)); return list; }, [workflows, search]); @@ -85,7 +85,7 @@ export const EditorWorkflowChatList: React.FC = ({ ) : ( filtered.map((wf) => { const isActive = wf.id === currentWorkflowId; - const ts = wf.lastStartedAt || wf.createdAt; + const ts = wf.lastStartedAt || wf.sysCreatedAt; return (
): string => { // Types for the FormGeneratorTable export interface ColumnConfig { key: string; - label: string; + /** Header label. Optional — when omitted, `resolveColumnTypes` fills it from the + * backend Pydantic field's `label`. Set explicitly only to override. */ + label?: string; type?: AttributeType; width?: number; minWidth?: number; @@ -129,6 +131,11 @@ export interface ColumnConfig { // Pre-translated label tokens for binary/categorical cells, e.g. ["Ja", "-", "Nein"]. // Resolved server-side via i18n so the FE never needs another translation hop. frontendFormatLabels?: string[]; + // Backend-provided enum options (frontend_options on a Pydantic Field). + // When present and column.type is `select`, the cell renderer translates the + // raw value into the matching label. Pages MUST NOT hardcode value→label + // mappings — they must come from the Pydantic model. + options?: Array<{ value: string | number; label: string }>; } export interface FormGeneratorTableProps { @@ -1219,9 +1226,10 @@ export function FormGeneratorTable>({ }, [openFilterColumn, detectedColumns, asyncFilterValues, filterValuesLoading, hookData, apiEndpoint, supportsBackendPagination, filters]); // Get unique values for a column (for filter dropdown) - // Sources: 1) column.filterOptions (static enum) - // 2) asyncFilterValues (loaded from backend via hookData.fetchFilterValues) - // 3) data — ONLY when no backend pagination (data = full dataset) + // Sources: 1) column.filterOptions (static enum, page-defined) + // 2) column.options (backend-provided frontend_options on Pydantic field) + // 3) asyncFilterValues (loaded from backend via hookData.fetchFilterValues) + // 4) data — ONLY when no backend pagination (data = full dataset) // With backend pagination, data is a single page, so extracting filter // values from it would be incomplete and misleading. const getUniqueValuesForColumn = useCallback((columnKey: string): FilterValue[] => { @@ -1231,6 +1239,10 @@ export function FormGeneratorTable>({ return column.filterOptions; } + if (column?.options && column.options.length > 0) { + return column.options.map((o) => ({ value: String(o.value), label: o.label })); + } + // displayField + local full dataset: { value, label } from enriched rows if (column?.displayField && !supportsBackendPagination) { const showKey = column.displayField; @@ -1905,6 +1917,15 @@ export function FormGeneratorTable>({ return renderBooleanCell(value, column, row); } + // Select / enum cells: translate raw value to backend-provided label. + // Pages MUST NOT hardcode value→label maps; they must declare + // `frontend_options` on the Pydantic field, and the API forwards them + // (already translated server-side via i18n) into `column.options`. + if (column.options && column.options.length > 0 && (value !== null && value !== undefined && value !== '')) { + const match = column.options.find((opt) => String(opt.value) === String(value)); + if (match) return match.label; + } + // Check if this is an ID or hash field that should be truncated and copyable // Do this BEFORE checking for custom formatters to ensure IDs/hashes are always copyable const isId = isIdField(column.key); @@ -2284,9 +2305,9 @@ export function FormGeneratorTable>({ column.sortable && handleSort(column.key)} - title={column.label} + title={column.label ?? column.key} > - {column.label} + {column.label ?? column.key}
@@ -2298,7 +2319,7 @@ export function FormGeneratorTable>({ onClick={(e) => e.stopPropagation()} >
- {t('Filter')}: {column.label} + {t('Filter')}: {column.label ?? column.key} {column.key in filters && (
+
+ +