diff --git a/src/components/UnifiedDataBar/SourcesTab.tsx b/src/components/UnifiedDataBar/SourcesTab.tsx index 7580ec0..aec1197 100644 --- a/src/components/UnifiedDataBar/SourcesTab.tsx +++ b/src/components/UnifiedDataBar/SourcesTab.tsx @@ -683,14 +683,14 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe try { const res = await api.get(`/api/workspace/${instanceId}/feature-connections/${node.featureInstanceId}/tables`); const tables: FeatureTableNode[] = (res.data.tables || []).map((t: any) => ({ - objectKey: t.objectKey, - tableName: t.tableName, - label: t.label || {}, - fields: t.fields || [], - isParent: t.isParent || false, - parentTable: t.parentTable || undefined, - parentKey: t.parentKey || undefined, - displayFields: t.displayFields || undefined, + objectKey: t.objectKey ?? '', + tableName: t.tableName ?? '', + label: t.label ?? '', + fields: t.fields ?? [], + isParent: Boolean(t.isParent), + parentTable: t.parentTable ?? null, + parentKey: t.parentKey ?? null, + displayFields: t.displayFields ?? [], })); if (mountedRef.current) { setFeatureTree(prev => _mapFeatureTreeUpdate(prev, node.featureInstanceId, n => ({ @@ -707,11 +707,11 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe }, [instanceId]); /* ── Feature: Add table as FeatureDataSource ── */ - const _addFeatureTable = useCallback(async (node: FeatureConnectionNode, table: FeatureTableNode) => { + const _addFeatureTable = useCallback(async (node: FeatureConnectionNode, table: FeatureTableNode): Promise => { const key = `${node.featureInstanceId}-${table.tableName}`; setAddingFeatureKey(key); try { - await api.post(`/api/workspace/${instanceId}/feature-datasources`, { + const res = await api.post(`/api/workspace/${instanceId}/feature-datasources`, { featureInstanceId: node.featureInstanceId, featureCode: node.featureCode, tableName: table.tableName, @@ -720,8 +720,10 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe }); _fetchFeatureDataSources(); onSourcesChanged?.(); + return res.data?.id || null; } catch (err) { console.error('Failed to add feature data source:', err); + return null; } finally { if (mountedRef.current) setAddingFeatureKey(null); } @@ -895,7 +897,7 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe node={node} depth={0} onToggle={_toggleNode} - onAdd={_addAsDataSource} + onEnsureDs={_addAsDataSource} isAdded={_isAdded} addingPath={addingPath} dataSources={dataSources} @@ -945,7 +947,7 @@ const SourcesTab: React.FC = ({ context, onSourcesChanged, onSe group={g} onToggleGroup={_toggleMandateGroup} onToggleFeature={_toggleFeatureNode} - onAddTable={_addFeatureTable} + onEnsureFds={_addFeatureTable} isTableAdded={_isFeatureTableAdded} addingKey={addingFeatureKey} onToggleParentGroup={_toggleParentGroup} @@ -983,7 +985,7 @@ interface _TreeNodeViewProps { node: TreeNode; depth: number; onToggle: (node: TreeNode) => void; - onAdd: (node: TreeNode) => void; + onEnsureDs: (node: TreeNode) => Promise; isAdded: (connectionId: string, service: string | undefined, path: string | undefined) => boolean; addingPath: string | null; dataSources: UdbDataSource[]; @@ -999,7 +1001,7 @@ interface _TreeNodeViewProps { } const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ - node, depth, onToggle, onAdd, isAdded, addingPath, + node, depth, onToggle, onEnsureDs, isAdded, addingPath, dataSources, onCycleScope, onToggleNeutralize, onRemoveDs, onSendToChat, scopeCycleTitle, selectedKeys, onSelect, inheritedScope, inheritedNeutralize, }) => { @@ -1010,8 +1012,6 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ ? (node.expanded ? '\u25BE' : '\u25B8') : '\u00A0\u00A0'; const ds = _findDs(dataSources, node); - const alreadyAdded = isAdded(node.connectionId, node.service, node.path); - const isAdding = addingPath === node.key; const effectiveScope = ds?.scope ?? inheritedScope; const effectiveNeutralize = ds?.neutralize ?? inheritedNeutralize ?? false; @@ -1111,45 +1111,41 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ {'\u{1F4AC}'} - {/* Scope: own DS → clickable, inherited → dimmed static */} - {ds ? ( - - ) : ( - - {_SCOPE_ICONS[effectiveScope || 'personal']} - - )} + {/* Scope: own DS → cycle, no DS → create DS then cycle */} + - {/* Neutralize: own DS → clickable, inherited → dimmed static */} - {ds ? ( - - ) : ( - - {'\uD83D\uDD12'} - - )} + {/* Neutralize: own DS → toggle, no DS → create DS then toggle */} + {/* Remove: only when DS exists */} {ds && ( @@ -1162,23 +1158,6 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ )} - {/* Add button: on hover when not yet added */} - {hovered && !alreadyAdded && !ds && ( - - )} {node.expanded && node.children && node.children.length > 0 && ( @@ -1189,7 +1168,7 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({ node={child} depth={depth + 1} onToggle={onToggle} - onAdd={onAdd} + onEnsureDs={onEnsureDs} isAdded={isAdded} addingPath={addingPath} dataSources={dataSources} @@ -1231,7 +1210,7 @@ interface _MandateGroupViewProps extends _FdsActionProps { group: MandateGroupNode; onToggleGroup: (mandateId: string) => void; onToggleFeature: (node: FeatureConnectionNode) => void; - onAddTable: (node: FeatureConnectionNode, table: FeatureTableNode) => void; + onEnsureFds: (node: FeatureConnectionNode, table: FeatureTableNode) => Promise; isTableAdded: (featureInstanceId: string, tableName: string) => boolean; addingKey: string | null; onToggleParentGroup: (node: FeatureConnectionNode, parentTableName: string) => void; @@ -1245,7 +1224,7 @@ interface _MandateGroupViewProps extends _FdsActionProps { } const _MandateGroupView: React.FC<_MandateGroupViewProps> = ({ - group, onToggleGroup, onToggleFeature, onAddTable, isTableAdded, addingKey, + group, onToggleGroup, onToggleFeature, onEnsureFds, isTableAdded, addingKey, onToggleParentGroup, onToggleParentRecord, onAddParentRecord, isParentRecordAdded, expandedParentGroups, loadingParentGroup, addingParentKey, onSendToChat, featureDataSources, onCycleScope, onToggleNeutralize, onToggleNeutralizeField, @@ -1283,7 +1262,7 @@ const _MandateGroupView: React.FC<_MandateGroupViewProps> = ({ key={fNode.featureInstanceId} node={fNode} onToggle={onToggleFeature} - onAddTable={onAddTable} + onEnsureFds={onEnsureFds} isTableAdded={isTableAdded} addingKey={addingKey} onToggleParentGroup={onToggleParentGroup} @@ -1313,7 +1292,7 @@ const _MandateGroupView: React.FC<_MandateGroupViewProps> = ({ interface _FeatureNodeViewProps extends _FdsActionProps { node: FeatureConnectionNode; onToggle: (node: FeatureConnectionNode) => void; - onAddTable: (node: FeatureConnectionNode, table: FeatureTableNode) => void; + onEnsureFds: (node: FeatureConnectionNode, table: FeatureTableNode) => Promise; isTableAdded: (featureInstanceId: string, tableName: string) => boolean; addingKey: string | null; onToggleParentGroup: (node: FeatureConnectionNode, parentTableName: string) => void; @@ -1327,7 +1306,7 @@ interface _FeatureNodeViewProps extends _FdsActionProps { } const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = ({ - node, onToggle, onAddTable, isTableAdded, addingKey, + node, onToggle, onEnsureFds, onToggleParentGroup, onToggleParentRecord, onAddParentRecord, isParentRecordAdded, expandedParentGroups, loadingParentGroup, addingParentKey, onSendToChat, featureDataSources, onCycleScope, onToggleNeutralize, onToggleNeutralizeField, @@ -1387,46 +1366,60 @@ const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = ({ {node.tableCount} {t('Tabellen')} - {(wildcardFds || hovered) && ( - - )} + {/* Chat: always visible */} + - {wildcardFds && ( - - )} - {wildcardFds && ( - - )} + {/* Scope: own wildcard-FDS → cycle, otherwise create then cycle */} + + + {/* Neutralize: own wildcard-FDS → toggle, otherwise create then toggle */} + + + {/* Remove: only when wildcard-FDS exists */} {wildcardFds && ( )} - {!wildcardFds && hovered && ( - - )} {node.expanded && node.tables && node.tables.length > 0 && ( @@ -1511,9 +1482,7 @@ const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = ({ key={table.objectKey} featureNode={node} table={table} - onAdd={onAddTable} - isAdded={isTableAdded(node.featureInstanceId, table.tableName)} - isAdding={addingKey === `${node.featureInstanceId}-${table.tableName}`} + onEnsureFds={onEnsureFds} onSendToChat={onSendToChat} fds={fds} onCycleScope={onCycleScope} @@ -1543,9 +1512,7 @@ const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = ({ interface _FeatureTableRowProps { featureNode: FeatureConnectionNode; table: FeatureTableNode; - onAdd: (node: FeatureConnectionNode, table: FeatureTableNode) => void; - isAdded: boolean; - isAdding: boolean; + onEnsureFds: (node: FeatureConnectionNode, table: FeatureTableNode) => Promise; onSendToChat?: (params: { featureInstanceId: string; featureCode: string; tableName?: string; objectKey: string; label: string; fieldName?: string }) => void; fds?: UdbFeatureDataSource; onCycleScope?: (fds: UdbFeatureDataSource) => void; @@ -1558,7 +1525,7 @@ interface _FeatureTableRowProps { } const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({ - featureNode, table, onAdd, isAdded, isAdding, onSendToChat, + featureNode, table, onEnsureFds, onSendToChat, fds, onCycleScope, onToggleNeutralize, onToggleNeutralizeField, onRemoveFds, featureTree, inheritedScope, inheritedNeutralize, }) => { @@ -1620,38 +1587,56 @@ const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({ )} - {(fds || hovered) && ( - - )} + {/* Chat: always visible */} + - {fds && onCycleScope && ( - - )} - {fds && onToggleNeutralize && ( - - )} + {/* Scope: own FDS → cycle, otherwise create then cycle */} + + + {/* Neutralize: own FDS → toggle, otherwise create then toggle */} + + + {/* Remove: only when FDS exists */} {fds && onRemoveFds && ( )} - {/* Inherited scope/neutralize indicators (no own FDS) */} - {!fds && effectiveScope && ( - - {_SCOPE_ICONS[effectiveScope] || _SCOPE_ICONS.personal} - - )} - {!fds && effectiveNeutralize && ( - - {'\uD83D\uDD12'} - - )} - - {!fds && hovered && !isAdded && ( - - )} {/* Expandable field sub-nodes */} @@ -1780,21 +1732,21 @@ const _FeatureFieldRow: React.FC<_FeatureFieldRowProps> = ({ {fieldName} - {(fds || hovered) && ( - - )} + {/* Chat: always visible */} + - {fds && onToggleNeutralizeField && ( + {/* Neutralize: own FDS → clickable, otherwise dimmed */} + {fds && onToggleNeutralizeField ? ( - )} - - {inheritedScope && ( + ) : ( - {_SCOPE_ICONS[inheritedScope] || _SCOPE_ICONS.personal} + {'\uD83D\uDD12'} )} + + {/* Scope: inherited indicator */} + + {_SCOPE_ICONS[inheritedScope || 'personal']} + ); }; @@ -1942,7 +1900,7 @@ interface _ParentRecordRowProps { const _ParentRecordRow: React.FC<_ParentRecordRowProps> = ({ featureNode, record, childTables, allTables: _allTables, - onToggle, onAdd, isAdded, isAdding, + onToggle, onSendToChat, fds, onCycleScope, onToggleNeutralize, onRemoveFds, inheritedScope, inheritedNeutralize, }) => { @@ -1991,31 +1949,29 @@ const _ParentRecordRow: React.FC<_ParentRecordRowProps> = ({ {record.displayLabel} - {/* Chat-Senden: always visible when fds, hover-only otherwise */} - {(fds || hovered) && ( - - )} + {/* Chat: always visible */} + - {/* FDS inline actions */} - {fds && onCycleScope && ( + {/* Scope: own FDS → clickable, otherwise dimmed */} + {fds && onCycleScope ? ( + ) : ( + + {_SCOPE_ICONS[inheritedScope || 'personal']} + )} - {fds && onToggleNeutralize && ( + + {/* Neutralize: own FDS → clickable, otherwise dimmed */} + {fds && onToggleNeutralize ? ( + ) : ( + + {'\uD83D\uDD12'} + )} + + {/* Remove: only when FDS exists */} {fds && onRemoveFds && ( )} - {/* Inherited scope/neutralize indicators */} - {!fds && inheritedScope && ( - - {_SCOPE_ICONS[inheritedScope] || _SCOPE_ICONS.personal} - - )} - {!fds && (inheritedNeutralize ?? false) && ( - - {'\uD83D\uDD12'} - - )} - - {/* Add button (only when not yet added) */} - {!fds && hovered && !isAdded && ( - - )} {record.expanded && (