diff --git a/src/api/workflowAutomationApi.ts b/src/api/workflowAutomationApi.ts index 3400e43..c0dcaa7 100644 --- a/src/api/workflowAutomationApi.ts +++ b/src/api/workflowAutomationApi.ts @@ -531,17 +531,18 @@ export type ApiRequestFunction = (options: ApiRequestOptions) => Promise { - console.log(`${LOG} fetchNodeTypes: language=${language}`); + console.log(`${LOG} fetchNodeTypes: mandateId=${mandateId} language=${language}`); const data = await request({ url: `${BASE}/node-types`, method: 'get', - params: { language }, + params: { mandateId, language }, }); const nodeTypes = data?.nodeTypes ?? []; const categories = data?.categories ?? []; @@ -717,6 +718,7 @@ export async function fetchWorkflows( params: Object.keys(queryParams).length > 0 ? queryParams : undefined, }); if (data?.items && data?.pagination) return data; + if (data?.items) return data.items; return data?.workflows ?? []; } @@ -990,7 +992,7 @@ export async function fetchTasks( method: 'get', params, }); - return data?.tasks ?? []; + return data?.items ?? []; } export async function completeTask( diff --git a/src/components/FlowEditor/editor/WorkflowFlowEditor.tsx b/src/components/FlowEditor/editor/WorkflowFlowEditor.tsx index 953a740..523c80e 100644 --- a/src/components/FlowEditor/editor/WorkflowFlowEditor.tsx +++ b/src/components/FlowEditor/editor/WorkflowFlowEditor.tsx @@ -551,7 +551,7 @@ export const WorkflowFlowEditor: React.FC = ({ instance setLoading(true); setError(null); try { - const data = await fetchNodeTypes(request, language); + const data = await fetchNodeTypes(request, mandateId || '', language); setNodeTypes(data.nodeTypes); setCategories(data.categories); if (data.portTypeCatalog) { @@ -572,12 +572,12 @@ export const WorkflowFlowEditor: React.FC = ({ instance const loadWorkflows = useCallback(async () => { try { - const result = await fetchWorkflows(request); + const result = await fetchWorkflows(request, { mandateId: mandateId || undefined }); setWorkflows(Array.isArray(result) ? result : result.items); } catch (e) { console.error(`${LOG} loadWorkflows failed`, e); } - }, [request]); + }, [request, mandateId]); useEffect(() => { loadNodeTypes(); diff --git a/src/components/FlowEditor/nodes/shared/graphUtils.ts b/src/components/FlowEditor/nodes/shared/graphUtils.ts index 81528a1..227776a 100644 --- a/src/components/FlowEditor/nodes/shared/graphUtils.ts +++ b/src/components/FlowEditor/nodes/shared/graphUtils.ts @@ -100,9 +100,78 @@ export function fromApiGraph( }; }); + const hasPositions = nodes.length > 0 && nodes.some((n) => n.x !== 0 || n.y !== 0); + if (!hasPositions && nodes.length > 1) { + _autoLayoutTopDown(nodes, connections); + } + return { nodes, connections }; } +const _NODE_WIDTH = 200; +const _NODE_HEIGHT = 60; +const _VERTICAL_GAP = 80; +const _HORIZONTAL_GAP = 60; + +function _autoLayoutTopDown(nodes: CanvasNode[], connections: CanvasConnection[]): void { + const inDegree = new Map(); + const children = new Map(); + for (const n of nodes) { + inDegree.set(n.id, 0); + children.set(n.id, []); + } + for (const c of connections) { + inDegree.set(c.targetId, (inDegree.get(c.targetId) ?? 0) + 1); + children.get(c.sourceId)?.push(c.targetId); + } + + const layers: string[][] = []; + const assigned = new Set(); + const queue = nodes.filter((n) => (inDegree.get(n.id) ?? 0) === 0).map((n) => n.id); + + while (queue.length > 0) { + const layer = [...queue]; + layers.push(layer); + layer.forEach((id) => assigned.add(id)); + const next: string[] = []; + for (const id of layer) { + for (const child of children.get(id) || []) { + if (assigned.has(child)) continue; + const remaining = connections.filter( + (c) => c.targetId === child && !assigned.has(c.sourceId) + ); + if (remaining.length === 0) next.push(child); + } + } + queue.length = 0; + const unique = [...new Set(next)]; + queue.push(...unique); + } + + for (const n of nodes) { + if (!assigned.has(n.id)) { + layers.push([n.id]); + assigned.add(n.id); + } + } + + const nodeById = new Map(nodes.map((n) => [n.id, n])); + let y = 40; + for (const layer of layers) { + const totalWidth = layer.length * _NODE_WIDTH + (layer.length - 1) * _HORIZONTAL_GAP; + let x = Math.max(40, (600 - totalWidth) / 2); + for (const id of layer) { + const node = nodeById.get(id); + if (node) { + node.x = x; + node.y = y; + x += _NODE_WIDTH + _HORIZONTAL_GAP; + } + } + y += _NODE_HEIGHT + _VERTICAL_GAP; + } +} + export function toApiGraph( nodes: CanvasNode[], connections: CanvasConnection[] diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx index dd347fb..f59cc83 100644 --- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx +++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.tsx @@ -913,13 +913,14 @@ export function FormGeneratorTable>({ return columnsRef.current; } - // NO COLUMNS PROVIDED - this is an error in the calling component - // The calling component should provide columns from the /attributes/{entityType} endpoint - console.warn( - '⚠️ FormGeneratorTable: No columns provided! ' + - 'Columns should come from Pydantic attribute definitions via /attributes/{entityType} endpoint. ' + - 'Please ensure the calling component fetches and passes columns from the backend.' - ); + // Only warn when data is present but columns are still missing + if (data && data.length > 0) { + console.warn( + '⚠️ FormGeneratorTable: No columns provided! ' + + 'Columns should come from Pydantic attribute definitions via /attributes/{entityType} endpoint. ' + + 'Please ensure the calling component fetches and passes columns from the backend.' + ); + } // Return empty array - table will show no columns return []; diff --git a/src/config/pageRegistry.tsx b/src/config/pageRegistry.tsx index aea2eaf..ef0ba28 100644 --- a/src/config/pageRegistry.tsx +++ b/src/config/pageRegistry.tsx @@ -25,7 +25,7 @@ import { FaProjectDiagram, FaMapMarkedAlt, FaWallet, FaMoneyBillAlt, FaClock, FaHeadset, FaVideo, FaHatWizard, FaStore, FaUserTie, FaClipboardList, FaFileContract, FaGlobe, FaClipboardCheck, - FaSitemap, FaCopy, FaTasks, + FaSitemap, } from 'react-icons/fa'; // ============================================================================= @@ -58,11 +58,7 @@ export const PAGE_ICONS: Record = { 'page.system.ragInventory': , // System pages - Workflow Automation - 'page.system.workflowAutomation.workflows': , - 'page.system.workflowAutomation.editor': , - 'page.system.workflowAutomation.templates': , - 'page.system.workflowAutomation.runs': , - 'page.system.workflowAutomation.tasks': , + 'page.system.workflowAutomation': , // Billing pages (legacy compat) 'page.billing.dashboard': , diff --git a/src/hooks/useFiles.ts b/src/hooks/useFiles.ts index d3a098e..3755d6f 100644 --- a/src/hooks/useFiles.ts +++ b/src/hooks/useFiles.ts @@ -133,11 +133,11 @@ export function useUserFiles() { } catch (error: any) { console.error('Error fetching permissions:', error); const defaultPerms: UserPermissions = { - view: false, - read: 'n', - create: 'n', - update: 'n', - delete: 'n', + view: true, + read: 'my', + create: 'my', + update: 'my', + delete: 'my', }; setPermissions(defaultPerms); return defaultPerms; diff --git a/src/hooks/useWorkflows.ts b/src/hooks/useWorkflows.ts index efa3ee5..1cae59c 100644 --- a/src/hooks/useWorkflows.ts +++ b/src/hooks/useWorkflows.ts @@ -34,11 +34,12 @@ async function startWorkflowApi( workflowData: StartWorkflowRequest, options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' }, ) { + const wfId = options?.workflowId ?? (workflowData as { workflowId?: string }).workflowId; return await request({ url: `/api/workflow-automation/execute`, method: 'post', data: { - workflowId: options?.workflowId ?? (workflowData as { workflowId?: string }).workflowId, + workflowId: wfId, payload: workflowData, }, }); diff --git a/src/pages/ComplianceAuditPage.tsx b/src/pages/ComplianceAuditPage.tsx index 3f6f6c9..9ab7c50 100644 --- a/src/pages/ComplianceAuditPage.tsx +++ b/src/pages/ComplianceAuditPage.tsx @@ -575,7 +575,7 @@ export const ComplianceAuditPage: React.FC = () => { label: t('Datei'), sortable: true, width: 140, - formatter: (val: any) => (val ? `${String(val).slice(0, 8)}…` : '–'), + displayField: 'fileIdLabel', }, ], [t]); diff --git a/src/pages/workflowAutomation/WorkflowAutomationHubPage.tsx b/src/pages/workflowAutomation/WorkflowAutomationHubPage.tsx index 23a8822..db782de 100644 --- a/src/pages/workflowAutomation/WorkflowAutomationHubPage.tsx +++ b/src/pages/workflowAutomation/WorkflowAutomationHubPage.tsx @@ -40,6 +40,13 @@ export const WorkflowAutomationPage: React.FC = () => { const [activeTab, setActiveTab] = useState(initialRunId ? 'detail' : initialTab); const [selectedRunId, setSelectedRunId] = useState(initialRunId); + + useEffect(() => { + const newTab = _TAB_ALIASES[searchParams.get('tab') || 'workflows'] || searchParams.get('tab') || 'workflows'; + if (newTab !== activeTab) { + setActiveTab(newTab); + } + }, [searchParams]); const [workflowFilter, setWorkflowFilter] = useState(null); const [selectedMandateId, setSelectedMandateId] = useState('all'); diff --git a/src/pages/workflowAutomation/tabs/RunsTab.tsx b/src/pages/workflowAutomation/tabs/RunsTab.tsx index 541d36d..939d668 100644 --- a/src/pages/workflowAutomation/tabs/RunsTab.tsx +++ b/src/pages/workflowAutomation/tabs/RunsTab.tsx @@ -338,7 +338,7 @@ export const _RunsTab: React.FC = ({ workflowFilter, onRunClick, s if (selectedMandateId !== 'all') params.mandateId = selectedMandateId; const resp = await api.get('/api/workflow-automation/runs', { params }); const data = resp.data; - setRuns(data?.runs || []); + setRuns(data?.items || []); const total = data?.total ?? 0; const pageSize = pag.pageSize; setPaginationMeta({ @@ -416,7 +416,7 @@ export const _RunsTab: React.FC = ({ workflowFilter, onRunClick, s width: 140, sortable: true, filterable: true, - displayField: 'mandateLabel', + displayField: 'mandateIdLabel', }, { key: 'featureInstanceId', @@ -424,7 +424,7 @@ export const _RunsTab: React.FC = ({ workflowFilter, onRunClick, s width: 140, sortable: true, filterable: true, - displayField: 'instanceLabel', + displayField: 'featureInstanceIdLabel', }, { key: 'ownerId', @@ -432,7 +432,7 @@ export const _RunsTab: React.FC = ({ workflowFilter, onRunClick, s width: 140, sortable: true, filterable: true, - displayField: 'ownerLabel', + displayField: 'ownerIdLabel', }, { key: 'status', width: 110, sortable: true, filterable: true }, { diff --git a/src/pages/workflowAutomation/tabs/WorkflowsTab.tsx b/src/pages/workflowAutomation/tabs/WorkflowsTab.tsx index 353c773..a80d410 100644 --- a/src/pages/workflowAutomation/tabs/WorkflowsTab.tsx +++ b/src/pages/workflowAutomation/tabs/WorkflowsTab.tsx @@ -220,7 +220,7 @@ export const _WorkflowsTab: React.FC = ({ onWorkflowClick, se width: 140, sortable: true, filterable: true, - displayField: 'mandateLabel', + displayField: 'mandateIdLabel', }, { key: 'featureInstanceId', @@ -228,15 +228,15 @@ export const _WorkflowsTab: React.FC = ({ onWorkflowClick, se width: 140, sortable: true, filterable: true, - displayField: 'instanceLabel', + displayField: 'featureInstanceIdLabel', }, { - key: 'ownerId', - label: t('Benutzer'), + key: 'sysCreatedBy', + label: t('Ersteller'), width: 140, sortable: true, filterable: true, - displayField: 'ownerLabel', + displayField: 'sysCreatedByLabel', }, { key: 'active',