fixed ai call end to end with saas multimandate
This commit is contained in:
parent
28af4cb068
commit
f41e6d0088
30 changed files with 371 additions and 233 deletions
|
|
@ -24,6 +24,7 @@ import {
|
||||||
FaThList,
|
FaThList,
|
||||||
FaTh,
|
FaTh,
|
||||||
} from 'react-icons/fa';
|
} from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import {
|
import {
|
||||||
useAccessRules,
|
useAccessRules,
|
||||||
type AccessRule,
|
type AccessRule,
|
||||||
|
|
@ -529,6 +530,7 @@ export const AccessRulesEditor: React.FC<AccessRulesEditorProps> = ({
|
||||||
mandateId,
|
mandateId,
|
||||||
featureCode,
|
featureCode,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { showError } = useToast();
|
||||||
const {
|
const {
|
||||||
rules,
|
rules,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -580,9 +582,8 @@ export const AccessRulesEditor: React.FC<AccessRulesEditorProps> = ({
|
||||||
}, [updateRuleLocally]);
|
}, [updateRuleLocally]);
|
||||||
|
|
||||||
const handleDelete = useCallback((ruleId: string) => {
|
const handleDelete = useCallback((ruleId: string) => {
|
||||||
if (window.confirm('Möchten Sie diese Regel wirklich löschen?')) {
|
// Direct delete - rules are local until saved
|
||||||
removeRuleLocally(ruleId);
|
removeRuleLocally(ruleId);
|
||||||
}
|
|
||||||
}, [removeRuleLocally]);
|
}, [removeRuleLocally]);
|
||||||
|
|
||||||
const handleAdd = useCallback((ruleData: AccessRuleCreate) => {
|
const handleAdd = useCallback((ruleData: AccessRuleCreate) => {
|
||||||
|
|
@ -607,16 +608,15 @@ export const AccessRulesEditor: React.FC<AccessRulesEditorProps> = ({
|
||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
onSave?.();
|
onSave?.();
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Speichern');
|
showError('Fehler', result.error || 'Fehler beim Speichern');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (window.confirm('Alle Änderungen verwerfen?')) {
|
// Direct reset - user clicked the reset button intentionally
|
||||||
fetchRules().then(fetchedRules => {
|
fetchRules().then(fetchedRules => {
|
||||||
setOriginalRules(fetchedRules);
|
setOriginalRules(fetchedRules);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJsonApply = (newRules: AccessRule[]) => {
|
const handleJsonApply = (newRules: AccessRule[]) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { IoIosDownload } from 'react-icons/io';
|
||||||
|
import { useLanguage } from '../../../../providers/language/LanguageContext';
|
||||||
|
import styles from '../ActionButton.module.css';
|
||||||
|
|
||||||
|
export interface DownloadActionButtonProps<T = any> {
|
||||||
|
row: T;
|
||||||
|
onDownload: (row: T) => Promise<void> | void;
|
||||||
|
disabled?: boolean | { disabled: boolean; message?: string };
|
||||||
|
loading?: boolean;
|
||||||
|
className?: string;
|
||||||
|
title?: string;
|
||||||
|
hookData?: any;
|
||||||
|
idField?: string;
|
||||||
|
loadingStateName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DownloadActionButton<T = any>({
|
||||||
|
row,
|
||||||
|
onDownload,
|
||||||
|
disabled = false,
|
||||||
|
loading = false,
|
||||||
|
className = '',
|
||||||
|
title,
|
||||||
|
hookData,
|
||||||
|
idField = 'id',
|
||||||
|
loadingStateName = 'downloadingFiles'
|
||||||
|
}: DownloadActionButtonProps<T>) {
|
||||||
|
const { t } = useLanguage();
|
||||||
|
const [internalLoading, setInternalLoading] = useState(false);
|
||||||
|
|
||||||
|
// Extract disabled state and tooltip message
|
||||||
|
const isDisabled = typeof disabled === 'boolean' ? disabled : disabled?.disabled || false;
|
||||||
|
const disabledMessage = typeof disabled === 'object' ? disabled?.message : undefined;
|
||||||
|
|
||||||
|
const handleClick = async (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!isDisabled && !loading && !internalLoading) {
|
||||||
|
setInternalLoading(true);
|
||||||
|
try {
|
||||||
|
if (onDownload) {
|
||||||
|
await onDownload(row);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setInternalLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonTitle = title || t('files.action.download', 'Download');
|
||||||
|
// Use hookData loading state if available
|
||||||
|
const loadingState = hookData?.[loadingStateName];
|
||||||
|
const actualIsLoading = loadingState?.has((row as any)[idField]) || loading || internalLoading;
|
||||||
|
|
||||||
|
// Determine the final button title (tooltip)
|
||||||
|
const finalTitle = isDisabled && disabledMessage ? disabledMessage : buttonTitle;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleClick}
|
||||||
|
className={`${styles.actionButton} ${styles.download} ${actualIsLoading ? styles.loading : ''} ${isDisabled ? styles.disabled : ''} ${className}`}
|
||||||
|
title={finalTitle}
|
||||||
|
disabled={isDisabled || actualIsLoading}
|
||||||
|
>
|
||||||
|
<span className={styles.actionIcon}>
|
||||||
|
{actualIsLoading ? '⏳' : <IoIosDownload />}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DownloadActionButton;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { DownloadActionButton, type DownloadActionButtonProps } from './DownloadActionButton';
|
||||||
|
export { DownloadActionButton as default } from './DownloadActionButton';
|
||||||
|
|
@ -4,6 +4,7 @@ export { DeleteActionButton } from './DeleteActionButton';
|
||||||
export { ViewActionButton } from './ViewActionButton';
|
export { ViewActionButton } from './ViewActionButton';
|
||||||
export { CopyActionButton } from './CopyActionButton';
|
export { CopyActionButton } from './CopyActionButton';
|
||||||
export { RemoveActionButton } from './RemoveActionButton';
|
export { RemoveActionButton } from './RemoveActionButton';
|
||||||
|
export { DownloadActionButton } from './DownloadActionButton';
|
||||||
|
|
||||||
// Generic Custom Action Button (for entity-specific actions)
|
// Generic Custom Action Button (for entity-specific actions)
|
||||||
export { CustomActionButton } from './CustomActionButton';
|
export { CustomActionButton } from './CustomActionButton';
|
||||||
|
|
@ -14,4 +15,5 @@ export type { DeleteActionButtonProps } from './DeleteActionButton';
|
||||||
export type { ViewActionButtonProps } from './ViewActionButton';
|
export type { ViewActionButtonProps } from './ViewActionButton';
|
||||||
export type { CopyActionButtonProps } from './CopyActionButton';
|
export type { CopyActionButtonProps } from './CopyActionButton';
|
||||||
export type { RemoveActionButtonProps } from './RemoveActionButton';
|
export type { RemoveActionButtonProps } from './RemoveActionButton';
|
||||||
|
export type { DownloadActionButtonProps } from './DownloadActionButton';
|
||||||
export type { CustomActionButtonProps } from './CustomActionButton';
|
export type { CustomActionButtonProps } from './CustomActionButton';
|
||||||
|
|
|
||||||
|
|
@ -853,7 +853,23 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
const minHeight = minRows * 1.5 * 16;
|
const minHeight = minRows * 1.5 * 16;
|
||||||
const maxHeight = maxRows * 1.5 * 16;
|
const maxHeight = maxRows * 1.5 * 16;
|
||||||
|
|
||||||
const currentValue = value || '';
|
// Handle object/array values by converting to JSON string for display
|
||||||
|
const isObjectValue = typeof value === 'object' && value !== null;
|
||||||
|
// Check if string value looks like JSON (for fields that were originally objects but temporarily invalid)
|
||||||
|
const looksLikeJson = typeof value === 'string' && value.trim().match(/^[\[{]/);
|
||||||
|
const isJsonField = isObjectValue || looksLikeJson;
|
||||||
|
|
||||||
|
let currentValue = '';
|
||||||
|
if (isObjectValue) {
|
||||||
|
try {
|
||||||
|
currentValue = JSON.stringify(value, null, 2);
|
||||||
|
} catch {
|
||||||
|
currentValue = String(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentValue = value || '';
|
||||||
|
}
|
||||||
|
|
||||||
const isContentField = attr.name === 'content' || attr.name.toLowerCase().includes('content');
|
const isContentField = attr.name === 'content' || attr.name.toLowerCase().includes('content');
|
||||||
const textareaClassName = isContentField
|
const textareaClassName = isContentField
|
||||||
? `${styles.fieldTextarea} ${styles.contentTextarea} ${hasError ? styles.fieldError : ''}`
|
? `${styles.fieldTextarea} ${styles.contentTextarea} ${hasError ? styles.fieldError : ''}`
|
||||||
|
|
@ -865,7 +881,20 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
name={attr.name}
|
name={attr.name}
|
||||||
value={currentValue}
|
value={currentValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleFieldChange(attr.name, e.target.value);
|
const newTextValue = e.target.value;
|
||||||
|
const trimmed = newTextValue.trim();
|
||||||
|
// Try to parse as JSON if it looks like JSON (starts with { or [)
|
||||||
|
if (trimmed.match(/^[\[{]/)) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(newTextValue);
|
||||||
|
handleFieldChange(attr.name, parsed);
|
||||||
|
} catch {
|
||||||
|
// If parsing fails, store as string (user is still typing)
|
||||||
|
handleFieldChange(attr.name, newTextValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleFieldChange(attr.name, newTextValue);
|
||||||
|
}
|
||||||
const textarea = e.target;
|
const textarea = e.target;
|
||||||
textarea.style.height = 'auto';
|
textarea.style.height = 'auto';
|
||||||
const scrollHeight = textarea.scrollHeight;
|
const scrollHeight = textarea.scrollHeight;
|
||||||
|
|
@ -879,6 +908,7 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
onBlur={() => handleFieldFocus(attr.name, false)}
|
onBlur={() => handleFieldFocus(attr.name, false)}
|
||||||
className={textareaClassName}
|
className={textareaClassName}
|
||||||
rows={minRows}
|
rows={minRows}
|
||||||
|
style={isJsonField ? { fontFamily: 'monospace', fontSize: '0.85em' } : undefined}
|
||||||
ref={(textarea) => {
|
ref={(textarea) => {
|
||||||
if (textarea) {
|
if (textarea) {
|
||||||
textarea.style.setProperty('min-height', `${minHeight}px`, 'important');
|
textarea.style.setProperty('min-height', `${minHeight}px`, 'important');
|
||||||
|
|
@ -888,7 +918,7 @@ export function FormGeneratorForm<T extends Record<string, any>>({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<label className={getLabelClass(attr.name, value)}>
|
<label className={getLabelClass(attr.name, currentValue)}>
|
||||||
{attr.label}
|
{attr.label}
|
||||||
{attr.required && <span className={styles.required}>*</span>}
|
{attr.required && <span className={styles.required}>*</span>}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,6 @@ export function FormGeneratorList<T extends Record<string, any>>({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('No delete handler found in hookData or props');
|
console.warn('No delete handler found in hookData or props');
|
||||||
alert('No delete handler configured');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (onDeleteMultiple) {
|
} else if (onDeleteMultiple) {
|
||||||
|
|
@ -388,7 +387,6 @@ export function FormGeneratorList<T extends Record<string, any>>({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('No delete handler provided');
|
console.warn('No delete handler provided');
|
||||||
alert('No delete handler configured');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,7 +396,6 @@ export function FormGeneratorList<T extends Record<string, any>>({
|
||||||
console.log('Delete completed, selection cleared');
|
console.log('Delete completed, selection cleared');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete failed:', error);
|
console.error('Delete failed:', error);
|
||||||
alert(`Delete failed: ${error}`);
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,13 @@ function SpeechSettings({ onDataUpdate }: SpeechSettingsProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (window.confirm(t('speech.settings.reset_confirm'))) {
|
// Direct reset - user clicked the reset button intentionally
|
||||||
localStorage.removeItem('speechSignUpData');
|
localStorage.removeItem('speechSignUpData');
|
||||||
localStorage.removeItem('speechSignUpTimestamp');
|
localStorage.removeItem('speechSignUpTimestamp');
|
||||||
window.dispatchEvent(new CustomEvent('speechSignUpChanged'));
|
window.dispatchEvent(new CustomEvent('speechSignUpChanged'));
|
||||||
setFormData(null);
|
setFormData(null);
|
||||||
setSaveMessage({ type: 'success', text: t('speech.settings.reset_success') });
|
setSaveMessage({ type: 'success', text: t('speech.settings.reset_success') });
|
||||||
setTimeout(() => setSaveMessage(null), 3000);
|
setTimeout(() => setSaveMessage(null), 3000);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ export interface ChatMessageProps {
|
||||||
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileView?: (file: WorkflowFile) => Promise<void>;
|
onFileView?: (file: WorkflowFile) => Promise<void>;
|
||||||
|
onFileDownload?: (file: WorkflowFile) => Promise<void>;
|
||||||
deletingFiles?: Set<string>;
|
deletingFiles?: Set<string>;
|
||||||
previewingFiles?: Set<string>;
|
previewingFiles?: Set<string>;
|
||||||
removingFiles?: Set<string>;
|
removingFiles?: Set<string>;
|
||||||
|
downloadingFiles?: Set<string>;
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,9 +32,11 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||||
onFileDelete,
|
onFileDelete,
|
||||||
onFileRemove,
|
onFileRemove,
|
||||||
onFileView,
|
onFileView,
|
||||||
|
onFileDownload,
|
||||||
deletingFiles,
|
deletingFiles,
|
||||||
previewingFiles,
|
previewingFiles,
|
||||||
removingFiles,
|
removingFiles,
|
||||||
|
downloadingFiles,
|
||||||
workflowId
|
workflowId
|
||||||
}) => {
|
}) => {
|
||||||
const isUser = message.role?.toLowerCase() === 'user';
|
const isUser = message.role?.toLowerCase() === 'user';
|
||||||
|
|
@ -131,9 +135,11 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||||
onFileDelete={onFileDelete}
|
onFileDelete={onFileDelete}
|
||||||
onFileRemove={onFileRemove}
|
onFileRemove={onFileRemove}
|
||||||
onFileView={onFileView}
|
onFileView={onFileView}
|
||||||
|
onFileDownload={onFileDownload}
|
||||||
deletingFiles={deletingFiles}
|
deletingFiles={deletingFiles}
|
||||||
previewingFiles={previewingFiles}
|
previewingFiles={previewingFiles}
|
||||||
removingFiles={removingFiles}
|
removingFiles={removingFiles}
|
||||||
|
downloadingFiles={downloadingFiles}
|
||||||
workflowId={workflowId}
|
workflowId={workflowId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { MessageDocument, Message } from '../MessagesTypes';
|
import { MessageDocument, Message } from '../MessagesTypes';
|
||||||
import { formatFileSize } from '../MessageUtils';
|
import { formatFileSize } from '../MessageUtils';
|
||||||
import { ViewActionButton, DeleteActionButton, RemoveActionButton } from '../../../FormGenerator/ActionButtons';
|
import { ViewActionButton, DeleteActionButton, RemoveActionButton, DownloadActionButton } from '../../../FormGenerator/ActionButtons';
|
||||||
import { WorkflowFile } from '../../../../hooks/usePlayground';
|
import { WorkflowFile } from '../../../../hooks/usePlayground';
|
||||||
import styles from '../Messages.module.css';
|
import styles from '../Messages.module.css';
|
||||||
|
|
||||||
|
|
@ -12,9 +12,11 @@ export interface DocumentItemProps {
|
||||||
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileView?: (file: WorkflowFile) => Promise<void>;
|
onFileView?: (file: WorkflowFile) => Promise<void>;
|
||||||
|
onFileDownload?: (file: WorkflowFile) => Promise<void>;
|
||||||
deletingFiles?: Set<string>;
|
deletingFiles?: Set<string>;
|
||||||
previewingFiles?: Set<string>;
|
previewingFiles?: Set<string>;
|
||||||
removingFiles?: Set<string>;
|
removingFiles?: Set<string>;
|
||||||
|
downloadingFiles?: Set<string>;
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,9 +30,11 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
onFileDelete,
|
onFileDelete,
|
||||||
onFileRemove,
|
onFileRemove,
|
||||||
onFileView,
|
onFileView,
|
||||||
|
onFileDownload,
|
||||||
deletingFiles = new Set(),
|
deletingFiles = new Set(),
|
||||||
previewingFiles = new Set(),
|
previewingFiles = new Set(),
|
||||||
removingFiles = new Set(),
|
removingFiles = new Set(),
|
||||||
|
downloadingFiles = new Set(),
|
||||||
workflowId: _workflowId
|
workflowId: _workflowId
|
||||||
}) => {
|
}) => {
|
||||||
// Convert MessageDocument to WorkflowFile format for compatibility with action buttons
|
// Convert MessageDocument to WorkflowFile format for compatibility with action buttons
|
||||||
|
|
@ -47,6 +51,7 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
const isDeleting = deletingFiles.has(document.fileId);
|
const isDeleting = deletingFiles.has(document.fileId);
|
||||||
const isPreviewing = previewingFiles.has(document.fileId);
|
const isPreviewing = previewingFiles.has(document.fileId);
|
||||||
const isRemoving = removingFiles.has(document.fileId);
|
const isRemoving = removingFiles.has(document.fileId);
|
||||||
|
const isDownloading = downloadingFiles.has(document.fileId);
|
||||||
|
|
||||||
// Create hookData object for action buttons
|
// Create hookData object for action buttons
|
||||||
const hookData = useMemo(() => ({
|
const hookData = useMemo(() => ({
|
||||||
|
|
@ -65,8 +70,9 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
},
|
},
|
||||||
deletingItems: deletingFiles,
|
deletingItems: deletingFiles,
|
||||||
previewingFiles: previewingFiles,
|
previewingFiles: previewingFiles,
|
||||||
removingItems: removingFiles
|
removingItems: removingFiles,
|
||||||
}), [onFileDelete, workflowFile, deletingFiles, previewingFiles, removingFiles]);
|
downloadingFiles: downloadingFiles
|
||||||
|
}), [onFileDelete, workflowFile, deletingFiles, previewingFiles, removingFiles, downloadingFiles]);
|
||||||
|
|
||||||
const handleView = async () => {
|
const handleView = async () => {
|
||||||
if (onFileView) {
|
if (onFileView) {
|
||||||
|
|
@ -74,6 +80,12 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
if (onFileDownload) {
|
||||||
|
await onFileDownload(workflowFile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemove = async () => {
|
const handleRemove = async () => {
|
||||||
if (onFileRemove) {
|
if (onFileRemove) {
|
||||||
await onFileRemove(workflowFile);
|
await onFileRemove(workflowFile);
|
||||||
|
|
@ -89,7 +101,7 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
{formatFileSize(document.fileSize)} • {document.mimeType}
|
{formatFileSize(document.fileSize)} • {document.mimeType}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(onFileView || onFileDelete || onFileRemove) && (
|
{(onFileView || onFileDownload || onFileDelete || onFileRemove) && (
|
||||||
<div style={{ display: 'flex', gap: '4px', flexShrink: 0 }}>
|
<div style={{ display: 'flex', gap: '4px', flexShrink: 0 }}>
|
||||||
{onFileView && (
|
{onFileView && (
|
||||||
<ViewActionButton
|
<ViewActionButton
|
||||||
|
|
@ -103,6 +115,17 @@ export const DocumentItem: React.FC<DocumentItemProps> = ({
|
||||||
typeField="mimeType"
|
typeField="mimeType"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{onFileDownload && (
|
||||||
|
<DownloadActionButton
|
||||||
|
row={workflowFile}
|
||||||
|
onDownload={handleDownload}
|
||||||
|
disabled={isDeleting || isRemoving}
|
||||||
|
loading={isDownloading}
|
||||||
|
hookData={hookData}
|
||||||
|
idField="fileId"
|
||||||
|
loadingStateName="downloadingFiles"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{onFileRemove && (
|
{onFileRemove && (
|
||||||
<RemoveActionButton
|
<RemoveActionButton
|
||||||
row={workflowFile}
|
row={workflowFile}
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,11 @@ const Messages: React.FC<MessagesProps> = ({
|
||||||
onFileDelete,
|
onFileDelete,
|
||||||
onFileRemove,
|
onFileRemove,
|
||||||
onFileView,
|
onFileView,
|
||||||
|
onFileDownload,
|
||||||
deletingFiles,
|
deletingFiles,
|
||||||
previewingFiles,
|
previewingFiles,
|
||||||
removingFiles,
|
removingFiles,
|
||||||
|
downloadingFiles,
|
||||||
workflowId
|
workflowId
|
||||||
}) => {
|
}) => {
|
||||||
if (!messages || messages.length === 0) {
|
if (!messages || messages.length === 0) {
|
||||||
|
|
@ -61,9 +63,11 @@ const Messages: React.FC<MessagesProps> = ({
|
||||||
onFileDelete={onFileDelete}
|
onFileDelete={onFileDelete}
|
||||||
onFileRemove={onFileRemove}
|
onFileRemove={onFileRemove}
|
||||||
onFileView={onFileView}
|
onFileView={onFileView}
|
||||||
|
onFileDownload={onFileDownload}
|
||||||
deletingFiles={deletingFiles}
|
deletingFiles={deletingFiles}
|
||||||
previewingFiles={previewingFiles}
|
previewingFiles={previewingFiles}
|
||||||
removingFiles={removingFiles}
|
removingFiles={removingFiles}
|
||||||
|
downloadingFiles={downloadingFiles}
|
||||||
workflowId={workflowId}
|
workflowId={workflowId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
@ -79,9 +83,11 @@ const Messages: React.FC<MessagesProps> = ({
|
||||||
onFileDelete={onFileDelete}
|
onFileDelete={onFileDelete}
|
||||||
onFileRemove={onFileRemove}
|
onFileRemove={onFileRemove}
|
||||||
onFileView={onFileView}
|
onFileView={onFileView}
|
||||||
|
onFileDownload={onFileDownload}
|
||||||
deletingFiles={deletingFiles}
|
deletingFiles={deletingFiles}
|
||||||
previewingFiles={previewingFiles}
|
previewingFiles={previewingFiles}
|
||||||
removingFiles={removingFiles}
|
removingFiles={removingFiles}
|
||||||
|
downloadingFiles={downloadingFiles}
|
||||||
workflowId={workflowId}
|
workflowId={workflowId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,11 @@ export interface MessagesProps {
|
||||||
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
onFileDelete?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
onFileRemove?: (file: WorkflowFile) => Promise<void>;
|
||||||
onFileView?: (file: WorkflowFile) => Promise<void>;
|
onFileView?: (file: WorkflowFile) => Promise<void>;
|
||||||
|
onFileDownload?: (file: WorkflowFile) => Promise<void>;
|
||||||
deletingFiles?: Set<string>;
|
deletingFiles?: Set<string>;
|
||||||
previewingFiles?: Set<string>;
|
previewingFiles?: Set<string>;
|
||||||
removingFiles?: Set<string>;
|
removingFiles?: Set<string>;
|
||||||
|
downloadingFiles?: Set<string>;
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ interface FileContextType {
|
||||||
refetch: () => Promise<void>;
|
refetch: () => Promise<void>;
|
||||||
handleFileUpload: (file: File, workflowId?: string) => Promise<{ success: boolean; fileData?: any; error?: string }>;
|
handleFileUpload: (file: File, workflowId?: string) => Promise<{ success: boolean; fileData?: any; error?: string }>;
|
||||||
handleFileDelete: (fileId: string, onOptimisticDelete?: () => void) => Promise<boolean>;
|
handleFileDelete: (fileId: string, onOptimisticDelete?: () => void) => Promise<boolean>;
|
||||||
|
handleFilePreview: (fileId: string, fileName: string, mimeType?: string) => Promise<{ success: boolean; previewUrl?: string | null; blob?: Blob; isJsonContent?: boolean; decodedContent?: string; error?: string }>;
|
||||||
|
handleFileDownload: (fileId: string, fileName: string) => Promise<void>;
|
||||||
uploadingFile: boolean;
|
uploadingFile: boolean;
|
||||||
deletingFiles: Set<string>;
|
deletingFiles: Set<string>;
|
||||||
previewingFiles: Set<string>;
|
previewingFiles: Set<string>;
|
||||||
|
downloadingFiles: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileContext = createContext<FileContextType | undefined>(undefined);
|
const FileContext = createContext<FileContextType | undefined>(undefined);
|
||||||
|
|
@ -20,9 +23,12 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
||||||
const {
|
const {
|
||||||
handleFileUpload: hookHandleFileUpload,
|
handleFileUpload: hookHandleFileUpload,
|
||||||
handleFileDelete: hookHandleFileDelete,
|
handleFileDelete: hookHandleFileDelete,
|
||||||
|
handleFilePreview,
|
||||||
|
handleFileDownload,
|
||||||
uploadingFile,
|
uploadingFile,
|
||||||
deletingFiles,
|
deletingFiles,
|
||||||
previewingFiles
|
previewingFiles,
|
||||||
|
downloadingFiles
|
||||||
} = useFileOperations();
|
} = useFileOperations();
|
||||||
|
|
||||||
// Centralized file upload that updates the shared state
|
// Centralized file upload that updates the shared state
|
||||||
|
|
@ -77,9 +83,12 @@ export function FileProvider({ children }: { children: React.ReactNode }) {
|
||||||
refetch,
|
refetch,
|
||||||
handleFileUpload,
|
handleFileUpload,
|
||||||
handleFileDelete,
|
handleFileDelete,
|
||||||
|
handleFilePreview,
|
||||||
|
handleFileDownload,
|
||||||
uploadingFile,
|
uploadingFile,
|
||||||
deletingFiles,
|
deletingFiles,
|
||||||
previewingFiles
|
previewingFiles,
|
||||||
|
downloadingFiles
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ export function useDashboardInputForm() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkPermissions = async () => {
|
const checkPermissions = async () => {
|
||||||
try {
|
try {
|
||||||
const uiPerm = await canView('UI', 'playground');
|
const uiPerm = await canView('UI', 'ui.system.playground');
|
||||||
setPlaygroundUIPermission(uiPerm);
|
setPlaygroundUIPermission(uiPerm);
|
||||||
|
|
||||||
if (uiPerm) {
|
if (uiPerm) {
|
||||||
|
|
@ -477,6 +477,17 @@ export function useDashboardInputForm() {
|
||||||
}
|
}
|
||||||
}, [workflowId, messages, fileContext, request]);
|
}, [workflowId, messages, fileContext, request]);
|
||||||
|
|
||||||
|
// handleFileView is a no-op because ViewActionButton's ContentPreview handles the preview internally
|
||||||
|
const handleFileView = useCallback(async (_file: WorkflowFile) => {
|
||||||
|
// The ViewActionButton component handles the preview via ContentPreview
|
||||||
|
// No additional action needed here
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleFileDownload = useCallback(async (file: WorkflowFile) => {
|
||||||
|
if (!file.fileId) return;
|
||||||
|
await fileContext.handleFileDownload(file.fileId, file.fileName);
|
||||||
|
}, [fileContext]);
|
||||||
|
|
||||||
const onInputChange = useCallback((value: string) => {
|
const onInputChange = useCallback((value: string) => {
|
||||||
setInputValue(value);
|
setInputValue(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -798,9 +809,12 @@ export function useDashboardInputForm() {
|
||||||
handleFileUpload,
|
handleFileUpload,
|
||||||
handleFileDelete,
|
handleFileDelete,
|
||||||
handleFileRemove,
|
handleFileRemove,
|
||||||
|
handleFileView,
|
||||||
uploadingFile: fileContext.uploadingFile,
|
uploadingFile: fileContext.uploadingFile,
|
||||||
deletingFiles: fileContext.deletingFiles,
|
deletingFiles: fileContext.deletingFiles,
|
||||||
previewingFiles: fileContext.previewingFiles,
|
previewingFiles: fileContext.previewingFiles,
|
||||||
|
downloadingFiles: fileContext.downloadingFiles,
|
||||||
|
handleFileDownload,
|
||||||
isFileAttachmentPopupOpen,
|
isFileAttachmentPopupOpen,
|
||||||
setIsFileAttachmentPopupOpen,
|
setIsFileAttachmentPopupOpen,
|
||||||
allUserFiles: fileContext.files || [],
|
allUserFiles: fileContext.files || [],
|
||||||
|
|
|
||||||
|
|
@ -515,13 +515,22 @@
|
||||||
border: 1px solid var(--border-color, #e0e0e0);
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logEntry {
|
.logEntry {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0.25rem 0;
|
padding: 0.25rem 0;
|
||||||
border-bottom: 1px solid var(--border-color, #e0e0e0);
|
border-bottom: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logEntry:last-child {
|
.logEntry:last-child {
|
||||||
|
|
@ -530,7 +539,8 @@
|
||||||
|
|
||||||
.logTime {
|
.logTime {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
flex-shrink: 0;
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logStatus {
|
.logStatus {
|
||||||
|
|
@ -538,13 +548,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.logMessage {
|
.logMessage {
|
||||||
flex: 1;
|
flex: 1 1 auto;
|
||||||
word-break: break-word;
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logProgress {
|
.logProgress {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
flex-shrink: 0;
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logs history */
|
/* Logs history */
|
||||||
|
|
|
||||||
|
|
@ -327,17 +327,15 @@ export const AdminFeatureInstanceUsersPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle remove user
|
// Handle remove user (confirmation handled by DeleteActionButton)
|
||||||
const handleRemoveUser = async (user: FeatureAccessUser) => {
|
const handleRemoveUser = async (user: FeatureAccessUser) => {
|
||||||
if (!selectedMandateId || !selectedInstanceId) return;
|
if (!selectedMandateId || !selectedInstanceId) return;
|
||||||
if (window.confirm(`Möchten Sie den Benutzer "${user.username}" wirklich aus dieser Feature-Instanz entfernen?`)) {
|
const result = await removeUserFromInstance(selectedMandateId, selectedInstanceId, user.userId);
|
||||||
const result = await removeUserFromInstance(selectedMandateId, selectedInstanceId, user.userId);
|
if (result.success) {
|
||||||
if (result.success) {
|
refreshUsers();
|
||||||
refreshUsers();
|
showSuccess('Benutzer entfernt', `"${user.username}" wurde aus der Feature-Instanz entfernt.`);
|
||||||
showSuccess('Benutzer entfernt', `"${user.username}" wurde aus der Feature-Instanz entfernt.`);
|
} else {
|
||||||
} else {
|
showError('Fehler', result.error || 'Fehler beim Entfernen des Benutzers');
|
||||||
showError('Fehler', result.error || 'Fehler beim Entfernen des Benutzers');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { FormGeneratorTable } from '../../components/FormGenerator/FormGenerator
|
||||||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { AccessRulesEditor } from '../../components/AccessRules';
|
import { AccessRulesEditor } from '../../components/AccessRules';
|
||||||
import { FaPlus, FaSync, FaUserShield, FaCube, FaShieldAlt } from 'react-icons/fa';
|
import { FaPlus, FaSync, FaUserShield, FaCube, FaShieldAlt } from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ interface FeatureRole {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdminFeatureRolesPage: React.FC = () => {
|
export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
|
const { showError } = useToast();
|
||||||
// State
|
// State
|
||||||
const [features, setFeatures] = useState<Feature[]>([]);
|
const [features, setFeatures] = useState<Feature[]>([]);
|
||||||
const [selectedFeatureCode, setSelectedFeatureCode] = useState<string>('');
|
const [selectedFeatureCode, setSelectedFeatureCode] = useState<string>('');
|
||||||
|
|
@ -197,7 +199,7 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
await fetchRoles();
|
await fetchRoles();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Error creating role:', err);
|
console.error('Error creating role:', err);
|
||||||
alert(err.response?.data?.detail || 'Fehler beim Erstellen der Rolle');
|
showError('Fehler', err.response?.data?.detail || 'Fehler beim Erstellen der Rolle');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -216,22 +218,20 @@ export const AdminFeatureRolesPage: React.FC = () => {
|
||||||
await fetchRoles();
|
await fetchRoles();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Error updating role:', err);
|
console.error('Error updating role:', err);
|
||||||
alert(err.response?.data?.detail || 'Fehler beim Aktualisieren der Rolle');
|
showError('Fehler', err.response?.data?.detail || 'Fehler beim Aktualisieren der Rolle');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete role
|
// Handle delete role (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteRole = async (role: FeatureRole) => {
|
const handleDeleteRole = async (role: FeatureRole) => {
|
||||||
if (window.confirm(`Möchten Sie die Rolle "${role.roleLabel}" wirklich löschen?`)) {
|
try {
|
||||||
try {
|
await api.delete(`/api/rbac/roles/${role.id}`);
|
||||||
await api.delete(`/api/rbac/roles/${role.id}`);
|
await fetchRoles();
|
||||||
await fetchRoles();
|
} catch (err: any) {
|
||||||
} catch (err: any) {
|
console.error('Error deleting role:', err);
|
||||||
console.error('Error deleting role:', err);
|
showError('Fehler', err.response?.data?.detail || 'Fehler beim Löschen der Rolle');
|
||||||
alert(err.response?.data?.detail || 'Fehler beim Löschen der Rolle');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@ import { useUserMandates, type Mandate, type Role } from '../../hooks/useUserMan
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { FaPlus, FaSync, FaEnvelopeOpenText, FaBuilding, FaCopy, FaLink } from 'react-icons/fa';
|
import { FaPlus, FaSync, FaEnvelopeOpenText, FaBuilding, FaCopy, FaLink } from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
export const AdminInvitationsPage: React.FC = () => {
|
export const AdminInvitationsPage: React.FC = () => {
|
||||||
|
const { showError } = useToast();
|
||||||
const {
|
const {
|
||||||
invitations,
|
invitations,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -188,7 +190,7 @@ export const AdminInvitationsPage: React.FC = () => {
|
||||||
setShowUrlModal(result.data);
|
setShowUrlModal(result.data);
|
||||||
fetchInvitations(selectedMandateId, { includeExpired: showExpired, includeUsed: showUsed });
|
fetchInvitations(selectedMandateId, { includeExpired: showExpired, includeUsed: showUsed });
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Erstellen der Einladung');
|
showError('Fehler', result.error || 'Fehler beim Erstellen der Einladung');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
@ -201,7 +203,7 @@ export const AdminInvitationsPage: React.FC = () => {
|
||||||
if (!selectedMandateId) return false;
|
if (!selectedMandateId) return false;
|
||||||
const result = await revokeInvitation(selectedMandateId, invitationId);
|
const result = await revokeInvitation(selectedMandateId, invitationId);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
alert(result.error || 'Fehler beim Widerrufen der Einladung');
|
showError('Fehler', result.error || 'Fehler beim Widerrufen der Einladung');
|
||||||
}
|
}
|
||||||
return result.success;
|
return result.success;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,12 @@ import { useUserMandates, type Mandate } from '../../hooks/useUserMandates';
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { FaPlus, FaSync, FaUserShield, FaBuilding, FaGlobe } from 'react-icons/fa';
|
import { FaPlus, FaSync, FaUserShield, FaBuilding, FaGlobe } from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
export const AdminMandateRolesPage: React.FC = () => {
|
export const AdminMandateRolesPage: React.FC = () => {
|
||||||
|
const { showError, showWarning } = useToast();
|
||||||
const {
|
const {
|
||||||
roles,
|
roles,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -209,11 +211,11 @@ export const AdminMandateRolesPage: React.FC = () => {
|
||||||
setShowCreateModal(false);
|
setShowCreateModal(false);
|
||||||
await fetchRoles(selectedMandateId, { scopeFilter });
|
await fetchRoles(selectedMandateId, { scopeFilter });
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Erstellen der Rolle');
|
showError('Fehler', result.error || 'Fehler beim Erstellen der Rolle');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Create role error:', err);
|
console.error('Create role error:', err);
|
||||||
alert(err.message || 'Fehler beim Erstellen der Rolle');
|
showError('Fehler', err.message || 'Fehler beim Erstellen der Rolle');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|
@ -248,31 +250,29 @@ export const AdminMandateRolesPage: React.FC = () => {
|
||||||
await fetchRoles(selectedMandateId, { scopeFilter });
|
await fetchRoles(selectedMandateId, { scopeFilter });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Aktualisieren der Rolle');
|
showError('Fehler', result.error || 'Fehler beim Aktualisieren der Rolle');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Update role error:', err);
|
console.error('Update role error:', err);
|
||||||
alert(err.message || 'Fehler beim Aktualisieren der Rolle');
|
showError('Fehler', err.message || 'Fehler beim Aktualisieren der Rolle');
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete role
|
// Handle delete role (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteRole = async (role: Role) => {
|
const handleDeleteRole = async (role: Role) => {
|
||||||
if (role.isSystemRole) {
|
if (role.isSystemRole) {
|
||||||
alert('System-Rollen können nicht gelöscht werden.');
|
showWarning('Nicht erlaubt', 'System-Rollen können nicht gelöscht werden.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.confirm(`Möchten Sie die Rolle "${role.roleLabel}" wirklich löschen?`)) {
|
const result = await deleteRole(role.id);
|
||||||
const result = await deleteRole(role.id);
|
if (result.success) {
|
||||||
if (result.success) {
|
// Refetch to update the list
|
||||||
// Refetch to update the list
|
await fetchRoles(selectedMandateId, { scopeFilter });
|
||||||
await fetchRoles(selectedMandateId, { scopeFilter });
|
} else {
|
||||||
} else {
|
showError('Fehler', result.error || 'Fehler beim Löschen der Rolle');
|
||||||
alert(result.error || 'Fehler beim Löschen der Rolle');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,11 +73,9 @@ export const AdminMandatesPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteMandate = async (mandate: Mandate) => {
|
const handleDeleteMandate = async (mandate: Mandate) => {
|
||||||
if (window.confirm(`Möchten Sie den Mandanten "${mandate.name || mandate.id}" wirklich löschen?`)) {
|
await handleDelete(mandate.id);
|
||||||
await handleDelete(mandate.id);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ import { useUserMandates, type MandateUser, type Mandate, type Role, type Pagina
|
||||||
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm, type AttributeDefinition } from '../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { FaPlus, FaSync, FaUsers, FaBuilding } from 'react-icons/fa';
|
import { FaPlus, FaSync, FaUsers, FaBuilding } from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import styles from './Admin.module.css';
|
import styles from './Admin.module.css';
|
||||||
|
|
||||||
export const AdminUserMandatesPage: React.FC = () => {
|
export const AdminUserMandatesPage: React.FC = () => {
|
||||||
|
const { showError } = useToast();
|
||||||
const {
|
const {
|
||||||
users,
|
users,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -206,7 +208,7 @@ export const AdminUserMandatesPage: React.FC = () => {
|
||||||
setShowAddModal(false);
|
setShowAddModal(false);
|
||||||
fetchMandateUsers(selectedMandateId);
|
fetchMandateUsers(selectedMandateId);
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Hinzufügen des Benutzers');
|
showError('Fehler', result.error || 'Fehler beim Hinzufügen des Benutzers');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
|
|
@ -223,21 +225,19 @@ export const AdminUserMandatesPage: React.FC = () => {
|
||||||
setEditingUser(null);
|
setEditingUser(null);
|
||||||
fetchMandateUsers(selectedMandateId);
|
fetchMandateUsers(selectedMandateId);
|
||||||
} else {
|
} else {
|
||||||
alert(result.error || 'Fehler beim Aktualisieren der Rollen');
|
showError('Fehler', result.error || 'Fehler beim Aktualisieren der Rollen');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle remove user
|
// Handle remove user (confirmation handled by DeleteActionButton)
|
||||||
const handleRemoveUser = async (user: MandateUser) => {
|
const handleRemoveUser = async (user: MandateUser) => {
|
||||||
if (!selectedMandateId) return;
|
if (!selectedMandateId) return;
|
||||||
if (window.confirm(`Möchten Sie den Benutzer "${user.username}" wirklich aus diesem Mandanten entfernen?`)) {
|
const result = await removeUserFromMandate(selectedMandateId, user.userId);
|
||||||
const result = await removeUserFromMandate(selectedMandateId, user.userId);
|
if (!result.success) {
|
||||||
if (!result.success) {
|
showError('Fehler', result.error || 'Fehler beim Entfernen des Benutzers');
|
||||||
alert(result.error || 'Fehler beim Entfernen des Benutzers');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,13 +96,11 @@ export const AdminUsersPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteUser = async (user: User) => {
|
const handleDeleteUser = async (user: User) => {
|
||||||
if (window.confirm(`Möchten Sie den Benutzer "${user.username}" wirklich löschen?`)) {
|
const success = await deleteUser(user.id);
|
||||||
const success = await deleteUser(user.id);
|
if (success) {
|
||||||
if (success) {
|
refetch(); // Refresh the list
|
||||||
refetch(); // Refresh the list
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,22 +93,20 @@ export const ConnectionsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDelete = async (connection: Connection) => {
|
const handleDelete = async (connection: Connection) => {
|
||||||
if (window.confirm(`Möchten Sie die Verbindung "${connection.name || connection.email || connection.id}" wirklich löschen?`)) {
|
setDeletingConnections(prev => new Set(prev).add(connection.id));
|
||||||
setDeletingConnections(prev => new Set(prev).add(connection.id));
|
try {
|
||||||
try {
|
await deleteConnection(connection.id);
|
||||||
await deleteConnection(connection.id);
|
refetch();
|
||||||
refetch();
|
} catch (error) {
|
||||||
} catch (error) {
|
console.error('Error deleting connection:', error);
|
||||||
console.error('Error deleting connection:', error);
|
} finally {
|
||||||
} finally {
|
setDeletingConnections(prev => {
|
||||||
setDeletingConnections(prev => {
|
const newSet = new Set(prev);
|
||||||
const newSet = new Set(prev);
|
newSet.delete(connection.id);
|
||||||
newSet.delete(connection.id);
|
return newSet;
|
||||||
return newSet;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,25 +100,20 @@ export const FilesPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete single file
|
// Handle delete single file (confirmation handled by DeleteActionButton)
|
||||||
const handleDelete = async (file: UserFile) => {
|
const handleDelete = async (file: UserFile) => {
|
||||||
if (window.confirm(`Möchten Sie die Datei "${file.fileName}" wirklich löschen?`)) {
|
const success = await handleFileDelete(file.id);
|
||||||
const success = await handleFileDelete(file.id);
|
if (success) {
|
||||||
if (success) {
|
refetch();
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete multiple files
|
// Handle delete multiple files (confirmation handled by FormGenerator)
|
||||||
const handleDeleteMultiple = async (filesToDelete: UserFile[]) => {
|
const handleDeleteMultiple = async (filesToDelete: UserFile[]) => {
|
||||||
const count = filesToDelete.length;
|
const ids = filesToDelete.map(f => f.id);
|
||||||
if (window.confirm(`Möchten Sie ${count} Datei(en) wirklich löschen?`)) {
|
const success = await handleFileDeleteMultiple(ids);
|
||||||
const ids = filesToDelete.map(f => f.id);
|
if (success) {
|
||||||
const success = await handleFileDeleteMultiple(ids);
|
refetch();
|
||||||
if (success) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,13 +109,11 @@ export const PromptsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete single prompt
|
// Handle delete single prompt (confirmation handled by DeleteActionButton)
|
||||||
const handleDelete = async (prompt: Prompt) => {
|
const handleDelete = async (prompt: Prompt) => {
|
||||||
if (window.confirm(`Möchten Sie den Prompt "${prompt.name}" wirklich löschen?`)) {
|
const success = await handlePromptDelete(prompt.id);
|
||||||
const success = await handlePromptDelete(prompt.id);
|
if (success) {
|
||||||
if (success) {
|
refetch();
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,12 @@ import { useInstanceId } from '../../../hooks/useCurrentInstance';
|
||||||
import { FormGeneratorTable } from '../../../components/FormGenerator/FormGeneratorTable';
|
import { FormGeneratorTable } from '../../../components/FormGenerator/FormGeneratorTable';
|
||||||
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
|
import { FormGeneratorForm } from '../../../components/FormGenerator/FormGeneratorForm';
|
||||||
import { FaSync, FaFileAlt, FaDownload } from 'react-icons/fa';
|
import { FaSync, FaFileAlt, FaDownload } from 'react-icons/fa';
|
||||||
|
import { useToast } from '../../../contexts/ToastContext';
|
||||||
import api from '../../../api';
|
import api from '../../../api';
|
||||||
import styles from '../../admin/Admin.module.css';
|
import styles from '../../admin/Admin.module.css';
|
||||||
|
|
||||||
export const TrusteeDocumentsView: React.FC = () => {
|
export const TrusteeDocumentsView: React.FC = () => {
|
||||||
|
const { showError } = useToast();
|
||||||
const instanceId = useInstanceId();
|
const instanceId = useInstanceId();
|
||||||
|
|
||||||
// Entity hook
|
// Entity hook
|
||||||
|
|
@ -103,14 +105,12 @@ export const TrusteeDocumentsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteDoc = async (doc: TrusteeDocument) => {
|
const handleDeleteDoc = async (doc: TrusteeDocument) => {
|
||||||
if (window.confirm(`Dokument "${doc.documentName}" wirklich löschen?`)) {
|
removeOptimistically(doc.id);
|
||||||
removeOptimistically(doc.id);
|
const success = await handleDelete(doc.id);
|
||||||
const success = await handleDelete(doc.id);
|
if (!success) {
|
||||||
if (!success) {
|
refetch(); // Revert on error
|
||||||
refetch(); // Revert on error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -136,7 +136,7 @@ export const TrusteeDocumentsView: React.FC = () => {
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Download error:', err);
|
console.error('Download error:', err);
|
||||||
alert('Fehler beim Herunterladen des Dokuments.');
|
showError('Fehler', 'Fehler beim Herunterladen des Dokuments.');
|
||||||
} finally {
|
} finally {
|
||||||
setDownloadingId(null);
|
setDownloadingId(null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,14 +117,12 @@ export const TrusteePositionDocumentsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDeleteLink = async (link: TrusteePositionDocument) => {
|
const handleDeleteLink = async (link: TrusteePositionDocument) => {
|
||||||
if (window.confirm('Verknüpfung wirklich entfernen?')) {
|
removeOptimistically(link.id);
|
||||||
removeOptimistically(link.id);
|
const success = await handleDelete(link.id);
|
||||||
const success = await handleDelete(link.id);
|
if (!success) {
|
||||||
if (!success) {
|
refetch(); // Revert on error
|
||||||
refetch(); // Revert on error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,19 +50,24 @@ export const TrusteePositionsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
}, [instanceId]);
|
}, [instanceId]);
|
||||||
|
|
||||||
|
// Hidden columns (not shown in table view, but available in form)
|
||||||
|
const hiddenColumns = ['desc'];
|
||||||
|
|
||||||
// Generate columns from attributes
|
// Generate columns from attributes
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return (attributes || []).map(attr => ({
|
return (attributes || [])
|
||||||
key: attr.name,
|
.filter(attr => !hiddenColumns.includes(attr.name))
|
||||||
label: attr.label || attr.name,
|
.map(attr => ({
|
||||||
type: attr.type as any,
|
key: attr.name,
|
||||||
sortable: attr.sortable !== false,
|
label: attr.label || attr.name,
|
||||||
filterable: attr.filterable !== false,
|
type: attr.type as any,
|
||||||
searchable: attr.searchable !== false,
|
sortable: attr.sortable !== false,
|
||||||
width: attr.width || 150,
|
filterable: attr.filterable !== false,
|
||||||
minWidth: attr.minWidth || 100,
|
searchable: attr.searchable !== false,
|
||||||
maxWidth: attr.maxWidth || 400,
|
width: attr.width || 150,
|
||||||
}));
|
minWidth: attr.minWidth || 100,
|
||||||
|
maxWidth: attr.maxWidth || 400,
|
||||||
|
}));
|
||||||
}, [attributes]);
|
}, [attributes]);
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
|
|
@ -108,14 +113,12 @@ export const TrusteePositionsView: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete
|
// Handle delete (confirmation handled by DeleteActionButton)
|
||||||
const handleDeletePos = async (pos: TrusteePosition) => {
|
const handleDeletePos = async (pos: TrusteePosition) => {
|
||||||
if (window.confirm(`Position "${pos.desc || pos.id}" wirklich löschen?`)) {
|
removeOptimistically(pos.id);
|
||||||
removeOptimistically(pos.id);
|
const success = await handleDelete(pos.id);
|
||||||
const success = await handleDelete(pos.id);
|
if (!success) {
|
||||||
if (!success) {
|
refetch(); // Revert on error
|
||||||
refetch(); // Revert on error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ export const AutomationsPage: React.FC = () => {
|
||||||
|
|
||||||
// Generate columns from attributes - exclude ID fields from display
|
// Generate columns from attributes - exclude ID fields from display
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
const hiddenColumns = ['id', 'mandateId', '_createdBy', '_createdAt', '_modifiedAt', 'template', 'executionLogs'];
|
const hiddenColumns = ['id', 'mandateId', '_createdBy', '_createdAt', '_modifiedAt', 'template', 'executionLogs', 'placeholders'];
|
||||||
|
|
||||||
return (attributes || [])
|
return (attributes || [])
|
||||||
.filter(attr => !hiddenColumns.includes(attr.name))
|
.filter(attr => !hiddenColumns.includes(attr.name))
|
||||||
|
|
@ -188,14 +188,12 @@ export const AutomationsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete single automation
|
// Handle delete single automation (confirmation handled by DeleteActionButton)
|
||||||
const handleDelete = async (automation: Automation) => {
|
const handleDelete = async (automation: Automation) => {
|
||||||
if (window.confirm(`Möchten Sie die Automatisierung "${automation.label}" wirklich löschen?`)) {
|
const success = await handleAutomationDelete(automation.id);
|
||||||
const success = await handleAutomationDelete(automation.id);
|
if (success) {
|
||||||
if (success) {
|
showSuccess('Automatisierung gelöscht');
|
||||||
showSuccess('Automatisierung gelöscht');
|
await refetch();
|
||||||
await refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -274,10 +272,16 @@ export const AutomationsPage: React.FC = () => {
|
||||||
const logs: WorkflowLog[] = response?.items || response || [];
|
const logs: WorkflowLog[] = response?.items || response || [];
|
||||||
|
|
||||||
if (logs.length > 0) {
|
if (logs.length > 0) {
|
||||||
setExecutionModal(prev => ({
|
setExecutionModal(prev => {
|
||||||
...prev,
|
// Deduplicate logs by ID
|
||||||
logs: [...prev.logs, ...logs],
|
const existingIds = new Set(prev.logs.map(l => l.id));
|
||||||
}));
|
const newLogs = logs.filter(l => !existingIds.has(l.id));
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
logs: [...prev.logs, ...newLogs],
|
||||||
|
};
|
||||||
|
});
|
||||||
lastLogIdRef.current = logs[logs.length - 1].id;
|
lastLogIdRef.current = logs[logs.length - 1].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,7 +381,7 @@ export const AutomationsPage: React.FC = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await request({
|
await request({
|
||||||
url: `/api/workflows/${executionModal.workflowId}/stop`,
|
url: `/api/chat/playground/${executionModal.workflowId}/stop`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,11 @@
|
||||||
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { useDashboardInputForm } from '../../hooks/usePlayground';
|
import { useDashboardInputForm } from '../../hooks/usePlayground';
|
||||||
import { useUserWorkflows } from '../../hooks/useWorkflows';
|
|
||||||
import { useResizablePanels } from '../../hooks/useResizablePanels';
|
import { useResizablePanels } from '../../hooks/useResizablePanels';
|
||||||
import { usePrompts } from '../../hooks/usePrompts';
|
import { usePrompts } from '../../hooks/usePrompts';
|
||||||
import { FaComment, FaTasks, FaPaperPlane, FaStop, FaFile, FaPlus, FaMicrophone, FaSquare, FaFileAlt } from 'react-icons/fa';
|
import { FaComment, FaTasks, FaPaperPlane, FaStop, FaFile, FaPlus, FaMicrophone, FaSquare, FaFileAlt } from 'react-icons/fa';
|
||||||
import { useToast } from '../../contexts/ToastContext';
|
import { useToast } from '../../contexts/ToastContext';
|
||||||
import { useVoiceLanguage, VoiceLanguageSelect } from '../../components/UiComponents';
|
import { useVoiceLanguage, VoiceLanguageSelect, Messages } from '../../components/UiComponents';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import styles from './PlaygroundPage.module.css';
|
import styles from './PlaygroundPage.module.css';
|
||||||
|
|
||||||
|
|
@ -44,11 +43,16 @@ export const PlaygroundPage: React.FC = () => {
|
||||||
workflowItems,
|
workflowItems,
|
||||||
pendingFiles,
|
pendingFiles,
|
||||||
handleFileRemove,
|
handleFileRemove,
|
||||||
|
handleFileDelete,
|
||||||
|
handleFileView,
|
||||||
|
handleFileDownload,
|
||||||
latestStats,
|
latestStats,
|
||||||
playgroundUIPermission,
|
playgroundUIPermission,
|
||||||
|
deletingFiles,
|
||||||
|
previewingFiles,
|
||||||
|
downloadingFiles,
|
||||||
} = hookData;
|
} = hookData;
|
||||||
|
|
||||||
const { data: workflows } = useUserWorkflows();
|
|
||||||
const { prompts, refetch: refetchPrompts } = usePrompts();
|
const { prompts, refetch: refetchPrompts } = usePrompts();
|
||||||
const { showError, showSuccess } = useToast();
|
const { showError, showSuccess } = useToast();
|
||||||
|
|
||||||
|
|
@ -310,14 +314,7 @@ export const PlaygroundPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Format timestamp for messages
|
// Render messages using the Messages component with document support
|
||||||
const formatTime = (timestamp: number | undefined) => {
|
|
||||||
if (!timestamp) return '';
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
return date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render messages
|
|
||||||
const renderMessages = () => {
|
const renderMessages = () => {
|
||||||
if (!messages || messages.length === 0) {
|
if (!messages || messages.length === 0) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -332,43 +329,21 @@ export const PlaygroundPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.messagesContainer}>
|
<Messages
|
||||||
{messages.map((msg: any, index: number) => (
|
messages={messages}
|
||||||
<div
|
variant="chat"
|
||||||
key={msg.id || index}
|
showDocuments={true}
|
||||||
style={{
|
showMetadata={false}
|
||||||
padding: '0.75rem 1rem',
|
onFileDelete={handleFileDelete}
|
||||||
borderRadius: '8px',
|
onFileRemove={handleFileRemove}
|
||||||
background: msg.role === 'user'
|
onFileView={handleFileView}
|
||||||
? 'var(--bg-secondary)'
|
onFileDownload={handleFileDownload}
|
||||||
: 'var(--surface-color)',
|
deletingFiles={deletingFiles}
|
||||||
border: '1px solid var(--border-color)',
|
previewingFiles={previewingFiles}
|
||||||
marginLeft: msg.role === 'user' ? '2rem' : '0',
|
downloadingFiles={downloadingFiles}
|
||||||
marginRight: msg.role === 'assistant' ? '2rem' : '0',
|
workflowId={workflowId}
|
||||||
}}
|
emptyMessage="Keine Nachrichten"
|
||||||
>
|
/>
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
marginBottom: '0.5rem',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
color: 'var(--text-secondary)',
|
|
||||||
}}>
|
|
||||||
<span style={{ fontWeight: 500 }}>
|
|
||||||
{msg.role === 'user' ? 'Sie' : 'Assistent'}
|
|
||||||
</span>
|
|
||||||
<span>{formatTime(msg.publishedAt)}</span>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
color: 'var(--text-primary)',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
}}>
|
|
||||||
{msg.message || msg.content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -615,9 +590,9 @@ export const PlaygroundPage: React.FC = () => {
|
||||||
disabled={isRunning}
|
disabled={isRunning}
|
||||||
>
|
>
|
||||||
<option value="">Neuer Workflow</option>
|
<option value="">Neuer Workflow</option>
|
||||||
{(workflowItems || workflows)?.map((item: any) => (
|
{workflowItems?.map((item: any) => (
|
||||||
<option key={item.id} value={item.id}>
|
<option key={item.id} value={item.id}>
|
||||||
{item.label || item.name || item.id}
|
{item.label || item.id}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
|
||||||
|
|
@ -98,25 +98,20 @@ export const WorkflowsPage: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete single workflow
|
// Handle delete single workflow (confirmation handled by DeleteActionButton)
|
||||||
const handleDelete = async (workflow: Workflow) => {
|
const handleDelete = async (workflow: Workflow) => {
|
||||||
if (window.confirm(`Möchten Sie den Workflow "${workflow.name || workflow.id}" wirklich löschen?`)) {
|
const success = await handleWorkflowDelete(workflow.id);
|
||||||
const success = await handleWorkflowDelete(workflow.id);
|
if (success) {
|
||||||
if (success) {
|
refetch();
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete multiple workflows
|
// Handle delete multiple workflows (confirmation handled by FormGenerator)
|
||||||
const handleDeleteMultiple = async (workflowsToDelete: Workflow[]) => {
|
const handleDeleteMultiple = async (workflowsToDelete: Workflow[]) => {
|
||||||
const count = workflowsToDelete.length;
|
const ids = workflowsToDelete.map(w => w.id);
|
||||||
if (window.confirm(`Möchten Sie ${count} Workflow(s) wirklich löschen?`)) {
|
const success = await handleWorkflowDeleteMultiple(ids);
|
||||||
const ids = workflowsToDelete.map(w => w.id);
|
if (success) {
|
||||||
const success = await handleWorkflowDeleteMultiple(ids);
|
refetch();
|
||||||
if (success) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue