frontend_nyla/src/components/Automation2FlowEditor/nodes/configs/SharePointNodeConfig.tsx

340 lines
11 KiB
TypeScript

/**
* SharePoint node config — connection selector, paths, search.
* All nodes use SharepointBrowseTree with the selected connection (fetchBrowse + onLoadChildren).
* Folder-style nodes (list, upload target, copy destination): folders only, folder selection.
* File-style nodes (read, download, find path, copy source): file selection; folders expand only.
*/
import React, { useEffect, useState, useCallback } from 'react';
import type { NodeConfigRendererProps } from './types';
import { fetchConnections, fetchBrowse, type UserConnection, type BrowseEntry } from '../../../../api/automation2Api';
import { SharepointBrowseTree } from '../../../FolderTree/SharepointBrowseTree';
const browseDetailsStyle: React.CSSProperties = {
marginTop: 12,
border: '1px solid var(--border-color, #e0e0e0)',
borderRadius: 6,
background: 'var(--bg-secondary, #f8f9fa)',
overflow: 'hidden',
};
const browseSummaryStyle: React.CSSProperties = {
padding: '0.5rem 0.75rem',
cursor: 'pointer',
fontWeight: 500,
fontSize: '0.875rem',
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
userSelect: 'none',
};
const browseBodyStyle: React.CSSProperties = {
padding: '0.5rem 0.75rem',
borderTop: '1px solid var(--border-color, #e0e0e0)',
maxHeight: 280,
overflowY: 'auto',
};
function browsePanelTitle(nodeType: string): string {
switch (nodeType) {
case 'sharepoint.uploadFile':
return 'Zielordner durchsuchen';
case 'sharepoint.listFiles':
return 'Ordner durchsuchen';
case 'sharepoint.readFile':
return 'Datei auswählen';
case 'sharepoint.downloadFile':
return 'Datei auswählen';
case 'sharepoint.findFile':
return 'Pfad aus Bibliothek wählen';
default:
return 'SharePoint durchsuchen';
}
}
/** Folder / location pickers — tree shows folders only; selecting sets folder path. */
function isFolderPickerNode(nodeType: string): boolean {
return nodeType === 'sharepoint.uploadFile' || nodeType === 'sharepoint.listFiles';
}
export const SharePointNodeConfig: React.FC<NodeConfigRendererProps> = ({
params,
updateParam,
instanceId,
request,
nodeType = 'sharepoint.findFile',
}) => {
const [connections, setConnections] = useState<UserConnection[]>([]);
const [browseExpanded, setBrowseExpanded] = useState(false);
const [findFileBrowseExpanded, setFindFileBrowseExpanded] = useState(false);
const [copySourceExpanded, setCopySourceExpanded] = useState(false);
const [copyDestExpanded, setCopyDestExpanded] = useState(false);
const [connectionsLoading, setConnectionsLoading] = useState(false);
const connectionId = (params.connectionId as string) ?? '';
const path =
(params.path as string) ?? (params.filePath as string) ?? '';
useEffect(() => {
if (instanceId && request) {
setConnectionsLoading(true);
fetchConnections(request, instanceId)
.then(setConnections)
.catch(() => setConnections([]))
.finally(() => setConnectionsLoading(false));
}
}, [instanceId, request]);
const loadChildren = useCallback(
async (pathToLoad: string): Promise<BrowseEntry[]> => {
if (!instanceId || !request || !connectionId) return [];
const r = await fetchBrowse(request, instanceId, connectionId, 'sharepoint', pathToLoad);
return r?.items ?? [];
},
[instanceId, request, connectionId]
);
const selectPath = useCallback(
(p: string) => {
updateParam('path', p);
setBrowseExpanded(false);
},
[updateParam]
);
const selectSearchQueryFromFile = useCallback(
(p: string) => {
updateParam('searchQuery', p);
setFindFileBrowseExpanded(false);
},
[updateParam]
);
const selectSourcePath = useCallback(
(p: string) => {
updateParam('sourcePath', p);
setCopySourceExpanded(false);
},
[updateParam]
);
const selectDestPath = useCallback(
(p: string) => {
updateParam('destPath', p);
setCopyDestExpanded(false);
},
[updateParam]
);
const needsSearch = nodeType === 'sharepoint.findFile';
const needsSiteId = false;
const showPathFieldsForList =
nodeType === 'sharepoint.listFiles';
const showPathFieldsForFileUploadDownload =
nodeType === 'sharepoint.readFile' ||
nodeType === 'sharepoint.uploadFile' ||
nodeType === 'sharepoint.downloadFile';
/** Path + browse (same tree wiring) for these types — not copyFile (copy uses its own trees). */
const showStandardPathBrowse =
connectionId &&
(showPathFieldsForList || showPathFieldsForFileUploadDownload);
const showFindFileBrowse = connectionId && needsSearch;
return (
<>
<div>
<label>Connection</label>
<select
value={connectionId}
onChange={(e) => updateParam('connectionId', e.target.value)}
disabled={connectionsLoading}
>
<option value="">{connectionsLoading ? 'Loading...' : 'Select connection'}</option>
{connections.map((c) => (
<option key={c.id} value={c.id}>
{c.externalUsername ?? c.id}
</option>
))}
</select>
</div>
{needsSearch && (
<div>
<label>Search query / path</label>
<input
value={(params.searchQuery as string) ?? ''}
onChange={(e) => updateParam('searchQuery', e.target.value)}
placeholder="/sites/SiteName/Shared Documents or search term"
/>
</div>
)}
{showPathFieldsForList && (
<div>
<label>Folder path</label>
<input
value={path}
onChange={(e) => updateParam('path', e.target.value)}
placeholder="/ or /sites/SiteName/Shared Documents/Folder"
/>
</div>
)}
{showPathFieldsForFileUploadDownload && (
<div>
<label>
{nodeType === 'sharepoint.uploadFile'
? 'Target folder path'
: nodeType === 'sharepoint.downloadFile'
? 'File path'
: 'Path'}
</label>
<input
value={(params.path as string) ?? (params.filePath as string) ?? ''}
onChange={(e) => updateParam('path', e.target.value)}
placeholder={
nodeType === 'sharepoint.downloadFile'
? '/sites/SiteName/Shared Documents/file.pdf'
: nodeType === 'sharepoint.uploadFile'
? '/sites/.../Shared Documents/TargetFolder/'
: 'File path'
}
/>
</div>
)}
{needsSiteId && (
<div>
<label>Site ID</label>
<input
value={(params.siteId as string) ?? ''}
onChange={(e) => updateParam('siteId', e.target.value)}
placeholder="SharePoint site ID"
/>
</div>
)}
{nodeType === 'sharepoint.copyFile' && (
<>
<div>
<label>Source file</label>
<input
value={(params.sourcePath as string) ?? ''}
onChange={(e) => updateParam('sourcePath', e.target.value)}
placeholder="/sites/.../folder/file.pdf"
/>
</div>
<div>
<label>Destination folder</label>
<input
value={(params.destPath as string) ?? ''}
onChange={(e) => updateParam('destPath', e.target.value)}
placeholder="/sites/.../target-folder/"
/>
</div>
{connectionId && (
<>
<details
open={copySourceExpanded}
onToggle={(e) => setCopySourceExpanded((e.target as HTMLDetailsElement).open)}
style={browseDetailsStyle}
>
<summary style={{ ...browseSummaryStyle, padding: '0.5rem 0.75rem' }}>
<span style={{ opacity: copySourceExpanded ? 0.7 : 1 }}>📂</span>
Quelldatei durchsuchen
</summary>
<div style={browseBodyStyle}>
<SharepointBrowseTree
rootPath="/"
onLoadChildren={loadChildren}
foldersOnly={false}
onSelectFile={selectSourcePath}
selectedPath={(params.sourcePath as string) || null}
/>
</div>
</details>
<details
open={copyDestExpanded}
onToggle={(e) => setCopyDestExpanded((e.target as HTMLDetailsElement).open)}
style={{ ...browseDetailsStyle, marginTop: 8 }}
>
<summary style={{ ...browseSummaryStyle, padding: '0.5rem 0.75rem' }}>
<span style={{ opacity: copyDestExpanded ? 0.7 : 1 }}>📂</span>
Zielordner durchsuchen
</summary>
<div style={browseBodyStyle}>
<SharepointBrowseTree
rootPath="/"
onLoadChildren={loadChildren}
foldersOnly
onSelectFile={() => {}}
onSelectFolder={selectDestPath}
selectedPath={(params.destPath as string) || null}
/>
</div>
</details>
</>
)}
</>
)}
{showStandardPathBrowse && (
<details
open={browseExpanded}
onToggle={(e) => setBrowseExpanded((e.target as HTMLDetailsElement).open)}
style={browseDetailsStyle}
>
<summary style={browseSummaryStyle}>
<span style={{ opacity: browseExpanded ? 0.7 : 1 }}>📂</span>
{browsePanelTitle(nodeType)}
</summary>
<div style={browseBodyStyle}>
{isFolderPickerNode(nodeType) && (
<SharepointBrowseTree
rootPath="/"
onLoadChildren={loadChildren}
foldersOnly
onSelectFile={() => {}}
onSelectFolder={selectPath}
selectedPath={path || null}
/>
)}
{(nodeType === 'sharepoint.readFile' || nodeType === 'sharepoint.downloadFile') && (
<SharepointBrowseTree
rootPath="/"
onLoadChildren={loadChildren}
onSelectFile={selectPath}
selectedPath={path || null}
/>
)}
</div>
</details>
)}
{showFindFileBrowse && (
<details
open={findFileBrowseExpanded}
onToggle={(e) => setFindFileBrowseExpanded((e.target as HTMLDetailsElement).open)}
style={browseDetailsStyle}
>
<summary style={browseSummaryStyle}>
<span style={{ opacity: findFileBrowseExpanded ? 0.7 : 1 }}>📂</span>
{browsePanelTitle('sharepoint.findFile')}
</summary>
<div style={browseBodyStyle}>
<SharepointBrowseTree
rootPath="/"
onLoadChildren={loadChildren}
onSelectFile={selectSearchQueryFromFile}
selectedPath={(params.searchQuery as string) || null}
/>
</div>
</details>
)}
</>
);
};