diff --git a/src/hooks/playground/useDashboardInputForm.ts b/src/hooks/playground/useDashboardInputForm.ts index bddba13..d9f0538 100644 --- a/src/hooks/playground/useDashboardInputForm.ts +++ b/src/hooks/playground/useDashboardInputForm.ts @@ -429,35 +429,36 @@ export function useDashboardInputForm(instanceId: string) { // Immediately remove document from UI for instant feedback setDeletedDocumentFileIds(prev => new Set([...prev, file.fileId])); - const success = await fileContext.handleFileDelete(file.fileId, () => { - setPendingFiles(prev => prev.filter(f => f.fileId !== file.fileId)); - }); - - if (success) { - setPendingFiles(prev => prev.filter(f => f.fileId !== file.fileId)); - - if (workflowId) { - const messagesWithFile = messages.filter((msg: WorkflowMessage) => { - const docs = (msg as any).documents as MessageDocument[] | undefined; - return docs?.some(doc => doc.fileId === file.fileId); + if (workflowId && file.messageId) { + // Document in a message: only remove the ChatDocument reference, keep the file itself + try { + await deleteFileFromMessageApi(request, workflowId, file.messageId, file.fileId); + } catch (error) { + // Restore document in UI on failure + setDeletedDocumentFileIds(prev => { + const next = new Set(prev); + next.delete(file.fileId); + return next; }); - - for (const message of messagesWithFile) { - try { - await deleteFileFromMessageApi(request, workflowId, message.id, file.fileId); - } catch (error) { - } - } } } else { - // Restore document in UI on failure - setDeletedDocumentFileIds(prev => { - const next = new Set(prev); - next.delete(file.fileId); - return next; + // Standalone file (pending file not yet in a message): delete the actual file + const success = await fileContext.handleFileDelete(file.fileId, () => { + setPendingFiles(prev => prev.filter(f => f.fileId !== file.fileId)); }); + + if (success) { + setPendingFiles(prev => prev.filter(f => f.fileId !== file.fileId)); + } else { + // Restore document in UI on failure + setDeletedDocumentFileIds(prev => { + const next = new Set(prev); + next.delete(file.fileId); + return next; + }); + } } - }, [workflowId, messages, fileContext, request]); + }, [workflowId, fileContext, request]); // handleFileView is a no-op because ViewActionButton's ContentPreview handles the preview internally const handleFileView = useCallback(async (_file: WorkflowFile) => { diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts index 9e89f0b..3abd5f3 100644 --- a/src/hooks/useApi.ts +++ b/src/hooks/useApi.ts @@ -74,6 +74,13 @@ export function useApiRequest() { // Generate cache key for GET requests (only cache GET requests) const cacheKey = method === 'get' ? generateCacheKey(url, method, params) : null; + // Mutating requests (POST/PUT/DELETE) invalidate the entire GET cache. + // This ensures refetch() after create/update/delete returns fresh data. + if (method !== 'get') { + requestCache.clear(); + cacheTimestamps.clear(); + } + // Check if we have a valid cached request for GET requests if (cacheKey && requestCache.has(cacheKey) && isCacheValid(cacheKey)) { console.log('🔧 useApiRequest: Using cached request', { url, method, cacheKey }); diff --git a/src/pages/admin/InstanceHierarchyView.tsx b/src/pages/admin/InstanceHierarchyView.tsx index 64c5874..ff1974e 100644 --- a/src/pages/admin/InstanceHierarchyView.tsx +++ b/src/pages/admin/InstanceHierarchyView.tsx @@ -81,16 +81,6 @@ function MandateContent({ return (
-
- -
- {mandateName} - - {mandateMeta.featureCount} Feature{mandateMeta.featureCount !== 1 ? 's' : ''} · {mandateMeta.instanceCount} Instanz{mandateMeta.instanceCount !== 1 ? 'en' : ''} - -
-
- {Object.entries(byFeature).map(([featureCode, featureInstances]) => { const featureUserCount = featureInstances.reduce( (sum, inst) => sum + (instanceUsersMap[inst.id]?.length ?? 0), diff --git a/src/pages/basedata/PromptsPage.tsx b/src/pages/basedata/PromptsPage.tsx index 8197027..f3d4435 100644 --- a/src/pages/basedata/PromptsPage.tsx +++ b/src/pages/basedata/PromptsPage.tsx @@ -127,6 +127,17 @@ export const PromptsPage: React.FC = () => { } }; + // Handle duplicate prompt + const handleDuplicate = async (prompt: Prompt) => { + const result = await handlePromptCreate({ + name: `Kopie von ${prompt.name || 'Prompt'}`, + content: prompt.content || '' + }); + if (result?.success) { + refetch(); + } + }; + // Handle delete single prompt (confirmation handled by DeleteActionButton) const handleDelete = async (prompt: Prompt) => { const success = await handlePromptDelete(prompt.id); @@ -217,6 +228,11 @@ export const PromptsPage: React.FC = () => { sortable={true} selectable={false} actionButtons={[ + ...(canCreate ? [{ + type: 'copy' as const, + title: 'Duplizieren', + onAction: handleDuplicate, + }] : []), ...(canUpdate ? [{ type: 'edit' as const, onAction: handleEditClick,