udb fixed toggles
This commit is contained in:
parent
4c959538ac
commit
dbec57d647
1 changed files with 229 additions and 289 deletions
|
|
@ -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 && (
|
||||
|
|
|
|||
Loading…
Reference in a new issue