udb fixed toggles

This commit is contained in:
ValueOn AG 2026-04-17 12:30:50 +02:00
parent 4c959538ac
commit dbec57d647

View file

@ -683,14 +683,14 @@ const SourcesTab: React.FC<SourcesTabProps> = ({ 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<SourcesTabProps> = ({ 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<string | null> => {
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<SourcesTabProps> = ({ 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<SourcesTabProps> = ({ 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<SourcesTabProps> = ({ 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<string | null>;
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}'}
</button>
{/* Scope: own DS → clickable, inherited → dimmed static */}
{ds ? (
<button
onClick={e => { e.stopPropagation(); onCycleScope(ds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1 }}
title={scopeCycleTitle(ds.scope)}
>
{_SCOPE_ICONS[ds.scope] || _SCOPE_ICONS.personal}
</button>
) : (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={effectiveScope ? `${t('Geerbt')}: ${effectiveScope}` : t('Kein Scope gesetzt')}
>
{_SCOPE_ICONS[effectiveScope || 'personal']}
</span>
)}
{/* Scope: own DS → cycle, no DS → create DS then cycle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (ds) { onCycleScope(ds); return; }
const newId = await onEnsureDs(node);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/scope`, { scope: _nextScope(effectiveScope || 'personal') }); } catch {}
}
}}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: ds ? 1 : 0.35 }}
title={ds ? scopeCycleTitle(ds.scope) : (effectiveScope ? `${t('Geerbt')}: ${effectiveScope}` : t('Scope setzen'))}
>
{_SCOPE_ICONS[ds?.scope || effectiveScope || 'personal']}
</button>
{/* Neutralize: own DS → clickable, inherited → dimmed static */}
{ds ? (
<button
onClick={e => { e.stopPropagation(); onToggleNeutralize(ds); }}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 12, padding: '0 2px', lineHeight: 1,
opacity: ds.neutralize ? 1 : 0.35,
}}
title={ds.neutralize ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
) : (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: effectiveNeutralize ? 0.5 : 0.15 }}
title={effectiveNeutralize ? t('Neutralisierung geerbt') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</span>
)}
{/* Neutralize: own DS → toggle, no DS → create DS then toggle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (ds) { onToggleNeutralize(ds); return; }
const newId = await onEnsureDs(node);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/neutralize`, { neutralize: !effectiveNeutralize }); } catch {}
}
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 12, padding: '0 2px', lineHeight: 1,
opacity: (ds?.neutralize ?? effectiveNeutralize) ? 1 : 0.35,
}}
title={(ds?.neutralize ?? effectiveNeutralize) ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
{/* Remove: only when DS exists */}
{ds && (
@ -1162,23 +1158,6 @@ const _TreeNodeView: React.FC<_TreeNodeViewProps> = ({
</button>
)}
{/* Add button: on hover when not yet added */}
{hovered && !alreadyAdded && !ds && (
<button
onClick={e => { e.stopPropagation(); onAdd(node); }}
disabled={isAdding}
style={{
background: 'none', border: '1px solid var(--primary-color, #F25843)', borderRadius: 3,
cursor: isAdding ? 'not-allowed' : 'pointer',
fontSize: 10, color: 'var(--primary-color, #F25843)', padding: '1px 5px',
opacity: isAdding ? 0.5 : 1,
flexShrink: 0,
}}
title={t('Als Datenquelle hinzufügen')}
>
{isAdding ? '...' : `+ ${t('Hinzufügen')}`}
</button>
)}
</div>
{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<string | null>;
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<string | null>;
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')}
</span>
{(wildcardFds || hovered) && (
<button
onClick={(e) => {
e.stopPropagation();
onSendToChat?.({
featureInstanceId: node.featureInstanceId,
featureCode: node.featureCode,
objectKey: `data.feature.${node.featureCode}.*`,
label: node.label,
});
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 14, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: 0.7, color: '#7b1fa2',
}}
title={t('Alle Tabellen in Chat senden')}
>
{'\u{1F4AC}'}
</button>
)}
{/* Chat: always visible */}
<button
onClick={(e) => {
e.stopPropagation();
onSendToChat?.({
featureInstanceId: node.featureInstanceId,
featureCode: node.featureCode,
objectKey: `data.feature.${node.featureCode}.*`,
label: node.label,
});
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 14, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: wildcardFds ? 0.7 : (hovered ? 0.5 : 0.25), color: '#7b1fa2',
}}
title={t('Alle Tabellen in Chat senden')}
>
{'\u{1F4AC}'}
</button>
{wildcardFds && (
<button
onClick={e => { e.stopPropagation(); onCycleScope(wildcardFds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1 }}
title={`${t('Bereich')}: ${wildcardFds.scope}`}
>
{_SCOPE_ICONS[wildcardFds.scope] || _SCOPE_ICONS.personal}
</button>
)}
{wildcardFds && (
<button
onClick={e => { e.stopPropagation(); onToggleNeutralize(wildcardFds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: wildcardFds.neutralize ? 1 : 0.35 }}
title={wildcardFds.neutralize ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
)}
{/* Scope: own wildcard-FDS → cycle, otherwise create then cycle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (wildcardFds) { onCycleScope(wildcardFds); return; }
const newId = await onEnsureFds(node, { objectKey: `data.feature.${node.featureCode}.*`, tableName: '*', label: node.label, fields: [] } as FeatureTableNode);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/scope`, { scope: _nextScope('personal') }); } catch {}
}
}}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: wildcardFds ? 1 : 0.35 }}
title={wildcardFds ? `${t('Bereich')}: ${wildcardFds.scope}` : t('Scope setzen')}
>
{_SCOPE_ICONS[wildcardFds?.scope || 'personal']}
</button>
{/* Neutralize: own wildcard-FDS → toggle, otherwise create then toggle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (wildcardFds) { onToggleNeutralize(wildcardFds); return; }
const newId = await onEnsureFds(node, { objectKey: `data.feature.${node.featureCode}.*`, tableName: '*', label: node.label, fields: [] } as FeatureTableNode);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/neutralize`, { neutralize: true }); } catch {}
}
}}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: wildcardFds ? (wildcardFds.neutralize ? 1 : 0.35) : 0.35 }}
title={wildcardFds?.neutralize ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
{/* Remove: only when wildcard-FDS exists */}
{wildcardFds && (
<button
onClick={e => { e.stopPropagation(); onRemoveFds(wildcardFds.id); }}
@ -1437,28 +1430,6 @@ const _FeatureNodeView: React.FC<_FeatureNodeViewProps> = ({
</button>
)}
{!wildcardFds && hovered && (
<button
onClick={e => {
e.stopPropagation();
onAddTable(node, {
objectKey: `data.feature.${node.featureCode}.*`,
tableName: '*',
label: node.label,
fields: [],
} as FeatureTableNode);
}}
style={{
background: 'none', border: '1px solid #7b1fa2', borderRadius: 3,
cursor: 'pointer',
fontSize: 10, color: '#7b1fa2', padding: '1px 5px',
flexShrink: 0,
}}
title={t('Als Feature-Datenquelle hinzufügen')}
>
{`+ ${t('Hinzufügen')}`}
</button>
)}
</div>
{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<string | null>;
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> = ({
)}
</span>
{(fds || hovered) && (
<button
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 13, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: 0.7, color: '#7b1fa2',
}}
title={t('In Chat senden')}
>
{'\u{1F4AC}'}
</button>
)}
{/* Chat: always visible */}
<button
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 13, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: fds ? 0.7 : (hovered ? 0.5 : 0.25), color: '#7b1fa2',
}}
title={t('In Chat senden')}
>
{'\u{1F4AC}'}
</button>
{fds && onCycleScope && (
<button
onClick={e => { e.stopPropagation(); onCycleScope(fds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1 }}
title={`${t('Bereich')}: ${fds.scope}`}
>
{_SCOPE_ICONS[fds.scope] || _SCOPE_ICONS.personal}
</button>
)}
{fds && onToggleNeutralize && (
<button
onClick={e => { e.stopPropagation(); onToggleNeutralize(fds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: fds.neutralize ? 1 : 0.35 }}
title={fds.neutralize ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
)}
{/* Scope: own FDS → cycle, otherwise create then cycle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (fds && onCycleScope) { onCycleScope(fds); return; }
const newId = await onEnsureFds(featureNode, table);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/scope`, { scope: _nextScope(effectiveScope || 'personal') }); } catch {}
}
}}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: fds ? 1 : 0.35 }}
title={fds ? `${t('Bereich')}: ${fds.scope}` : (effectiveScope ? `${t('Geerbt')}: ${effectiveScope}` : t('Scope setzen'))}
>
{_SCOPE_ICONS[fds?.scope || effectiveScope || 'personal']}
</button>
{/* Neutralize: own FDS → toggle, otherwise create then toggle */}
<button
onClick={async (e) => {
e.stopPropagation();
if (fds && onToggleNeutralize) { onToggleNeutralize(fds); return; }
const newId = await onEnsureFds(featureNode, table);
if (newId) {
try { await api.patch(`/api/datasources/${newId}/neutralize`, { neutralize: !effectiveNeutralize }); } catch {}
}
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 12, padding: '0 2px', lineHeight: 1,
opacity: (fds?.neutralize ?? effectiveNeutralize) ? 1 : 0.35,
}}
title={(fds?.neutralize ?? effectiveNeutralize) ? t('Neutralisierung an') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</button>
{/* Remove: only when FDS exists */}
{fds && onRemoveFds && (
<button
onClick={e => { e.stopPropagation(); onRemoveFds(fds.id); }}
@ -1662,39 +1647,6 @@ const _FeatureTableRow: React.FC<_FeatureTableRowProps> = ({
</button>
)}
{/* Inherited scope/neutralize indicators (no own FDS) */}
{!fds && effectiveScope && (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={`${t('Geerbt')}: ${effectiveScope}`}
>
{_SCOPE_ICONS[effectiveScope] || _SCOPE_ICONS.personal}
</span>
)}
{!fds && effectiveNeutralize && (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={t('Neutralisierung geerbt')}
>
{'\uD83D\uDD12'}
</span>
)}
{!fds && hovered && !isAdded && (
<button
onClick={e => { e.stopPropagation(); onAdd(featureNode, table); }}
disabled={isAdding}
style={{
background: 'none', border: '1px solid #7b1fa2', borderRadius: 3,
cursor: isAdding ? 'not-allowed' : 'pointer',
fontSize: 10, color: '#7b1fa2', padding: '1px 5px',
opacity: isAdding ? 0.5 : 1, flexShrink: 0,
}}
title={t('Als Feature-Datenquelle hinzufügen')}
>
{isAdding ? '...' : `+ ${t('Hinzufügen')}`}
</button>
)}
</div>
{/* Expandable field sub-nodes */}
@ -1780,21 +1732,21 @@ const _FeatureFieldRow: React.FC<_FeatureFieldRowProps> = ({
{fieldName}
</span>
{(fds || hovered) && (
<button
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 11, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: 0.7, color: '#7b1fa2',
}}
title={t('Feld in Chat senden')}
>
{'\u{1F4AC}'}
</button>
)}
{/* Chat: always visible */}
<button
onClick={e => { e.stopPropagation(); onSendToChat?.(_chatPayload); }}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 11, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: fds ? 0.7 : (hovered ? 0.5 : 0.25), color: '#7b1fa2',
}}
title={t('Feld in Chat senden')}
>
{'\u{1F4AC}'}
</button>
{fds && onToggleNeutralizeField && (
{/* Neutralize: own FDS → clickable, otherwise dimmed */}
{fds && onToggleNeutralizeField ? (
<button
onClick={e => { e.stopPropagation(); onToggleNeutralizeField(fds, fieldName); }}
style={{
@ -1806,16 +1758,22 @@ const _FeatureFieldRow: React.FC<_FeatureFieldRowProps> = ({
>
{'\uD83D\uDD12'}
</button>
)}
{inheritedScope && (
) : (
<span
style={{ fontSize: 10, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={`${t('Geerbt')}: ${inheritedScope}`}
style={{ fontSize: 11, padding: '0 2px', lineHeight: 1, opacity: isNeutralized ? 0.5 : 0.15 }}
title={isNeutralized ? t('Neutralisierung geerbt') : t('Neutralisierung aus')}
>
{_SCOPE_ICONS[inheritedScope] || _SCOPE_ICONS.personal}
{'\uD83D\uDD12'}
</span>
)}
{/* Scope: inherited indicator */}
<span
style={{ fontSize: 10, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={inheritedScope ? `${t('Geerbt')}: ${inheritedScope}` : t('Kein Scope gesetzt')}
>
{_SCOPE_ICONS[inheritedScope || 'personal']}
</span>
</div>
);
};
@ -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}
</span>
{/* Chat-Senden: always visible when fds, hover-only otherwise */}
{(fds || hovered) && (
<button
onClick={(e) => {
e.stopPropagation();
onSendToChat?.({
featureInstanceId: featureNode.featureInstanceId,
featureCode: featureNode.featureCode,
objectKey: `data.feature.${featureNode.featureCode}.${record.tableName || '*'}`,
label: record.displayLabel,
});
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 13, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: 0.7, color: '#7b1fa2',
}}
title={t('In Chat senden')}
>
{'\u{1F4AC}'}
</button>
)}
{/* Chat: always visible */}
<button
onClick={(e) => {
e.stopPropagation();
onSendToChat?.({
featureInstanceId: featureNode.featureInstanceId,
featureCode: featureNode.featureCode,
objectKey: `data.feature.${featureNode.featureCode}.${record.tableName || '*'}`,
label: record.displayLabel,
});
}}
style={{
background: 'none', border: 'none', cursor: 'pointer',
fontSize: 13, padding: '0 2px', flexShrink: 0, lineHeight: 1,
opacity: fds ? 0.7 : (hovered ? 0.5 : 0.25), color: '#7b1fa2',
}}
title={t('In Chat senden')}
>
{'\u{1F4AC}'}
</button>
{/* FDS inline actions */}
{fds && onCycleScope && (
{/* Scope: own FDS → clickable, otherwise dimmed */}
{fds && onCycleScope ? (
<button
onClick={(e) => { e.stopPropagation(); onCycleScope(fds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1 }}
@ -2023,8 +1979,17 @@ const _ParentRecordRow: React.FC<_ParentRecordRowProps> = ({
>
{_SCOPE_ICONS[fds.scope] || _SCOPE_ICONS.personal}
</button>
) : (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={inheritedScope ? `${t('Geerbt')}: ${inheritedScope}` : t('Kein Scope gesetzt')}
>
{_SCOPE_ICONS[inheritedScope || 'personal']}
</span>
)}
{fds && onToggleNeutralize && (
{/* Neutralize: own FDS → clickable, otherwise dimmed */}
{fds && onToggleNeutralize ? (
<button
onClick={(e) => { e.stopPropagation(); onToggleNeutralize(fds); }}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: fds.neutralize ? 1 : 0.35 }}
@ -2032,7 +1997,16 @@ const _ParentRecordRow: React.FC<_ParentRecordRowProps> = ({
>
{'\uD83D\uDD12'}
</button>
) : (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: (inheritedNeutralize ?? false) ? 0.5 : 0.15 }}
title={(inheritedNeutralize ?? false) ? t('Neutralisierung geerbt') : t('Neutralisierung aus')}
>
{'\uD83D\uDD12'}
</span>
)}
{/* Remove: only when FDS exists */}
{fds && onRemoveFds && (
<button
onClick={(e) => { e.stopPropagation(); onRemoveFds(fds.id); }}
@ -2043,40 +2017,6 @@ const _ParentRecordRow: React.FC<_ParentRecordRowProps> = ({
</button>
)}
{/* Inherited scope/neutralize indicators */}
{!fds && inheritedScope && (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={`${t('Geerbt')}: ${inheritedScope}`}
>
{_SCOPE_ICONS[inheritedScope] || _SCOPE_ICONS.personal}
</span>
)}
{!fds && (inheritedNeutralize ?? false) && (
<span
style={{ fontSize: 12, padding: '0 2px', lineHeight: 1, opacity: 0.25 }}
title={t('Neutralisierung geerbt')}
>
{'\uD83D\uDD12'}
</span>
)}
{/* Add button (only when not yet added) */}
{!fds && hovered && !isAdded && (
<button
onClick={e => { e.stopPropagation(); onAdd(); }}
disabled={isAdding}
style={{
background: 'none', border: '1px solid #7b1fa2', borderRadius: 3,
cursor: isAdding ? 'not-allowed' : 'pointer',
fontSize: 10, color: '#7b1fa2', padding: '1px 5px',
opacity: isAdding ? 0.5 : 1, flexShrink: 0,
}}
title={t('Alle Tabellen für diese Quelle hinzufügen')}
>
{isAdding ? '...' : `+ ${t('Hinzufügen')}`}
</button>
)}
</div>
{record.expanded && (