import React, { useCallback, useRef, useMemo, useState } from 'react'; import type { UdbContext } from './UnifiedDataBar'; import api from '../../api'; import { useApiRequest } from '../../hooks/useApi'; import { importWorkflowFromFile, WORKFLOW_FILE_EXTENSION, } from '../../api/workflowApi'; import { useToast } from '../../contexts/ToastContext'; import { FormGeneratorTree } from '../FormGenerator/FormGeneratorTree'; import { createFolderFileProvider } from '../FormGenerator/FormGeneratorTree/providers/FolderFileProvider'; import type { TreeNode } from '../FormGenerator/FormGeneratorTree'; import styles from './FilesTab.module.css'; import { useLanguage } from '../../providers/language/LanguageContext'; interface FilesTabProps { context: UdbContext; onFileSelect?: (fileId: string, fileName?: string) => void; onSendToChat?: (items: Array<{ id: string; type: 'file' | 'group'; name: string }>) => void; /** Wird aufgerufen, wenn ein ``.workflow.json``-File via Custom-Action in * den Graph-Editor importiert wurde. */ onWorkflowImported?: (workflowId: string) => void; } const FilesTab: React.FC = ({ context, onFileSelect, onSendToChat, onWorkflowImported }) => { const { t } = useLanguage(); const { request } = useApiRequest(); const { showSuccess, showError } = useToast(); const [isDragOver, setIsDragOver] = useState(false); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const provider = useMemo(() => createFolderFileProvider(), []); const [ownTreeKey, setOwnTreeKey] = useState(0); const [sharedTreeKey, setSharedTreeKey] = useState(0); const _handleNodeClick = useCallback((node: TreeNode) => { if (node.type === 'file') { onFileSelect?.(node.id, node.name); } }, [onFileSelect]); const _handleRefresh = useCallback(() => { setOwnTreeKey(k => k + 1); setSharedTreeKey(k => k + 1); }, []); const _uploadFiles = useCallback(async (fileList: FileList | File[]) => { if (!context.instanceId || uploading) return; setUploading(true); try { for (const file of Array.from(fileList)) { const formData = new FormData(); formData.append('file', file); formData.append('featureInstanceId', context.instanceId); await api.post('/api/files/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); } _handleRefresh(); } catch (err) { console.error('File upload failed:', err); } finally { setUploading(false); } }, [context.instanceId, uploading, _handleRefresh]); const _handleDragOver = useCallback((e: React.DragEvent) => { if (e.dataTransfer.types.includes('Files')) { e.preventDefault(); e.stopPropagation(); setIsDragOver(true); } }, []); const _handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); }, []); const _handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragOver(false); if (e.dataTransfer.files.length > 0) { _uploadFiles(e.dataTransfer.files); } }, [_uploadFiles]); const _handleFileInputChange = useCallback((e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { _uploadFiles(e.target.files); e.target.value = ''; } }, [_uploadFiles]); /* Workflow import is only available when embedded in the graph editor */ const _handleWorkflowImport = useCallback(async (fileId: string, fileName: string) => { if (context.surface !== 'graphEditor' || !context.instanceId) return; if (!fileName?.toLowerCase().endsWith(WORKFLOW_FILE_EXTENSION)) return; try { const result = await importWorkflowFromFile(request, context.instanceId, { fileId }); const warnings = result?.warnings ?? []; const wfId = result?.workflow?.id; if (warnings.length > 0) { showSuccess(t('Workflow importiert ({n} Warnungen).', { n: String(warnings.length) })); } else { showSuccess(t('Workflow importiert (deaktiviert).')); } if (wfId && onWorkflowImported) onWorkflowImported(wfId); } catch (e: unknown) { const msg = e instanceof Error ? e.message : String(e); showError(t('Import fehlgeschlagen: {msg}', { msg })); } }, [context.surface, context.instanceId, request, showSuccess, showError, t, onWorkflowImported]); const _handleNodeClickWithImport = useCallback((node: TreeNode) => { _handleNodeClick(node); if (node.type === 'file') { _handleWorkflowImport(node.id, node.name); } }, [_handleNodeClick, _handleWorkflowImport]); const _handleSendToChat = useCallback((node: TreeNode) => { onSendToChat?.([{ id: node.id, type: node.type === 'folder' ? 'group' : 'file', name: node.name }]); }, [onSendToChat]); return (
{isDragOver && (
{ e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; }} onDragLeave={(e) => { if (!e.relatedTarget || !(e.currentTarget as Node).contains(e.relatedTarget as Node)) { setIsDragOver(false); } }} onDrop={_handleDrop} > {t('Dateien hier ablegen')}
)}
{t('Dateien')}
{'\uD83D\uDC64'} {t('Persoenlich')} {'\uD83D\uDC65'} {t('Instanz')} {'\uD83C\uDFE2'} {t('Mandant')} {'\uD83D\uDD12'} {t('Neutralisiert')}
); }; export default FilesTab;