diff --git a/src/components/FlowEditor/editor/Automation2FlowEditor.module.css b/src/components/FlowEditor/editor/Automation2FlowEditor.module.css index b043a5a..06594b5 100644 --- a/src/components/FlowEditor/editor/Automation2FlowEditor.module.css +++ b/src/components/FlowEditor/editor/Automation2FlowEditor.module.css @@ -14,13 +14,28 @@ SIDEBAR - Node List ============================================================================= */ +.resizeDivider { + flex-shrink: 0; + width: 5px; + cursor: col-resize; + background: var(--border-color, #e0e0e0); + transition: background 0.15s; + position: relative; + z-index: 5; +} + +.resizeDivider:hover, +.resizeDivider:active { + background: var(--primary-color, #007bff); +} + .sidebar { flex-shrink: 0; width: 280px; display: flex; flex-direction: column; background: var(--bg-secondary, #f8f9fa); - border-left: 1px solid var(--border-color, #e0e0e0); + border-left: none; overflow: hidden; } @@ -108,6 +123,7 @@ cursor: grab; transition: background 0.15s; border: 1px solid transparent; + position: relative; } .nodeItem:hover { @@ -151,6 +167,29 @@ text-overflow: ellipsis; } +.nodeItem .nodeItemTooltip { + display: none; + position: absolute; + left: 0; + top: 100%; + z-index: 100; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 6px; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + color: var(--text-primary, #333); + white-space: normal; + word-break: break-word; + max-width: 280px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + pointer-events: none; +} + +.nodeItem:hover .nodeItemTooltip { + display: block; +} + /* Loading / Error */ .loading, .error { @@ -318,6 +357,19 @@ box-shadow: 0 0 0 2px var(--primary-color, #007bff); } +.canvasNodeHighlighted { + transition: border-color 0.3s ease, background-color 0.3s ease, box-shadow 0.3s ease; +} + +@keyframes pulseGlow { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +.canvasNodeHighlighted[style*="box-shadow"] { + animation: pulseGlow 1.5s ease-in-out infinite; +} + .canvasNodeContent { display: flex; align-items: flex-start; @@ -360,6 +412,39 @@ text-decoration: underline; } +.canvasNodeComment { + font-size: 0.7rem; + color: var(--text-tertiary, #999); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.2; +} + +.canvasNode .canvasNodeCommentTooltip { + display: none; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: calc(100% + 6px); + z-index: 100; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e0e0e0); + border-radius: 6px; + padding: 0.4rem 0.6rem; + font-size: 0.75rem; + color: var(--text-primary, #333); + white-space: normal; + word-break: break-word; + max-width: 260px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); + pointer-events: none; +} + +.canvasNode:hover .canvasNodeCommentTooltip { + display: block; +} + .canvasNodeInput { width: 100%; padding: 0.15rem 0.25rem; @@ -446,6 +531,13 @@ color: var(--text-tertiary, #999); } +.nodeConfigDescription { + margin: -0.5rem 0 0.75rem; + font-size: 0.75rem; + color: var(--text-secondary, #666); + line-height: 1.4; +} + .nodeConfigPanel label { display: block; font-size: 0.75rem; @@ -713,13 +805,13 @@ .scheduleModeBlock { position: relative; /* Ausgewählte Karte (orange) + Text auf „An“-Chips im erweiterten Bereich */ - --schedule-active: var(--schedule-mode-active, var(--color-secondary, #f25843)); - --schedule-active-border: var(--schedule-mode-active-border, var(--color-text, #3a3a3a)); + --schedule-active: var(--schedule-mode-active, var(--color-secondary)); + --schedule-active-border: var(--schedule-mode-active-border, var(--color-text)); display: flex; flex-direction: column; gap: 0; - border-radius: 25px; - border: 1px solid var(--color-text, #ddd); + border-radius: 8px; + border: 1px solid var(--color-border, #E2E8F0); background-color: var(--bg-primary, #fff); color: var(--color-text, #222); overflow: hidden; @@ -1451,3 +1543,33 @@ border-color: var(--primary-color, #007bff); color: var(--primary-color, #007bff); } + +/* Right panel tab bar (Nodes / Tracing) */ +.rightTabBar { + display: flex; + border-bottom: 1px solid var(--border-color, #e0e0e0); + flex-shrink: 0; + background: var(--bg-primary, #fff); +} + +.rightTab { + flex: 1; + padding: 8px; + border: none; + background: transparent; + cursor: pointer; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary, #666); + transition: background 0.15s, color 0.15s; +} + +.rightTab:hover { + background: var(--bg-hover, #f0f0f0); +} + +.rightTabActive { + background: var(--bg-secondary, #f5f5f5); + color: var(--text-primary, #333); + box-shadow: inset 0 -2px 0 var(--primary-color, #007bff); +} diff --git a/src/components/FlowEditor/editor/Automation2FlowEditor.tsx b/src/components/FlowEditor/editor/Automation2FlowEditor.tsx index ca1889e..e0fa29f 100644 --- a/src/components/FlowEditor/editor/Automation2FlowEditor.tsx +++ b/src/components/FlowEditor/editor/Automation2FlowEditor.tsx @@ -5,7 +5,7 @@ * Workflow configuration (gear): primary start kind + invocations; canvas start node stays in sync. */ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { FaSpinner } from 'react-icons/fa'; import { useApiRequest } from '../../../hooks/useApi'; import { @@ -49,6 +49,8 @@ import { usePrompt } from '../../../hooks/usePrompt'; import { EditorChatPanel } from './EditorChatPanel'; import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel'; import { RunTracingPanel } from './RunTracingPanel'; +import { UnifiedDataBar } from '../../../components/UnifiedDataBar'; +import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar'; import styles from './Automation2FlowEditor.module.css'; const LOG = '[Automation2]'; @@ -58,6 +60,7 @@ const DEFAULT_INVOCATIONS = (): WorkflowEntryPoint[] => interface Automation2FlowEditorProps { instanceId: string; + mandateId?: string; language?: string; /** When set, load this workflow on mount (e.g. from workflows list edit) */ initialWorkflowId?: string | null; @@ -65,16 +68,21 @@ interface Automation2FlowEditorProps { onRemovePendingFile?: (fileId: string) => void; dataSources?: EditorDataSource[]; featureDataSources?: EditorFeatureDataSource[]; + onFileSelect?: (fileId: string, fileName?: string) => void; + onSourcesChanged?: () => void; } export const Automation2FlowEditor: React.FC = ({ instanceId, + mandateId, language = 'de', initialWorkflowId, pendingFiles, onRemovePendingFile, dataSources, featureDataSources, + onFileSelect, + onSourcesChanged, }) => { const { request } = useApiRequest(); const { prompt: promptInput, PromptDialog } = usePrompt(); @@ -96,12 +104,64 @@ export const Automation2FlowEditor: React.FC = ({ const [saving, setSaving] = useState(false); const [invocations, setInvocations] = useState(DEFAULT_INVOCATIONS); const [workflowSettingsOpen, setWorkflowSettingsOpen] = useState(false); - const [chatPanelOpen, setChatPanelOpen] = useState(false); + const [leftPanelOpen, setLeftPanelOpen] = useState(true); const [tracingRunId, setTracingRunId] = useState(null); + const [tracingNodeStatuses, setTracingNodeStatuses] = useState>({}); + const [rightTab, setRightTab] = useState<'nodes' | 'tracing'>('nodes'); + const [udbTab, setUdbTab] = useState('chats'); + + const udbContext: UdbContext = useMemo(() => ({ + instanceId, + mandateId: mandateId || '', + featureInstanceId: instanceId, + }), [instanceId, mandateId]); const [versions, setVersions] = useState([]); const [currentVersionId, setCurrentVersionId] = useState(null); const [versionLoading, setVersionLoading] = useState(false); + const [leftPanelWidth, setLeftPanelWidth] = useState(() => { + try { const v = parseInt(localStorage.getItem('flowEditor.leftPanelWidth') ?? ''); return v >= 240 && v <= 600 ? v : 340; } catch { return 340; } + }); + const [sidebarWidth, setSidebarWidth] = useState(() => { + try { const v = parseInt(localStorage.getItem('flowEditor.sidebarWidth') ?? ''); return v >= 200 && v <= 500 ? v : 280; } catch { return 280; } + }); + const resizingRef = useRef<{ target: 'left' | 'right'; startX: number; startW: number } | null>(null); + + useEffect(() => { + const _onMouseMove = (e: MouseEvent) => { + if (!resizingRef.current) return; + const { target, startX, startW } = resizingRef.current; + const delta = e.clientX - startX; + if (target === 'left') { + setLeftPanelWidth(Math.max(240, Math.min(600, startW + delta))); + } else { + setSidebarWidth(Math.max(200, Math.min(500, startW - delta))); + } + }; + const _onMouseUp = () => { + if (!resizingRef.current) return; + const { target } = resizingRef.current; + resizingRef.current = null; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + if (target === 'left') { + setLeftPanelWidth((w) => { try { localStorage.setItem('flowEditor.leftPanelWidth', String(w)); } catch {} return w; }); + } else { + setSidebarWidth((w) => { try { localStorage.setItem('flowEditor.sidebarWidth', String(w)); } catch {} return w; }); + } + }; + document.addEventListener('mousemove', _onMouseMove); + document.addEventListener('mouseup', _onMouseUp); + return () => { document.removeEventListener('mousemove', _onMouseMove); document.removeEventListener('mouseup', _onMouseUp); }; + }, []); + + const _startResize = useCallback((target: 'left' | 'right', e: React.MouseEvent) => { + e.preventDefault(); + resizingRef.current = { target, startX: e.clientX, startW: target === 'left' ? leftPanelWidth : sidebarWidth }; + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, [leftPanelWidth, sidebarWidth]); + const sidebarExcludedCategories = useMemo(() => new Set(['trigger']), []); const nodeOutputsPreview = useMemo( @@ -149,7 +209,10 @@ export const Automation2FlowEditor: React.FC = ({ ...(ep ? { entryPointId: ep } : {}), }); setExecuteResult(result); - if (result.runId) setTracingRunId(result.runId); + if (result.runId) { + setTracingRunId(result.runId); + setRightTab('tracing'); + } } catch (err: unknown) { setExecuteResult({ success: false, error: err instanceof Error ? err.message : String(err) }); } finally { @@ -317,11 +380,13 @@ export const Automation2FlowEditor: React.FC = ({ loadWorkflows(); }, [loadWorkflows]); + const lastAppliedInitialRef = useRef(undefined); useEffect(() => { - if (initialWorkflowId && workflows.length > 0 && !currentWorkflowId && nodeTypes.length > 0) { - handleWorkflowSelect(initialWorkflowId); - } - }, [initialWorkflowId, workflows, currentWorkflowId, handleWorkflowSelect, nodeTypes.length]); + if (!initialWorkflowId || workflows.length === 0 || nodeTypes.length === 0) return; + if (lastAppliedInitialRef.current === initialWorkflowId) return; + lastAppliedInitialRef.current = initialWorkflowId; + handleWorkflowSelect(initialWorkflowId); + }, [initialWorkflowId, workflows, handleWorkflowSelect, nodeTypes.length]); useEffect(() => { if (loading || nodeTypes.length === 0) return; @@ -501,10 +566,21 @@ export const Automation2FlowEditor: React.FC = ({ [request, instanceId, handleFromApiGraph] ); + const handleWorkflowRename = useCallback(async (workflowId: string, newName: string) => { + try { + await updateWorkflow(request, instanceId, workflowId, { label: newName }); + setWorkflows((prev) => prev.map((w) => w.id === workflowId ? { ...w, label: newName } : w)); + } catch (e: unknown) { + console.error(`${LOG} rename failed`, e); + } + }, [request, instanceId]); + + const _sidebarStyle = useMemo(() => ({ width: sidebarWidth }), [sidebarWidth]); + const renderSidebar = () => { if (loading) { return ( -
+

Nodes

@@ -517,7 +593,7 @@ export const Automation2FlowEditor: React.FC = ({ } if (error) { return ( -
+

Nodes

@@ -540,6 +616,7 @@ export const Automation2FlowEditor: React.FC = ({ expandedCategories={expandedCategories} onToggleCategory={toggleCategory} excludedCategories={sidebarExcludedCategories} + style={_sidebarStyle} /> ); }; @@ -552,46 +629,45 @@ export const Automation2FlowEditor: React.FC = ({ return (
- {/* Chat/Tracing panel - left side */} - {(chatPanelOpen || tracingRunId) && ( -
-
- - - + {/* Left panel: Workspace (Chats / Dateien / Quellen) */} + {leftPanelOpen && (<> +
+
+ {(['chats', 'files', 'sources'] as const).map((tab) => ( + + ))}
- {chatPanelOpen && currentWorkflowId ? ( + {udbTab === 'chats' ? ( handleLoad(currentWorkflowId)} + onGraphUpdated={() => { if (currentWorkflowId) handleLoad(currentWorkflowId); }} pendingFiles={pendingFiles} onRemovePendingFile={onRemovePendingFile} dataSources={dataSources} featureDataSources={featureDataSources} /> - ) : tracingRunId ? ( - - ) : null} + ) : ( + + )}
- )} +
_startResize('left', e)} /> + )} {/* Canvas area - center */}
@@ -603,7 +679,7 @@ export const Automation2FlowEditor: React.FC = ({ onSave={handleSave} onExecute={handleExecute} onWorkflowSettings={() => setWorkflowSettingsOpen(true)} - onToggleChat={() => setChatPanelOpen((prev) => !prev)} + onToggleChat={() => setLeftPanelOpen((prev) => !prev)} saving={saving} executing={executing} hasNodes={canvasNodes.length > 0} @@ -619,6 +695,7 @@ export const Automation2FlowEditor: React.FC = ({ onSaveAsTemplate={handleSaveAsTemplate} templateSaving={templateSaving} onNewFromTemplate={() => setTemplatePickerOpen(true)} + onWorkflowRename={handleWorkflowRename} />
@@ -632,6 +709,7 @@ export const Automation2FlowEditor: React.FC = ({ getLabel={(node) => node.title ?? node.label ?? node.type} getCategoryIcon={getCategoryIcon} onSelectionChange={setSelectedNode} + highlightedNodeIds={tracingRunId ? tracingNodeStatuses : undefined} />
{configurableSelected && selectedNode && ( @@ -658,8 +736,40 @@ export const Automation2FlowEditor: React.FC = ({
- {/* Node sidebar - right side */} - {renderSidebar()} + {/* Right panel: Nodes + Tracing tabs */} +
_startResize('right', e)} /> +
+
+ + +
+
+ {rightTab === 'nodes' ? ( + renderSidebar() + ) : ( + { + const node = canvasNodes.find((n) => n.id === nodeId); + if (node) setSelectedNode(node); + }} + onActiveStepsChange={setTracingNodeStatuses} + /> + )} +
+
+ void; templateSaving?: boolean; onNewFromTemplate?: () => void; + onWorkflowRename?: (workflowId: string, newName: string) => void; } const STATUS_BADGE: Record = { @@ -63,6 +64,7 @@ export const CanvasHeader: React.FC = ({ onSaveAsTemplate, templateSaving, onNewFromTemplate, + onWorkflowRename, }) => { const currentVersion = versions?.find((v) => v.id === currentVersionId); const currentStatus = currentVersion?.status || 'draft'; @@ -74,6 +76,34 @@ export const CanvasHeader: React.FC = ({ const [templateMenuOpen, setTemplateMenuOpen] = useState(false); const templateMenuRef = useRef(null); + const [editingName, setEditingName] = useState(false); + const [nameValue, setNameValue] = useState(''); + const nameInputRef = useRef(null); + + const currentWorkflow = workflows.find((w) => w.id === currentWorkflowId); + + const _startNameEdit = useCallback(() => { + if (!currentWorkflowId || !onWorkflowRename) return; + setNameValue(currentWorkflow?.label || ''); + setEditingName(true); + }, [currentWorkflowId, currentWorkflow?.label, onWorkflowRename]); + + const _commitNameEdit = useCallback(() => { + setEditingName(false); + const trimmed = nameValue.trim(); + if (!trimmed || !currentWorkflowId || !onWorkflowRename) return; + if (trimmed !== currentWorkflow?.label) { + onWorkflowRename(currentWorkflowId, trimmed); + } + }, [nameValue, currentWorkflowId, currentWorkflow?.label, onWorkflowRename]); + + useEffect(() => { + if (editingName && nameInputRef.current) { + nameInputRef.current.focus(); + nameInputRef.current.select(); + } + }, [editingName]); + useEffect(() => { const _handleClickOutside = (e: MouseEvent) => { if (newMenuRef.current && !newMenuRef.current.contains(e.target as Node)) setNewMenuOpen(false); @@ -87,10 +117,33 @@ export const CanvasHeader: React.FC = ({ return (
-
-

- Workflow-Editor -

+
+ {/* Workflow name: inline editable */} + {currentWorkflowId && currentWorkflow ? ( + editingName ? ( + setNameValue(e.target.value)} + onBlur={_commitNameEdit} + onKeyDown={(e) => { if (e.key === 'Enter') _commitNameEdit(); if (e.key === 'Escape') setEditingName(false); }} + style={{ padding: '0.25rem 0.4rem', fontSize: '0.95rem', fontWeight: 600, border: '1px solid var(--primary-color, #007bff)', borderRadius: 4, outline: 'none', minWidth: 140, maxWidth: 300 }} + /> + ) : ( +

+ {currentWorkflow.label} +

+ ) + ) : ( +

+ Neuer Workflow +

+ )} {onWorkflowSettings && ( {onToggleChat && ( - )}
diff --git a/src/components/FlowEditor/editor/FlowCanvas.tsx b/src/components/FlowEditor/editor/FlowCanvas.tsx index 660b046..f28bf3f 100644 --- a/src/components/FlowEditor/editor/FlowCanvas.tsx +++ b/src/components/FlowEditor/editor/FlowCanvas.tsx @@ -44,8 +44,16 @@ interface FlowCanvasProps { getLabel: (node: CanvasNode) => string; getCategoryIcon: (category: string) => React.ReactNode; onSelectionChange?: (node: CanvasNode | null) => void; + highlightedNodeIds?: Record; } +const HIGHLIGHT_COLORS: Record = { + running: '#f0ad4e', + completed: '#28a745', + failed: '#dc3545', + skipped: '#6c757d', +}; + export const FlowCanvas: React.FC = ({ nodes, connections, @@ -56,6 +64,7 @@ export const FlowCanvas: React.FC = ({ getLabel, getCategoryIcon, onSelectionChange, + highlightedNodeIds, }) => { const containerRef = useRef(null); const [selectedNodeIds, setSelectedNodeIds] = useState>(new Set()); @@ -621,18 +630,21 @@ export const FlowCanvas: React.FC = ({ const isSelected = selectedNodeIds.has(node.id); const isEditingTitle = editingNodeId === node.id && editingField === 'title'; const displayTitle = node.title ?? node.label ?? getLabel(node); + const hlStatus = highlightedNodeIds?.[node.id]; + const hlColor = hlStatus ? HIGHLIGHT_COLORS[hlStatus] : null; return (
e.stopPropagation()} onMouseDown={(e) => { @@ -743,7 +755,13 @@ export const FlowCanvas: React.FC = ({ {displayTitle} )} + {node.comment && ( + {node.comment} + )}
+ {node.comment && ( +
{node.comment}
+ )}
); diff --git a/src/components/FlowEditor/editor/NodeConfigPanel.tsx b/src/components/FlowEditor/editor/NodeConfigPanel.tsx index e834eef..5401bc3 100644 --- a/src/components/FlowEditor/editor/NodeConfigPanel.tsx +++ b/src/components/FlowEditor/editor/NodeConfigPanel.tsx @@ -107,6 +107,11 @@ export const NodeConfigPanel: React.FC = ({
)}

{getLabel(nodeType?.label, language) || node.type}

+ {nodeType?.description && ( +

+ {getLabel(nodeType.description, language)} +

+ )} = ({ language, getLabel, getCategoryIcon: getIcon = getCategoryIcon, -}) => ( -
{ - e.dataTransfer.setData('application/json', JSON.stringify({ type: node.id })); - e.dataTransfer.effectAllowed = 'copy'; - }} - > +}) => { + const desc = getLabel(node.description, language); + return (
{ + e.dataTransfer.setData('application/json', JSON.stringify({ type: node.id })); + e.dataTransfer.effectAllowed = 'copy'; }} > - {getIcon(node.category)} +
+ {getIcon(node.category)} +
+
+ {getLabel(node.label, language)} + {desc} +
+ {desc &&
{desc}
}
-
- {getLabel(node.label, language)} - {getLabel(node.description, language)} -
-
-); + ); +}; diff --git a/src/components/FlowEditor/editor/NodeSidebar.tsx b/src/components/FlowEditor/editor/NodeSidebar.tsx index 88570ca..8dce433 100644 --- a/src/components/FlowEditor/editor/NodeSidebar.tsx +++ b/src/components/FlowEditor/editor/NodeSidebar.tsx @@ -21,6 +21,7 @@ interface NodeSidebarProps { onToggleCategory: (id: string) => void; /** Hide palette categories (e.g. trigger — start node comes from workflow config only) */ excludedCategories?: Set; + style?: React.CSSProperties; } export const NodeSidebar: React.FC = ({ @@ -32,6 +33,7 @@ export const NodeSidebar: React.FC = ({ expandedCategories, onToggleCategory, excludedCategories, + style, }) => { const filteredNodeTypes = useMemo(() => { const visible = nodeTypes.filter( @@ -78,7 +80,7 @@ export const NodeSidebar: React.FC = ({ getLabel(t, lang ?? language); return ( -
+

Nodes

void; + onActiveStepsChange?: (nodeStatuses: Record) => void; } const STATUS_COLORS: Record = { @@ -30,13 +32,60 @@ const STATUS_ICONS: Record = { skipped: '—', }; +function _formatTimestamp(ts: number | string | null | undefined): string { + if (!ts) return ''; + const d = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts); + if (isNaN(d.getTime())) return ''; + return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); +} + +function _truncateJson(obj: unknown, maxLen = 300): string { + if (!obj || (typeof obj === 'object' && Object.keys(obj as object).length === 0)) return ''; + try { + const s = JSON.stringify(obj, null, 2); + return s.length > maxLen ? s.slice(0, maxLen) + '\n...' : s; + } catch { + return String(obj); + } +} + +const CollapsibleSection: React.FC<{ label: string; content: string }> = ({ label, content }) => { + const [open, setOpen] = useState(false); + if (!content) return null; + return ( +
+ + {open && ( +
+          {content}
+        
+ )} +
+ ); +}; + export const RunTracingPanel: React.FC = ({ instanceId, runId, onNodeSelect, + onActiveStepsChange, }) => { const [steps, setSteps] = useState([]); const [loading, setLoading] = useState(false); + const [sseConnected, setSseConnected] = useState(false); + const eventSourceRef = useRef(null); const { request } = useApiRequest(); const loadSteps = useCallback(async () => { @@ -55,11 +104,65 @@ export const RunTracingPanel: React.FC = ({ } }, [runId, instanceId, request]); + // SSE live-push connection useEffect(() => { + if (!runId || !instanceId) return; loadSteps(); + + const url = `/api/workflows/${instanceId}/runs/${runId}/stream`; + const es = new EventSource(url); + eventSourceRef.current = es; + + es.onopen = () => setSseConnected(true); + es.onmessage = (event) => { + try { + const payload = JSON.parse(event.data); + if (payload.type === 'keepalive') return; + if (payload.type === 'run_complete' || payload.type === 'run_failed') { + loadSteps(); + es.close(); + setSseConnected(false); + return; + } + if (payload.status === 'running') { + setSteps((prev) => { + const exists = prev.some((s) => s.id === payload.id); + if (exists) return prev.map((s) => s.id === payload.id ? { ...s, ...payload } : s); + return [...prev, payload as AutoStepLog]; + }); + } else { + setSteps((prev) => prev.map((s) => s.id === payload.id ? { ...s, ...payload } : s)); + } + } catch { /* ignore parse errors */ } + }; + es.onerror = () => { + setSseConnected(false); + es.close(); + }; + + return () => { + es.close(); + eventSourceRef.current = null; + setSseConnected(false); + }; + }, [runId, instanceId]); // eslint-disable-line react-hooks/exhaustive-deps + + // Fallback polling when SSE is not connected + useEffect(() => { + if (sseConnected || !runId || !instanceId) return; const interval = setInterval(loadSteps, 3000); return () => clearInterval(interval); - }, [loadSteps]); + }, [sseConnected, runId, instanceId, loadSteps]); + + // Emit active node statuses for canvas highlighting + useEffect(() => { + if (!onActiveStepsChange) return; + const nodeStatuses: Record = {}; + for (const step of steps) { + nodeStatuses[step.nodeId] = step.status; + } + onActiveStepsChange(nodeStatuses); + }, [steps, onActiveStepsChange]); if (!runId) { return ( @@ -77,40 +180,73 @@ export const RunTracingPanel: React.FC = ({ {steps.length === 0 && !loading && (
No steps recorded yet.
)} - {steps.map((step) => ( -
onNodeSelect?.(step.nodeId)} - style={{ - padding: '8px 12px', - marginBottom: '6px', - borderRadius: '6px', - border: `1px solid ${STATUS_COLORS[step.status] || '#ddd'}`, - background: 'var(--bg-primary, #fff)', - cursor: 'pointer', - fontSize: '13px', - }} - > -
- - - {STATUS_ICONS[step.status] || '?'} + {steps.map((step: any) => { + const startStr = _formatTimestamp(step.startedAt); + const endStr = _formatTimestamp(step.completedAt); + const inputStr = _truncateJson(step.inputSnapshot); + const outputStr = _truncateJson(step.output); + const isLoop = step.inputSnapshot?._loopIndex != null; + + return ( +
onNodeSelect?.(step.nodeId)} + style={{ + padding: '8px 12px', + marginBottom: '6px', + borderRadius: '6px', + border: `1px solid ${STATUS_COLORS[step.status] || '#ddd'}`, + background: 'var(--bg-primary, #fff)', + cursor: 'pointer', + fontSize: '13px', + marginLeft: isLoop ? '16px' : '0', + }} + > +
+ + + {STATUS_ICONS[step.status] || '?'} + + {step.nodeType} + ({step.nodeId}) + {isLoop && ( + + [iter {step.inputSnapshot._loopIndex}] + + )} - {step.nodeType} - ({step.nodeId}) - - {step.durationMs != null && ( - {step.durationMs}ms + + {step.retryCount > 0 && ( + + {step.retryCount}x retry + + )} + {step.durationMs != null && ( + {step.durationMs}ms + )} + +
+ + {(startStr || endStr) && ( +
+ {startStr && {startStr}} + {startStr && endStr && } + {endStr && {endStr}} +
)} + + {step.error && ( +
{step.error}
+ )} + {step.tokensUsed > 0 && ( +
{step.tokensUsed} tokens
+ )} + + +
- {step.error && ( -
{step.error}
- )} - {step.tokensUsed > 0 && ( -
{step.tokensUsed} tokens
- )} -
- ))} + ); + })}
); }; diff --git a/src/components/FormGenerator/ActionButtons/ActionButton.module.css b/src/components/FormGenerator/ActionButtons/ActionButton.module.css index 58d1e7e..6bd5717 100644 --- a/src/components/FormGenerator/ActionButtons/ActionButton.module.css +++ b/src/components/FormGenerator/ActionButtons/ActionButton.module.css @@ -3,54 +3,57 @@ display: flex; align-items: center; justify-content: center; - padding: 6px; + padding: 5px; border: none; - border-radius: 50%; + border-radius: 6px; font-size: 12px; font-family: var(--font-family); cursor: pointer; - transition: all 0.2s ease; + transition: all 0.15s ease; white-space: nowrap; position: relative; - min-width: 28px; - min-height: 28px; - background: var(--color-secondary); - color: var(--color-bg); + min-width: 26px; + min-height: 26px; + background: var(--color-gray, #718096); + color: #fff; } .actionButton:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover, #4A5568); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .actionButton:disabled { - opacity: 0.6; + opacity: 0.4; cursor: not-allowed; transform: none !important; + box-shadow: none !important; } -/* Disabled state class */ .actionButton.disabled { opacity: 0.4; cursor: not-allowed; transform: none !important; - background: #ccc !important; - color: #666 !important; + background: var(--color-gray-disabled, #CBD5E0) !important; + color: var(--color-text-secondary, #718096) !important; } .actionButton.disabled:hover { - background: #ccc !important; + background: var(--color-gray-disabled, #CBD5E0) !important; transform: none !important; + box-shadow: none !important; } .actionButton:focus { outline: none; - box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb), 0.3); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.25); } .actionIcon { - font-size: 16px; - height: 16px; - width: 16px; + font-size: 14px; + height: 14px; + width: 14px; display: flex; align-items: center; justify-content: center; @@ -58,7 +61,7 @@ /* Loading State */ .actionButton.loading { - opacity: 0.7; + opacity: 0.65; cursor: not-allowed; } @@ -66,7 +69,6 @@ animation: spin 1s linear infinite; } -/* Delete button loading state - no animation for user-friendly experience */ .actionButton.delete.loading .actionIcon { animation: none; } @@ -82,8 +84,8 @@ gap: 2px; justify-content: center; align-items: center; - background: var(--color-secondary); - border-radius: 25px; + background: var(--color-gray, #718096); + border-radius: 6px; padding: 2px; } @@ -96,8 +98,8 @@ } .confirmButton:hover { - background: rgba(255, 255, 255, 0.2) !important; - transform: scale(1.05); + background: rgba(255, 255, 255, 0.15) !important; + transform: none; } .cancelButton { @@ -109,121 +111,121 @@ } .cancelButton:hover { - background: rgba(255, 255, 255, 0.2) !important; - transform: scale(1.05); + background: rgba(255, 255, 255, 0.15) !important; + transform: none; } /* Action Button Container */ .actionButtons { display: flex; - gap: 2px; + gap: 4px; justify-content: center; align-items: center; width: 100%; margin: 0 auto; } -/* Button Variants - All use same styling as delete button */ +/* Button Variants */ .actionButton.edit { - background: var(--color-secondary); + background: var(--color-secondary, #4A6FA5); color: white; } .actionButton.edit:hover { - background: var(--color-secondary-hover); + background: var(--color-secondary-hover, #3D5D8A); } .actionButton.delete { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.delete:hover { - background: var(--color-secondary-hover); + background: var(--color-red, #C53030); } .actionButton.download { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.download:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover, #4A5568); } .actionButton.view { - background: var(--color-secondary); + background: var(--color-secondary, #4A6FA5); color: white; } .actionButton.view:hover { - background: var(--color-secondary-hover); + background: var(--color-secondary-hover, #3D5D8A); } .actionButton.copy { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.copy:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover, #4A5568); } .actionButton.connect { - background: var(--color-secondary); + background: var(--color-secondary, #4A6FA5); color: white; } .actionButton.connect:hover { - background: var(--color-secondary-hover); + background: var(--color-secondary-hover, #3D5D8A); } .actionButton.refresh { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.refresh:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover, #4A5568); } .actionButton.remove { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.remove:hover { - background: var(--color-secondary-hover); + background: var(--color-red, #C53030); } /* Generic Custom Action Button */ .actionButton.custom { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; } .actionButton.custom:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover, #4A5568); } /* Success State */ .actionButton.success { - background: #28a745 !important; + background: var(--color-success, #38A169) !important; color: white !important; } .actionButton.success:hover { - background: #218838 !important; + background: var(--color-success-hover, #2F855A) !important; } /* Error State */ .actionButton.error { - background: #dc3545 !important; + background: var(--color-red, #C53030) !important; color: white !important; } .actionButton.error:hover { - background: #c82333 !important; + background: #9B2C2C !important; } /* Responsive Design */ @@ -234,20 +236,20 @@ } .actionButton { - padding: 4px 8px; + padding: 4px; font-size: 11px; - min-width: 24px; - min-height: 24px; + min-width: 22px; + min-height: 22px; } .actionIcon { - font-size: 14px; - height: 14px; - width: 14px; + font-size: 13px; + height: 13px; + width: 13px; } } -/* Dark theme support - All use same styling as delete button */ +/* Dark theme support */ @media (prefers-color-scheme: dark) { .actionButton.edit { background: var(--color-secondary); @@ -258,19 +260,19 @@ } .actionButton.delete { - background: var(--color-secondary); + background: var(--color-gray); } .actionButton.delete:hover { - background: var(--color-secondary-hover); + background: var(--color-red); } .actionButton.download { - background: var(--color-secondary); + background: var(--color-gray); } .actionButton.download:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover); } .actionButton.view { @@ -282,11 +284,11 @@ } .actionButton.copy { - background: var(--color-secondary); + background: var(--color-gray); } .actionButton.copy:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover); } .actionButton.connect { @@ -298,18 +300,18 @@ } .actionButton.refresh { - background: var(--color-secondary); + background: var(--color-gray); } .actionButton.refresh:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover); } .actionButton.custom { - background: var(--color-secondary); + background: var(--color-gray); } .actionButton.custom:hover { - background: var(--color-secondary-hover); + background: var(--color-gray-hover); } } diff --git a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.module.css b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.module.css index 2aa4da0..397acad 100644 --- a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.module.css +++ b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.module.css @@ -15,8 +15,8 @@ gap: 20px; padding: 15px; background: var(--color-bg); - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 8px; } .searchContainer { @@ -44,8 +44,8 @@ gap: 5px; height: 40px; padding: 0 14px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; background: var(--color-bg); color: var(--color-text); font-size: 12px; @@ -58,9 +58,9 @@ } .csvExportButton:hover:not(:disabled) { - background: var(--color-secondary); - color: var(--color-bg); - border-color: var(--color-secondary); + background: var(--color-gray, #718096); + color: #fff; + border-color: var(--color-gray, #718096); } .csvExportButton:disabled { @@ -80,21 +80,21 @@ justify-content: center; width: 40px; height: 40px; - border: 1px solid var(--color-primary); - border-radius: 50%; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; background: var(--color-bg); color: var(--color-text); cursor: pointer; - transition: all 0.2s ease; + transition: all 0.15s ease; font-size: 16px; font-family: var(--font-family); flex-shrink: 0; } .refreshButton:hover:not(:disabled) { - background: var(--color-secondary); + background: var(--color-gray, #718096); color: white; - border-color: var(--color-secondary); + border-color: var(--color-gray, #718096); } .refreshButton:disabled { @@ -133,7 +133,7 @@ left: 12px; top: -8px; transform: translateY(0); - color: var(--color-secondary); + color: var(--color-text, #2D3748); font-size: 12px; pointer-events: none; transition: all 0.3s ease; @@ -147,19 +147,19 @@ width: 100%; height: 40px; padding: 8px 12px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: var(--font-family); background: var(--color-bg); color: var(--color-text); - transition: all 0.2s ease; + transition: all 0.15s ease; box-sizing: border-box; } .searchInput:focus { border-color: var(--color-secondary); - box-shadow: 0 0 0 2px rgba(var(--color-secondary), 0.1); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15); } .searchInput::placeholder { @@ -193,15 +193,15 @@ width: 100%; height: 40px; padding: 6px 10px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: var(--font-family); background: var(--color-bg); color: var(--color-text); opacity: 0.6; min-width: 120px; - transition: all 0.2s ease; + transition: all 0.15s ease; box-sizing: border-box; } @@ -209,7 +209,7 @@ outline: none; border-color: var(--color-secondary); opacity: 1; - box-shadow: 0 0 0 2px rgba(var(--color-secondary), 0.1); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15); } .filterInput::placeholder { @@ -219,8 +219,8 @@ .filterSelect { height: 40px; padding: 6px 10px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: var(--font-family); background: var(--color-bg); @@ -239,7 +239,7 @@ /* Hide dropdown arrow when filter has a value */ .filterSelect.hasValue { background-image: none; - color: var(--color-secondary); + color: var(--color-text); border-color: var(--color-secondary); opacity: 1; } @@ -257,7 +257,7 @@ transform: translateY(-50%); background: none; border: none; - color: var(--color-primary); + color: var(--color-gray, #718096); cursor: pointer; font-size: 16px; padding: 2px; @@ -345,8 +345,8 @@ .pageSizeSelect { height: 32px; padding: 4px 8px; - border: 1px solid var(--color-primary); - border-radius: 4px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: var(--font-family); background: var(--color-bg); @@ -361,13 +361,13 @@ } .paginationButton { - width: 36px; - height: 36px; + width: 30px; + height: 30px; padding: 0; - border: none; - background: var(--color-secondary); - color: white; - border-radius: 50%; + border: 1px solid var(--color-border, #E2E8F0); + background: var(--color-bg, #fff); + color: var(--color-text, #2D3748); + border-radius: 6px; cursor: pointer; font-family: var(--font-family); transition: all 0.2s ease; @@ -380,7 +380,8 @@ } .paginationButton:hover:not(:disabled) { - background: var(--color-secondary-hover); + background: var(--color-gray-disabled, #EDF2F7); + border-color: var(--color-gray, #718096); } .paginationButton:disabled { diff --git a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.module.css b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.module.css index edb7750..db5b91c 100644 --- a/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.module.css +++ b/src/components/FormGenerator/FormGeneratorForm/FormGeneratorForm.module.css @@ -16,7 +16,7 @@ .loadingSpinner { width: 40px; height: 40px; - border: 3px solid var(--color-primary); + border: 2px solid var(--color-border, #E2E8F0); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; @@ -51,10 +51,10 @@ .fieldInput { width: 100%; padding: 12px 12px 8px 12px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; - transition: all 0.2s ease; + transition: all 0.15s ease; background-color: var(--color-bg); box-sizing: border-box; color: var(--color-text); @@ -79,8 +79,8 @@ .multiselectContainer { width: 100%; padding: 12px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; background-color: var(--color-bg); min-height: 60px; max-height: 200px; @@ -128,7 +128,7 @@ width: 16px; height: 16px; cursor: pointer; - accent-color: var(--color-primary); + accent-color: var(--color-secondary); } .multiselectLabel { @@ -139,7 +139,7 @@ .multiselectCount { font-size: 0.75rem; - color: var(--color-primary); + color: var(--color-secondary); margin-left: 4px; font-weight: normal; } @@ -148,10 +148,10 @@ .fieldTextarea { width: 100%; padding: 12px 12px 8px 12px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; - transition: all 0.2s ease; + transition: all 0.15s ease; background-color: var(--color-bg); box-sizing: border-box; color: var(--color-text); @@ -184,7 +184,7 @@ left: 12px; top: 50%; transform: translateY(-50%); - color: var(--color-primary); + color: var(--color-gray, #718096); opacity: 0.7; font-size: 14px; transition: all 0.2s ease; @@ -197,7 +197,7 @@ left: 12px; top: -8px; transform: translateY(0); - color: var(--color-primary); + color: var(--color-text, #2D3748); opacity: 1; font-size: 12px; font-weight: 500; @@ -227,8 +227,8 @@ .readonlyField { padding: 12px 12px 8px 12px; background-color: var(--color-bg); - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; color: var(--color-text); font-size: 14px; min-height: 20px; @@ -252,7 +252,7 @@ width: 16px; height: 16px; cursor: pointer; - accent-color: var(--color-primary); + accent-color: var(--color-secondary); } /* Required field indicator */ @@ -276,15 +276,15 @@ gap: 12px; margin-top: 24px; padding-top: 16px; - border-top: 1px solid var(--color-primary); + border-top: 1px solid var(--color-border, #E2E8F0); } .cancelButton { padding: 8px 16px; - border: 1px solid var(--color-primary); + border: 1px solid var(--color-border, #E2E8F0); background-color: var(--color-bg); color: var(--color-text); - border-radius: 25px; + border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; @@ -307,11 +307,11 @@ border: none; background-color: var(--color-secondary); color: var(--color-bg); - border-radius: 25px; + border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.15s ease; } .submitButton:hover:not(:disabled) { diff --git a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css index cd30431..f9f1db0 100644 --- a/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css +++ b/src/components/FormGenerator/FormGeneratorTable/FormGeneratorTable.module.css @@ -4,12 +4,9 @@ gap: 10px; width: 100%; font-family: var(--font-family); - /* Fill available space and constrain height */ min-height: 0; flex: 1; - /* Prevent overflow - constrain to parent height */ overflow: hidden; - /* Ensure container respects parent's height */ height: 100%; max-height: 100%; } @@ -22,62 +19,57 @@ margin-bottom: 10px; } -/* Table wrapper - contains top scrollbar and table container */ +/* Table wrapper */ .tableWrapper { display: flex; flex-direction: column; flex: 1; min-height: 0; - /* Constrain height to prevent growing beyond parent */ max-height: 100%; overflow: hidden; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #e2e8f0); + border-radius: 10px; background: var(--color-bg); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); } -/* Top horizontal scrollbar - syncs with table container */ +/* Top horizontal scrollbar */ .topScrollbar { overflow-x: auto; overflow-y: hidden; flex-shrink: 0; background: var(--color-bg); - border-bottom: 1px solid var(--color-primary); - border-radius: 25px 25px 0 0; + border-bottom: 1px solid var(--color-border, #e2e8f0); + border-radius: 10px 10px 0 0; } -/* Inner div that matches table width for proper scrollbar sizing */ .topScrollbarInner { - height: 1px; /* Minimal height - just need width to activate scrollbar */ + height: 1px; } -/* Table Container - scrollable area for table data only (vertical only) */ +/* Table Container */ .tableContainer { position: relative; - overflow-x: hidden; /* Horizontal scroll handled by topScrollbar */ + overflow-x: hidden; overflow-y: auto; scrollbar-gutter: stable; background: var(--color-bg); - /* Fill remaining space but constrain to available height */ flex: 1 1 0; min-height: 0; max-height: 100%; - border-radius: 0 0 25px 25px; + border-radius: 0 0 10px 10px; } -/* Empty table styling - no extra space, just header */ .emptyTable { min-height: auto; height: auto; max-height: none; } -/* Hide top scrollbar when table is empty */ .emptyTable .topScrollbar { display: none; } -/* Empty state styling */ .emptyState { display: flex; align-items: center; @@ -86,13 +78,12 @@ padding: 40px 20px; } -/* Empty message styling */ .emptyMessage { text-align: center; padding: 20px; color: var(--color-text); - opacity: 0.7; - font-size: 1rem; + opacity: 0.5; + font-size: 0.9rem; line-height: 1.5; } @@ -101,23 +92,21 @@ justify-content: center; align-items: center; padding: 40px; - font-size: 16px; - color: var(--color-text); + font-size: 14px; + color: var(--color-text-secondary, #94a3b8); } /* Table Styles */ .table { width: 100%; - /* Use separate borders for sticky header support */ border-collapse: separate; border-spacing: 0; - font-size: 12px; + font-size: 13px; background: var(--color-bg); table-layout: fixed; word-wrap: break-word; } -/* Disabled user row styling */ .table tbody tr[data-user-enabled="false"] { opacity: 0.6 !important; background-color: rgba(0, 0, 0, 0.02) !important; @@ -128,7 +117,6 @@ background-color: rgba(0, 0, 0, 0.05) !important; } -/* Dark mode disabled user styling */ .dark .table tbody tr[data-user-enabled="false"] { background-color: rgba(255, 255, 255, 0.02) !important; } @@ -137,13 +125,11 @@ background-color: rgba(255, 255, 255, 0.05) !important; } -/* Sticky thead for table header */ +/* Sticky thead */ .table thead { position: sticky; top: 0; z-index: 10; - /* Shadow to separate header from scrolled content */ - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .table thead tr { @@ -151,20 +137,19 @@ } .th { - background: var(--color-bg); - padding: 6px 10px; + background: var(--color-bg, #f8fafc); + padding: 10px 12px; text-align: left; - font-weight: 400; - font-size: 12px; - color: var(--color-text); - white-space: normal; - word-wrap: break-word; - overflow-wrap: break-word; - word-break: break-word; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--color-text-secondary, #64748b); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; user-select: none; - overflow: visible; - /* Border separates header from scrolled content */ - border-bottom: 2px solid var(--color-primary); + border-bottom: 1px solid var(--color-border, #e2e8f0); } .th.actionsColumn { @@ -173,11 +158,12 @@ .th.sortable { cursor: pointer; - transition: background-color 0.2s ease; + transition: background-color 0.15s ease, color 0.15s ease; } .th.sortable:hover { - background: var(--color-gray-disabled); + background: var(--color-gray-disabled, #f1f5f9); + color: var(--color-text, #334155); } .headerContent { @@ -193,8 +179,8 @@ } .sortIcon { - font-size: 12px; - color: var(--color-text-secondary, #999); + font-size: 11px; + color: var(--color-text-secondary, #94a3b8); cursor: pointer; padding: 2px; display: inline-flex; @@ -203,7 +189,7 @@ } .sortIcon:hover { - color: var(--color-secondary); + color: var(--color-text, #334155); } .sortIcon.sortActive { @@ -216,11 +202,11 @@ font-weight: 500; } -/* Filter icon in column header */ +/* Filter icon */ .filterIcon { background: none; border: none; - color: var(--color-text-secondary, #999); + color: var(--color-text-secondary, #94a3b8); cursor: pointer; padding: 2px; display: flex; @@ -231,13 +217,13 @@ } .filterIcon:hover { - color: var(--color-secondary); - background: rgba(var(--color-secondary-rgb), 0.1); + color: var(--color-text, #334155); + background: rgba(0, 0, 0, 0.05); } .filterIcon.filterActive { color: var(--color-secondary); - background: rgba(var(--color-secondary-rgb), 0.15); + background: rgba(var(--color-secondary-rgb), 0.1); } /* Filter dropdown */ @@ -248,9 +234,9 @@ min-width: 180px; max-width: 300px; background: var(--color-bg); - border: 1px solid var(--color-border, #ddd); + border: 1px solid var(--color-border, #e2e8f0); border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); z-index: 100; margin-top: 4px; } @@ -260,7 +246,7 @@ justify-content: space-between; align-items: center; padding: 8px 12px; - border-bottom: 1px solid var(--color-border, #ddd); + border-bottom: 1px solid var(--color-border, #e2e8f0); font-size: 12px; font-weight: 500; color: var(--color-text); @@ -269,7 +255,7 @@ .filterClearBtn { background: none; border: none; - color: var(--color-text-secondary); + color: var(--color-text-secondary, #94a3b8); cursor: pointer; font-size: 14px; padding: 2px 6px; @@ -277,8 +263,8 @@ } .filterClearBtn:hover { - background: rgba(255, 0, 0, 0.1); - color: #c00; + background: rgba(0, 0, 0, 0.06); + color: var(--color-text, #334155); } .filterDropdownOptions { @@ -298,23 +284,23 @@ } .filterOption:hover { - background: var(--color-gray-disabled, #f5f5f5); + background: var(--color-gray-disabled, #f1f5f9); } .filterOptionSelected { - background: rgba(var(--color-secondary-rgb), 0.15); + background: rgba(var(--color-secondary-rgb), 0.08); color: var(--color-secondary); font-weight: 500; } .filterOptionSelected:hover { - background: rgba(var(--color-secondary-rgb), 0.2); + background: rgba(var(--color-secondary-rgb), 0.12); } .filterOptionMore { padding: 6px 12px; font-size: 11px; - color: var(--color-text-secondary); + color: var(--color-text-secondary, #94a3b8); font-style: italic; } @@ -330,21 +316,21 @@ } .resizeHandle:hover { - background: var(--color-secondary); - opacity: 0.5; + background: var(--color-border, #cbd5e1); } .resizeHandle:active { background: var(--color-secondary); - opacity: 0.8; + opacity: 0.6; } +/* Table cells */ .td { - padding: 4px 10px; - border-top: 1px solid var(--color-primary); + padding: 8px 12px; + border-top: 1px solid var(--color-border, #f1f5f9); color: var(--color-text); font-weight: 400; - font-size: 12px; + font-size: 13px; vertical-align: middle; word-wrap: break-word; overflow-wrap: break-word; @@ -353,23 +339,31 @@ overflow: visible; } -/* FK Loading state - shows truncated ID while loading */ .fkLoading { color: var(--color-text); opacity: 0.6; font-style: italic; } +/* Rows */ .tr { - transition: background-color 0.2s ease; + transition: background-color 0.12s ease; } .tr:hover { - background: transparent; + background: var(--color-gray-disabled, #f8fafc); +} + +.tr:nth-child(even) { + background: rgba(0, 0, 0, 0.015); +} + +.tr:nth-child(even):hover { + background: var(--color-gray-disabled, #f8fafc); } .tr.selected { - background: rgba(var(--color-secondary-rgb), 0.1); + background: rgba(var(--color-secondary-rgb), 0.08); } .tr.clickable { @@ -384,14 +378,12 @@ position: relative; } -/* Selection column header - background inherited from thead */ thead .selectColumn { background: var(--color-bg); } -/* Selection Column border only on body cells, not header */ tbody .selectColumn { - border-top: 1px solid var(--color-primary); + border-top: 1px solid var(--color-border, #f1f5f9); } .selectColumn input[type="checkbox"] { @@ -402,7 +394,7 @@ tbody .selectColumn { accent-color: var(--color-secondary); margin: 0; padding: 0; - border: 2px solid var(--color-primary); + border: 1.5px solid var(--color-border, #cbd5e1); border-radius: 3px; background: var(--color-bg); position: relative; @@ -426,7 +418,7 @@ tbody .selectColumn { outline-offset: 2px; } -/* Actions Column - Fixed width like select column */ +/* Actions Column */ .actionsColumn { white-space: nowrap; text-align: center; @@ -437,27 +429,24 @@ tbody .selectColumn { position: relative; } -/* Actions column header - background inherited from thead */ thead .actionsColumn { background: var(--color-bg); } -/* Actions Column border only on body cells, not header */ tbody .actionsColumn { - border-top: 1px solid var(--color-primary); + border-top: 1px solid var(--color-border, #f1f5f9); } .actionButtons { display: flex; flex-wrap: nowrap; - gap: 2px; + gap: 4px; justify-content: center; align-items: center; width: 100%; margin: 0 auto; } -/* Enable wrapping only when column exceeds 20% of container width */ .actionButtonsWrap { flex-wrap: wrap; } @@ -466,29 +455,31 @@ tbody .actionsColumn { display: flex; align-items: center; justify-content: center; - padding: 4px; + padding: 5px; border: none; - border-radius: 50%; + border-radius: 6px; font-size: 11px; font-family: var(--font-family); cursor: pointer; - transition: all 0.2s ease; + transition: all 0.15s ease; white-space: nowrap; position: relative; - min-width: 24px; - min-height: 24px; - background: var(--color-secondary); - color: var(--color-bg); + min-width: 26px; + min-height: 26px; + background: var(--color-text-secondary, #64748b); + color: #fff; } .actionButton:hover { - background: var(--color-secondary-hover); + background: var(--color-text, #334155); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12); } .actionIcon { - font-size: 14px; - height: 14px; - width: 14px; + font-size: 13px; + height: 13px; + width: 13px; display: flex; align-items: center; justify-content: center; @@ -500,8 +491,8 @@ tbody .actionsColumn { gap: 2px; justify-content: center; align-items: center; - background: var(--color-secondary); - border-radius: 25px; + background: var(--color-text-secondary, #64748b); + border-radius: 8px; } .confirmButton { @@ -510,8 +501,8 @@ tbody .actionsColumn { } .confirmButton:hover { - background: transparent !important; - transform: scale(1.05); + background: rgba(255, 255, 255, 0.15) !important; + transform: none; } .cancelButton { @@ -520,23 +511,24 @@ tbody .actionsColumn { } .cancelButton:hover { - background: transparent !important; - transform: scale(1.05); + background: rgba(255, 255, 255, 0.15) !important; + transform: none; } .actionButton:disabled { - opacity: 0.6; + opacity: 0.4; cursor: not-allowed; transform: none !important; + box-shadow: none !important; } +/* Pagination */ .pagination { display: flex; justify-content: flex-end; align-items: center; - gap: 10px; - padding: 8px 0; - /* Ensure pagination stays visible and doesn't get cut off */ + gap: 8px; + padding: 10px 0; flex-shrink: 0; flex-wrap: wrap; background: var(--color-bg); @@ -546,8 +538,8 @@ tbody .actionsColumn { display: flex; align-items: center; gap: 8px; - font-size: 14px; - color: var(--color-text); + font-size: 13px; + color: var(--color-text-secondary, #64748b); } .pageSizeSelector label { @@ -556,11 +548,11 @@ tbody .actionsColumn { } .pageSizeSelect { - height: 32px; + height: 30px; padding: 4px 8px; - border: 1px solid var(--color-primary); - border-radius: 4px; - font-size: 14px; + border: 1px solid var(--color-border, #e2e8f0); + border-radius: 6px; + font-size: 13px; font-family: var(--font-family); background: var(--color-bg); color: var(--color-text); @@ -571,43 +563,45 @@ tbody .actionsColumn { .pageSizeSelect:focus { outline: none; border-color: var(--color-secondary); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb), 0.15); } .paginationButton { - width: 36px; - height: 36px; + width: 30px; + height: 30px; padding: 0; - border: none; - background: var(--color-secondary); - color: white; - border-radius: 50%; + border: 1px solid var(--color-border, #e2e8f0); + background: var(--color-bg, #fff); + color: var(--color-text, #334155); + border-radius: 6px; cursor: pointer; font-family: var(--font-family); - transition: all 0.2s ease; + transition: all 0.15s ease; display: flex; align-items: center; justify-content: center; - font-size: 18px; + font-size: 14px; font-weight: 500; } .paginationButton:hover:not(:disabled) { - background: var(--color-secondary-hover); + background: var(--color-gray-disabled, #f1f5f9); + border-color: var(--color-text-secondary, #94a3b8); } .paginationButton:disabled { - opacity: 0.5; + opacity: 0.35; cursor: not-allowed; } .paginationInfo { - font-size: 14px; - color: var(--color-text); - margin: 0 15px; + font-size: 13px; + color: var(--color-text-secondary, #64748b); + margin: 0 8px; white-space: nowrap; } -/* Page numbers container */ +/* Page numbers */ .pageNumbers { display: flex; flex-wrap: wrap; @@ -617,50 +611,51 @@ tbody .actionsColumn { max-width: 60vw; max-height: 120px; overflow-y: auto; - padding: 4px; + padding: 2px; } -/* Individual page number button */ .pageNumber { min-width: 28px; height: 28px; padding: 0 6px; - border: 1px solid var(--color-border, #ddd); + border: 1px solid var(--color-border, #e2e8f0); background: var(--color-bg, #fff); color: var(--color-text); - border-radius: 4px; + border-radius: 6px; cursor: pointer; font-family: var(--font-family); font-size: 12px; - transition: all 0.15s ease; + transition: all 0.12s ease; display: flex; align-items: center; justify-content: center; } .pageNumber:hover:not(:disabled) { - background: var(--color-secondary); - color: white; - border-color: var(--color-secondary); + background: var(--color-gray-disabled, #f1f5f9); + border-color: var(--color-text-secondary, #94a3b8); } .pageNumber:disabled { cursor: default; } -/* Active/current page number */ .pageNumberActive { - background: var(--color-secondary); + background: var(--color-text, #334155); color: white; - border-color: var(--color-secondary); + border-color: var(--color-text, #334155); font-weight: 600; } -/* Ellipsis indicator */ +.pageNumberActive:hover:not(:disabled) { + background: var(--color-text, #1e293b); + border-color: var(--color-text, #1e293b); +} + .pageEllipsis { - padding: 0 8px; - color: var(--color-text-secondary, #666); - font-size: 14px; + padding: 0 6px; + color: var(--color-text-secondary, #94a3b8); + font-size: 13px; } /* Loading overlay */ @@ -676,24 +671,23 @@ tbody .actionsColumn { align-items: center; justify-content: center; z-index: 10; - border-radius: 8px; + border-radius: 10px; } .loadingOverlay p { margin-top: 12px; - color: var(--color-text-secondary, #666); - font-size: 14px; + color: var(--color-text-secondary, #64748b); + font-size: 13px; } -/* Responsive Design */ +/* Responsive */ @media (max-width: 768px) { .tableContainer { flex: 1; min-height: 0; max-height: 100%; } - - /* Empty table styling - no extra space */ + .emptyTable { min-height: auto; height: auto; @@ -701,8 +695,8 @@ tbody .actionsColumn { .th, .td { - padding: 4px 8px; - font-size: 11px; + padding: 6px 8px; + font-size: 12px; } .actionButtons { @@ -711,7 +705,7 @@ tbody .actionsColumn { } .actionButton { - padding: 3px; + padding: 4px; font-size: 10px; min-width: 22px; min-height: 22px; @@ -731,7 +725,7 @@ tbody .actionsColumn { .paginationInfo { text-align: center; margin: 0; - font-size: 13px; + font-size: 12px; } .pageNumbers { @@ -746,18 +740,22 @@ tbody .actionsColumn { } } -/* Dark theme support */ +/* Dark theme */ @media (prefers-color-scheme: dark) { .th.sortable:hover { - background: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.06); } .tr:hover { - background: transparent; + background: rgba(255, 255, 255, 0.04); + } + + .tr:nth-child(even) { + background: rgba(255, 255, 255, 0.02); } .tr.selected { - background: rgba(var(--color-secondary-rgb), 0.2); + background: rgba(var(--color-secondary-rgb), 0.15); } .loadingOverlay { @@ -774,45 +772,43 @@ tbody .actionsColumn { .actionButton:focus, .paginationButton:focus { outline: none; + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb), 0.3); } -/* Custom scrollbar for table container (vertical only) */ +/* Scrollbar */ .tableContainer::-webkit-scrollbar { - width: 8px; - height: 8px; + width: 6px; + height: 6px; } .tableContainer::-webkit-scrollbar-track { - background: var(--color-gray-disabled); - border-radius: 4px; + background: transparent; } .tableContainer::-webkit-scrollbar-thumb { - background: var(--color-gray); - border-radius: 4px; + background: var(--color-border, #cbd5e1); + border-radius: 3px; } .tableContainer::-webkit-scrollbar-thumb:hover { - background: var(--color-secondary); + background: var(--color-text-secondary, #94a3b8); } -/* Custom scrollbar for top scrollbar (horizontal only) */ .topScrollbar::-webkit-scrollbar { - height: 8px; + height: 6px; } .topScrollbar::-webkit-scrollbar-track { - background: var(--color-gray-disabled); - border-radius: 4px; + background: transparent; } .topScrollbar::-webkit-scrollbar-thumb { - background: var(--color-gray); - border-radius: 4px; + background: var(--color-border, #cbd5e1); + border-radius: 3px; } .topScrollbar::-webkit-scrollbar-thumb:hover { - background: var(--color-secondary); + background: var(--color-text-secondary, #94a3b8); } /* Loading State */ @@ -823,16 +819,16 @@ tbody .actionsColumn { justify-content: center; padding: 4rem 2rem; text-align: center; - color: var(--color-text-secondary, #666); + color: var(--color-text-secondary, #64748b); } .loadingSpinner { - width: 40px; - height: 40px; - border: 3px solid var(--color-bg-secondary, #e9ecef); - border-top: 3px solid var(--color-primary, #007bff); + width: 32px; + height: 32px; + border: 2px solid var(--color-border, #e2e8f0); + border-top: 2px solid var(--color-text-secondary, #64748b); border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.8s linear infinite; margin-bottom: 1rem; } @@ -841,16 +837,16 @@ tbody .actionsColumn { 100% { transform: rotate(360deg); } } -/* Inline Editable Boolean Cells */ +/* Boolean Cells */ .booleanCell { display: inline-flex; align-items: center; justify-content: center; - width: 28px; - height: 28px; - font-size: 16px; + width: 26px; + height: 26px; + font-size: 14px; font-weight: bold; - border-radius: 4px; + border-radius: 6px; transition: all 0.15s ease; user-select: none; } @@ -858,12 +854,12 @@ tbody .actionsColumn { .booleanEditable { cursor: pointer; background: transparent; - border: 2px solid var(--color-border, #dee2e6); + border: 1.5px solid var(--color-border, #e2e8f0); } .booleanEditable:hover { transform: scale(1.1); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } .booleanEditable:active { @@ -871,25 +867,25 @@ tbody .actionsColumn { } .booleanEditable.booleanTrue { - color: var(--color-success, #28a745); - border-color: var(--color-success, #28a745); - background: rgba(40, 167, 69, 0.1); + color: var(--color-success, #16a34a); + border-color: var(--color-success, #16a34a); + background: rgba(22, 163, 74, 0.08); } .booleanEditable.booleanTrue:hover { - background: rgba(40, 167, 69, 0.2); + background: rgba(22, 163, 74, 0.15); } .booleanEditable.booleanFalse { - color: var(--color-text-secondary, #6c757d); - border-color: var(--color-border, #dee2e6); + color: var(--color-text-secondary, #94a3b8); + border-color: var(--color-border, #e2e8f0); background: transparent; } .booleanEditable.booleanFalse:hover { - color: var(--color-danger, #dc3545); - border-color: var(--color-danger, #dc3545); - background: rgba(220, 53, 69, 0.1); + color: var(--color-text, #334155); + border-color: var(--color-text-secondary, #94a3b8); + background: rgba(0, 0, 0, 0.03); } .booleanReadonly { @@ -900,11 +896,11 @@ tbody .actionsColumn { } .booleanReadonly.booleanTrue { - color: var(--color-success, #28a745); + color: var(--color-success, #16a34a); } .booleanReadonly.booleanFalse { - color: var(--color-text-secondary, #adb5bd); + color: var(--color-text-secondary, #94a3b8); } .booleanLoading { @@ -912,12 +908,12 @@ tbody .actionsColumn { align-items: center; justify-content: center; animation: booleanPulse 1s ease-in-out infinite; - color: var(--color-primary, #007bff); + color: var(--color-text-secondary, #64748b); font-size: 14px; } @keyframes booleanPulse { - 0%, 100% { opacity: 0.4; } + 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } } @@ -928,7 +924,7 @@ tbody .actionsColumn { } .groupHeader:hover { - background-color: var(--bg-hover, #f1f5f9) !important; + background-color: var(--color-gray-disabled, #f1f5f9) !important; } .groupHeader .td { @@ -943,13 +939,13 @@ tbody .actionsColumn { width: 18px; height: 18px; font-size: 10px; - color: var(--text-muted, #64748b); + color: var(--color-text-secondary, #64748b); transition: transform 0.2s ease; } .groupCount { font-size: 11px; - color: var(--text-muted, #64748b); + color: var(--color-text-secondary, #94a3b8); font-weight: 400; margin-left: 4px; } @@ -959,5 +955,3 @@ tbody .actionsColumn { gap: 4px; align-items: center; } - - diff --git a/src/components/UiComponents/DropdownSelect/DropdownSelect.module.css b/src/components/UiComponents/DropdownSelect/DropdownSelect.module.css index 6fd5806..558da4e 100644 --- a/src/components/UiComponents/DropdownSelect/DropdownSelect.module.css +++ b/src/components/UiComponents/DropdownSelect/DropdownSelect.module.css @@ -9,8 +9,8 @@ justify-content: space-between; gap: 8px; padding: 12px 16px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: var(--font-family); cursor: pointer; @@ -23,13 +23,13 @@ .dropdownButton:hover:not(.disabled):not(:disabled) { border-color: var(--color-secondary); - background-color: var(--color-secondary); - color: white; + background-color: var(--color-bg); + color: var(--color-text); } .dropdownButton:focus { outline: none; - box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb, 0, 123, 255), 0.1); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15); } .dropdownButton.disabled, @@ -146,7 +146,7 @@ .buttonSpinner { width: 16px; height: 16px; - border: 2px solid var(--color-primary); + border: 2px solid var(--color-border, #E2E8F0); border-top-color: transparent; border-radius: 50%; animation: spin 0.6s linear infinite; @@ -164,7 +164,7 @@ left: 0; right: 0; background-color: var(--color-bg); - border: 1px solid var(--color-primary); + border: 1px solid var(--color-border, #E2E8F0); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); z-index: 1000; @@ -178,7 +178,7 @@ font-weight: 600; color: var(--color-text); background-color: var(--color-bg); - border-bottom: 1px solid var(--color-primary); + border-bottom: 1px solid var(--color-border, #E2E8F0); text-transform: uppercase; letter-spacing: 0.5px; } @@ -205,7 +205,7 @@ cursor: pointer; font-family: var(--font-family); transition: all 0.2s ease; - border-bottom: 1px solid var(--color-primary); + border-bottom: 1px solid var(--color-border, #E2E8F0); color: var(--color-text); display: flex; align-items: center; @@ -218,19 +218,18 @@ } .dropdownItem:hover { - background-color: var(--color-secondary); - color: white; + background-color: var(--color-highlight-gray, #F7FAFC); + color: var(--color-text); } .dropdownItemSelected { - background-color: var(--color-primary); - color: white; + background-color: rgba(var(--color-secondary-rgb, 74, 111, 165), 0.1); + color: var(--color-secondary); font-weight: 500; } .dropdownItemSelected:hover { - background-color: var(--color-primary); - opacity: 0.9; + background-color: rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15); } .itemLabel { diff --git a/src/components/UiComponents/Popup/Popup.module.css b/src/components/UiComponents/Popup/Popup.module.css index 424f1ae..ce95dcd 100644 --- a/src/components/UiComponents/Popup/Popup.module.css +++ b/src/components/UiComponents/Popup/Popup.module.css @@ -15,10 +15,10 @@ /* Main popup container */ .popup { - border: 1px solid var(--color-primary); + border: 1px solid var(--color-border, #E2E8F0); background: var(--color-bg); - border-radius: 25px; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); + border-radius: 10px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); max-height: 90vh; display: flex; flex-direction: column; @@ -64,7 +64,7 @@ /* Header section */ .header { padding: 20px 24px 16px; - border-bottom: 1px solid var(--color-primary); + border-bottom: 1px solid var(--color-border, #E2E8F0); display: flex; justify-content: space-between; align-items: center; @@ -101,13 +101,13 @@ gap: 6px; padding: 15px; border: none; - border-radius: 25px; + border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.15s ease; white-space: nowrap; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-family: var(--font-family); background: var(--color-secondary); color: white; } @@ -140,13 +140,13 @@ } .closeButton { - background: var(--color-primary); + background: var(--color-gray-disabled, #CBD5E0); border: none; font-size: 18px; - color: #3A3A3A; + color: var(--color-text, #2D3748); cursor: pointer; padding: 8px; - border-radius: 25px; + border-radius: 6px; transition: all 0.2s ease; line-height: 1; margin-left: 8px; @@ -158,8 +158,8 @@ } .closeButton:hover { - background-color: var(--color-primary-hover); - color: #3A3A3A; + background-color: var(--color-gray, #718096); + color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } @@ -173,7 +173,7 @@ /* Footer section */ .footer { padding: 16px 24px; - border-top: 1px solid var(--color-primary); + border-top: 1px solid var(--color-border, #E2E8F0); display: flex; justify-content: flex-end; gap: 12px; diff --git a/src/components/UiComponents/TextField/TextField.module.css b/src/components/UiComponents/TextField/TextField.module.css index 0415031..69de0c0 100644 --- a/src/components/UiComponents/TextField/TextField.module.css +++ b/src/components/UiComponents/TextField/TextField.module.css @@ -30,8 +30,8 @@ .textarea { width: 100%; padding: 12px 16px; - border: 1px solid var(--color-primary); - border-radius: 25px; + border: 1px solid var(--color-border, #E2E8F0); + border-radius: 6px; font-size: 14px; font-family: inherit; color: var(--color-text); @@ -54,7 +54,7 @@ .textarea:focus { outline: none; border-color: var(--color-secondary); - box-shadow: 0 0 0 3px rgba(var(--color-primary-rgb, 0, 123, 255), 0.1); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15); } .input:disabled, diff --git a/src/pages/FeatureView.tsx b/src/pages/FeatureView.tsx index 36fe017..ecefae6 100644 --- a/src/pages/FeatureView.tsx +++ b/src/pages/FeatureView.tsx @@ -221,6 +221,9 @@ export const FeatureViewPage: React.FC = ({ view }) => { return null; } + // GraphicalEditor sub-pages have their own headers with actions; skip the wrapper title. + const _skipViewHeader = featureCode === 'graphicalEditor'; + // View-Komponente finden const featureViews = VIEW_COMPONENTS[featureCode]; if (!featureViews) { @@ -240,9 +243,11 @@ export const FeatureViewPage: React.FC = ({ view }) => { return (
-
-

{viewLabel}

-
+ {!_skipViewHeader && ( +
+

{viewLabel}

+
+ )}
diff --git a/src/pages/Login.module.css b/src/pages/Login.module.css index 16905c5..cb0ec8d 100644 --- a/src/pages/Login.module.css +++ b/src/pages/Login.module.css @@ -47,8 +47,8 @@ margin-top: 2rem; padding: 2rem; - border-radius: 25px; - border: 1px solid color-mix(in srgb, var(--color-primary) 15%, transparent); + border-radius: 10px; + border: 1px solid var(--color-border, #E2E8F0); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02), 0 0 10px rgba(0, 0, 0, 0.1); } @@ -73,7 +73,7 @@ left: 16px; top: 50%; transform: translateY(-50%); - color: var(--color-primary); + color: var(--color-gray, #718096); font-size: 1rem; pointer-events: none; transition: all 0.3s ease; @@ -101,10 +101,10 @@ height: 50px; padding: 12px 16px; border: 1px solid var(--color-gray-disabled); - border-radius: 25px; + border-radius: 6px; font-size: 1rem; - transition: all 0.2s ease; + transition: all 0.15s ease; background-color: var(--color-bg); color: var(--color-text); font-family: var(--font-family); @@ -147,7 +147,7 @@ width: 100%; height: 50px; padding: 12px 20px; - border-radius: 25px; + border-radius: 6px; font-size: 1rem; font-weight: 500; @@ -171,7 +171,7 @@ .loginButton { background-color: var(--color-secondary); - color: var(--color-text); + color: #fff; } .loginButton:hover { @@ -179,21 +179,23 @@ } .microsoftButton { - background-color: var(--color-primary); - color: var(--color-bg); + background-color: var(--color-gray-disabled, #CBD5E0); + color: var(--color-text); } .microsoftButton:hover { - background-color: var(--color-primary-hover); + background-color: var(--color-gray, #718096); + color: #fff; } .googleButton { - background-color: var(--color-primary); - color: var(--color-bg); + background-color: var(--color-gray-disabled, #CBD5E0); + color: var(--color-text); } .googleButton:hover { - background-color: var(--color-primary-hover); + background-color: var(--color-gray, #718096); + color: #fff; } .divider { @@ -252,13 +254,13 @@ flex: 1; height: 46px; padding: 10px 16px; - border-radius: 25px; + border-radius: 6px; font-size: 0.95rem; font-weight: 600; cursor: pointer; border: none; background-color: var(--color-secondary); - color: var(--color-text); + color: #fff; transition: all 0.2s ease; font-family: var(--font-family); } @@ -271,7 +273,7 @@ flex: 1; height: 46px; padding: 10px 16px; - border-radius: 25px; + border-radius: 6px; font-size: 0.95rem; font-weight: 600; cursor: pointer; @@ -349,7 +351,7 @@ button:disabled { width: 100%; margin-top: 1.25rem; padding: 1.25rem; - border-radius: 20px; + border-radius: 10px; } .registerLink { @@ -365,7 +367,7 @@ button:disabled { .loginBox { padding: 1rem; - border-radius: 16px; + border-radius: 10px; } .input, diff --git a/src/pages/PasswordResetRequest.module.css b/src/pages/PasswordResetRequest.module.css index 7b12a8b..98397a2 100644 --- a/src/pages/PasswordResetRequest.module.css +++ b/src/pages/PasswordResetRequest.module.css @@ -47,8 +47,8 @@ margin-top: 2rem; padding: 2rem; - border-radius: 25px; - border: 1px solid color-mix(in srgb, var(--color-primary) 15%, transparent); + border-radius: 8px; + border: 1px solid var(--color-border, #E2E8F0); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02), 0 0 10px rgba(0, 0, 0, 0.1); } @@ -105,7 +105,7 @@ height: 50px; padding: 12px 16px; border: 1px solid var(--color-gray-disabled); - border-radius: 25px; + border-radius: 8px; font-size: 1rem; transition: all 0.2s ease; @@ -145,7 +145,7 @@ width: 100%; height: 50px; padding: 12px 20px; - border-radius: 25px; + border-radius: 8px; font-size: 1rem; font-weight: 500; @@ -200,7 +200,7 @@ button:disabled { color: var(--color-secondary); background-color: var(--color-secondary-disabled); border: 1px solid var(--color-secondary); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; @@ -212,7 +212,7 @@ button:disabled { color: var(--color-success); background-color: color-mix(in srgb, var(--color-success) 10%, transparent); border: 1px solid var(--color-success); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; diff --git a/src/pages/Register.module.css b/src/pages/Register.module.css index a0350a7..c967413 100644 --- a/src/pages/Register.module.css +++ b/src/pages/Register.module.css @@ -47,8 +47,8 @@ margin-top: 2rem; padding: 2rem; - border-radius: 25px; - border: 1px solid color-mix(in srgb, var(--color-primary) 15%, transparent); + border-radius: 8px; + border: 1px solid var(--color-border, #E2E8F0); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02), 0 0 10px rgba(0, 0, 0, 0.1); } @@ -101,7 +101,7 @@ height: 50px; padding: 12px 16px; border: 1px solid var(--color-gray-disabled); - border-radius: 25px; + border-radius: 8px; font-size: 1rem; transition: all 0.2s ease; @@ -152,7 +152,7 @@ width: 100%; height: 50px; padding: 12px 20px; - border-radius: 25px; + border-radius: 8px; font-size: 1rem; font-weight: 500; @@ -214,7 +214,7 @@ button:disabled { color: var(--color-secondary); background-color: var(--color-secondary-disabled); border: 1px solid var(--color-secondary); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; @@ -226,7 +226,7 @@ button:disabled { color: var(--color-success); background-color: color-mix(in srgb, var(--color-success) 10%, transparent); border: 1px solid var(--color-success); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; diff --git a/src/pages/Reset.module.css b/src/pages/Reset.module.css index 18bda8b..49e3dfb 100644 --- a/src/pages/Reset.module.css +++ b/src/pages/Reset.module.css @@ -47,8 +47,8 @@ margin-top: 2rem; padding: 2rem; - border-radius: 25px; - border: 1px solid color-mix(in srgb, var(--color-primary) 15%, transparent); + border-radius: 8px; + border: 1px solid var(--color-border, #E2E8F0); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.02), 0 0 10px rgba(0, 0, 0, 0.1); } @@ -106,7 +106,7 @@ height: 50px; padding: 12px 16px; border: 1px solid var(--color-gray-disabled); - border-radius: 25px; + border-radius: 8px; font-size: 1rem; transition: all 0.2s ease; @@ -153,7 +153,7 @@ width: 100%; height: 50px; padding: 12px 20px; - border-radius: 25px; + border-radius: 8px; font-size: 1rem; font-weight: 500; @@ -209,7 +209,7 @@ button:disabled { color: var(--color-secondary); background-color: var(--color-secondary-disabled); border: 1px solid var(--color-secondary); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; @@ -221,7 +221,7 @@ button:disabled { color: var(--color-success); background-color: color-mix(in srgb, var(--color-success) 10%, transparent); border: 1px solid var(--color-success); - border-radius: 25px; + border-radius: 8px; padding: 12px; font-size: 0.9rem; text-align: center; diff --git a/src/pages/views/graphicalEditor/GraphicalEditorDashboardPage.tsx b/src/pages/views/graphicalEditor/GraphicalEditorDashboardPage.tsx index d985c6c..5b95772 100644 --- a/src/pages/views/graphicalEditor/GraphicalEditorDashboardPage.tsx +++ b/src/pages/views/graphicalEditor/GraphicalEditorDashboardPage.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useEffect } from 'react'; -import { FaSync, FaPlay, FaCog, FaClipboardList, FaChartBar } from 'react-icons/fa'; +import { FaSync, FaPlay, FaCog, FaClipboardList, FaChartBar, FaDownload } from 'react-icons/fa'; import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator'; import { useInstanceId } from '../../../hooks/useCurrentInstance'; import { useApiRequest } from '../../../hooks/useApi'; @@ -110,6 +110,36 @@ export const GraphicalEditorDashboardPage: React.FC = () => { load(); }, [load]); + const _downloadRunTracing = useCallback(async (run: CompletedRun) => { + if (!instanceId || !run.id) return; + try { + const data = await request({ + url: `/api/workflows/${instanceId}/runs/${run.id}/steps`, + method: 'get', + }); + const steps = data?.steps || []; + const report = { + runId: run.id, + workflowId: run.workflowId, + workflowLabel: run.workflowLabel, + status: run.status, + startedAt: _formatTs(run.sysCreatedAt), + endedAt: _formatTs(run.sysModifiedAt), + steps, + }; + const blob = new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `run-tracing-${run.id.slice(0, 8)}.json`; + a.click(); + URL.revokeObjectURL(url); + } catch (e) { + console.error('[dashboard] download tracing failed', e); + showError('Download fehlgeschlagen'); + } + }, [instanceId, request, showError]); + const runColumns: ColumnConfig[] = [ { key: 'workflowLabel', @@ -144,6 +174,26 @@ export const GraphicalEditorDashboardPage: React.FC = () => { width: 150, formatter: (v: number) => _formatTs(v), }, + { + key: 'id', + label: '', + type: 'string', + width: 50, + sortable: false, + formatter: (_v: string, row: CompletedRun) => ( + + ), + }, ]; if (!instanceId) { diff --git a/src/pages/views/graphicalEditor/GraphicalEditorPage.tsx b/src/pages/views/graphicalEditor/GraphicalEditorPage.tsx index b1371a3..e5ca004 100644 --- a/src/pages/views/graphicalEditor/GraphicalEditorPage.tsx +++ b/src/pages/views/graphicalEditor/GraphicalEditorPage.tsx @@ -1,25 +1,16 @@ /** * GraphicalEditorPage * - * Layout: [UDB sidebar (collapsible)] [FlowEditor (flex)] [Chat/Tracing (inside FlowEditor)] - * UDB provides access to Files & Sources while configuring nodes. - * AI Chat and Tracing panels are managed by the FlowEditor's CanvasHeader. - * - * File/Source attachment UX mirrors the Workspace: - * - Files: click in UDB FilesTab → added as pendingFile chip in chat input - * - Data Sources: 🔗 picker button in chat input (loaded from UDB API) + * Thin wrapper: passes instance context to FlowEditor which now owns the full layout + * including the Workspace panel (Chats/Dateien/Quellen) on the left. */ -import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react'; +import React, { useState, useCallback, useEffect, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { FaDatabase, FaChevronLeft } from 'react-icons/fa'; import { useInstanceId, useMandateId } from '../../../hooks/useCurrentInstance'; import { useLanguage } from '../../../providers/language/LanguageContext'; import { Automation2FlowEditor as FlowEditor } from '../../../components/FlowEditor'; import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from '../../../components/FlowEditor'; -import { UnifiedDataBar } from '../../../components/UnifiedDataBar'; -import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar'; import api from '../../../api'; -import styles from '../../FeatureView.module.css'; interface GraphicalEditorPageProps { persistentInstanceId?: string; @@ -35,22 +26,24 @@ export const GraphicalEditorPage: React.FC = ({ const instanceId = persistentInstanceId || urlInstanceId; const mandateId = persistentMandateId || urlMandateId; const [searchParams] = useSearchParams(); - const initialWorkflowIdRef = useRef(searchParams.get('workflowId')); + const workflowIdFromUrl = searchParams.get('workflowId'); + const [activeWorkflowId, setActiveWorkflowId] = useState(workflowIdFromUrl); + const prevWorkflowIdRef = useRef(workflowIdFromUrl); + + useEffect(() => { + if (workflowIdFromUrl && workflowIdFromUrl !== prevWorkflowIdRef.current) { + prevWorkflowIdRef.current = workflowIdFromUrl; + setActiveWorkflowId(workflowIdFromUrl); + } + }, [workflowIdFromUrl]); + const { currentLanguage } = useLanguage(); const language = (currentLanguage?.slice(0, 2) || 'de') as string; - const [udbTab, setUdbTab] = useState('files'); - const [udbOpen, setUdbOpen] = useState(true); const [pendingFiles, setPendingFiles] = useState([]); const [dataSources, setDataSources] = useState([]); const [featureDataSources, setFeatureDataSources] = useState([]); - const udbContext: UdbContext = useMemo(() => ({ - instanceId: instanceId || '', - mandateId: mandateId || '', - featureInstanceId: instanceId || '', - }), [instanceId, mandateId]); - useEffect(() => { if (!instanceId) return; api.get(`/api/workspace/${instanceId}/datasources`) @@ -108,7 +101,7 @@ export const GraphicalEditorPage: React.FC = ({ if (!instanceId) { return ( -
+

Graphical Editor

Keine Feature-Instanz gefunden.

@@ -116,75 +109,19 @@ export const GraphicalEditorPage: React.FC = ({ } return ( -
- {/* UDB Sidebar */} - {udbOpen ? ( -
-
- - Daten - - -
- -
- ) : ( - - )} - - {/* FlowEditor */} -
- -
+
+
); }; diff --git a/src/pages/views/graphicalEditor/GraphicalEditorTemplatesPage.tsx b/src/pages/views/graphicalEditor/GraphicalEditorTemplatesPage.tsx index 64431b8..178695a 100644 --- a/src/pages/views/graphicalEditor/GraphicalEditorTemplatesPage.tsx +++ b/src/pages/views/graphicalEditor/GraphicalEditorTemplatesPage.tsx @@ -8,7 +8,8 @@ import React, { useState, useCallback, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { FaCopy, FaSync, FaShareAlt } from 'react-icons/fa'; +import { FaCopy, FaSync, FaShareAlt, FaPen } from 'react-icons/fa'; +import { usePrompt } from '../../../hooks/usePrompt'; import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator'; import { useInstanceId } from '../../../hooks/useCurrentInstance'; import { useApiRequest } from '../../../hooks/useApi'; @@ -17,6 +18,7 @@ import { copyTemplate, shareTemplate, deleteWorkflow, + updateWorkflow, type AutoWorkflowTemplate, type AutoTemplateScope, } from '../../../api/workflowApi'; @@ -50,6 +52,7 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { const { request } = useApiRequest(); const navigate = useNavigate(); const { showSuccess, showError } = useToast(); + const { prompt: promptInput, PromptDialog } = usePrompt(); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); @@ -117,18 +120,15 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { ); const handleShare = useCallback( - async (row: AutoWorkflowTemplate) => { + async (row: AutoWorkflowTemplate, targetScope: AutoTemplateScope) => { if (!instanceId) return; - const currentScope = row.templateScope || 'user'; - const nextScope: AutoTemplateScope = - currentScope === 'user' ? 'instance' : currentScope === 'instance' ? 'mandate' : 'mandate'; setSharingId(row.id); try { - await shareTemplate(request, instanceId, row.id, nextScope); - showSuccess(`Vorlage freigegeben (Scope: ${SCOPE_LABELS[nextScope]})`); + await shareTemplate(request, instanceId, row.id, targetScope); + showSuccess(`Scope geändert: ${SCOPE_LABELS[targetScope]}`); await load(); } catch (e: any) { - showError(`Fehler: ${e?.message || 'Freigabe fehlgeschlagen'}`); + showError(`Fehler: ${e?.message || 'Scope-Änderung fehlgeschlagen'}`); } finally { setSharingId(null); } @@ -136,6 +136,28 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { [instanceId, request, showSuccess, showError, load] ); + const [scopeMenuId, setScopeMenuId] = useState(null); + + const handleRename = useCallback( + async (row: AutoWorkflowTemplate) => { + if (!instanceId) return; + const newLabel = await promptInput('Neuer Name:', { + title: 'Vorlage umbenennen', + defaultValue: row.label, + placeholder: 'Vorlagen-Name', + }); + if (!newLabel || newLabel.trim() === row.label) return; + try { + await updateWorkflow(request, instanceId, row.id, { label: newLabel.trim() }); + showSuccess('Vorlage umbenannt'); + await load(); + } catch (e: any) { + showError(`Fehler: ${e?.message || 'Umbenennen fehlgeschlagen'}`); + } + }, + [instanceId, request, promptInput, showSuccess, showError, load] + ); + const handleEdit = useCallback( (row: AutoWorkflowTemplate) => { if (!mandateId || !instanceId) return; @@ -243,6 +265,13 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { }, ]} customActions={[ + { + id: 'rename', + icon: , + title: 'Umbenennen', + onClick: (row) => handleRename(row), + visible: (row) => (row.templateScope || 'user') !== 'system', + }, { id: 'copy', icon: , @@ -251,10 +280,10 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { loading: (row) => copyingId === row.id, }, { - id: 'share', + id: 'scope', icon: , - title: 'Scope erweitern (freigeben)', - onClick: (row) => handleShare(row), + title: 'Scope ändern', + onClick: (row) => setScopeMenuId(scopeMenuId === row.id ? null : row.id), loading: (row) => sharingId === row.id, visible: (row) => (row.templateScope || 'user') !== 'system', }, @@ -264,6 +293,59 @@ export const GraphicalEditorTemplatesPage: React.FC = () => { emptyMessage="Keine Vorlagen gefunden. Erstelle eine Vorlage aus einem bestehenden Workflow." />
+ + {/* Scope change dropdown overlay */} + {scopeMenuId && (() => { + const tpl = templates.find(t => t.id === scopeMenuId); + if (!tpl) return null; + const currentScope = (tpl.templateScope || 'user') as AutoTemplateScope; + const scopes: AutoTemplateScope[] = ['user', 'instance', 'mandate']; + return ( +
setScopeMenuId(null)} + > +
e.stopPropagation()} + > +

Scope ändern

+

+ Aktuell: {SCOPE_LABELS[currentScope]} +

+
+ {scopes.map(s => ( + + ))} +
+ +
+
+ ); + })()} + +
); }; diff --git a/src/pages/views/graphicalEditor/GraphicalEditorWorkflowsPage.tsx b/src/pages/views/graphicalEditor/GraphicalEditorWorkflowsPage.tsx index c34f3cc..58d6ef1 100644 --- a/src/pages/views/graphicalEditor/GraphicalEditorWorkflowsPage.tsx +++ b/src/pages/views/graphicalEditor/GraphicalEditorWorkflowsPage.tsx @@ -8,7 +8,8 @@ import React, { useState, useCallback, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import { FaPlay, FaSync, FaCheck, FaBan } from 'react-icons/fa'; +import { FaPlay, FaSync, FaCheck, FaBan, FaPen } from 'react-icons/fa'; +import { usePrompt } from '../../../hooks/usePrompt'; import { FormGeneratorTable, type ColumnConfig } from '../../../components/FormGenerator'; import { useInstanceId } from '../../../hooks/useCurrentInstance'; import { useApiRequest } from '../../../hooks/useApi'; @@ -42,6 +43,7 @@ export const GraphicalEditorWorkflowsPage: React.FC = () => { const { request } = useApiRequest(); const navigate = useNavigate(); const { showSuccess, showError } = useToast(); + const { prompt: promptInput, PromptDialog } = usePrompt(); const [workflows, setWorkflows] = useState([]); const [loading, setLoading] = useState(true); @@ -123,6 +125,26 @@ export const GraphicalEditorWorkflowsPage: React.FC = () => { [instanceId, request, showSuccess, showError, load] ); + const handleRename = useCallback( + async (row: Automation2Workflow) => { + if (!instanceId) return; + const newLabel = await promptInput('Neuer Name:', { + title: 'Workflow umbenennen', + defaultValue: row.label, + placeholder: 'Workflow-Name', + }); + if (!newLabel || newLabel.trim() === row.label) return; + try { + await updateWorkflow(request, instanceId, row.id, { label: newLabel.trim() }); + showSuccess('Workflow umbenannt'); + await load(); + } catch (e: any) { + showError(`Fehler: ${e?.message || 'Umbenennen fehlgeschlagen'}`); + } + }, + [instanceId, request, promptInput, showSuccess, showError, load] + ); + const handleExecute = useCallback( async (row: Automation2Workflow) => { if (!instanceId) return; @@ -282,6 +304,12 @@ export const GraphicalEditorWorkflowsPage: React.FC = () => { }, ]} customActions={[ + { + id: 'rename', + icon: , + title: 'Umbenennen', + onClick: (row) => handleRename(row), + }, { id: 'activate', icon: , @@ -312,6 +340,7 @@ export const GraphicalEditorWorkflowsPage: React.FC = () => { emptyMessage="Keine Workflows gefunden. Erstelle einen im Editor." />
+
); }; diff --git a/src/styles/buttons.css b/src/styles/buttons.css index 7b5556c..5021487 100644 --- a/src/styles/buttons.css +++ b/src/styles/buttons.css @@ -11,39 +11,39 @@ --button-secondary-bg-disabled: var(--color-gray-disabled); --button-secondary-text: var(--color-text); - --button-danger-bg: #dc3545; - --button-danger-bg-hover: #c82333; - --button-danger-bg-disabled: #dc3545; + --button-danger-bg: var(--color-red, #C53030); + --button-danger-bg-hover: #9B2C2C; + --button-danger-bg-disabled: rgba(197, 48, 48, 0.4); --button-danger-text: white; - --button-success-bg: #28a745; - --button-success-bg-hover: #218838; - --button-success-bg-disabled: #28a745; + --button-success-bg: var(--color-success, #38A169); + --button-success-bg-hover: #2F855A; + --button-success-bg-disabled: rgba(56, 161, 105, 0.4); --button-success-text: white; - --button-warning-bg: #ffc107; - --button-warning-bg-hover: #e0a800; - --button-warning-bg-disabled: #ffc107; - --button-warning-text: #212529; + --button-warning-bg: #D69E2E; + --button-warning-bg-hover: #B7791F; + --button-warning-bg-disabled: rgba(214, 158, 46, 0.4); + --button-warning-text: white; /* Button Sizes */ - --button-sm-padding: 8px 12px; + --button-sm-padding: 6px 14px; --button-sm-font-size: 12px; --button-sm-icon-size: 14px; - --button-md-padding: 10px 20px; + --button-md-padding: 8px 18px; --button-md-font-size: 14px; --button-md-icon-size: 16px; - --button-lg-padding: 12px 24px; - --button-lg-font-size: 16px; + --button-lg-padding: 10px 22px; + --button-lg-font-size: 15px; --button-lg-icon-size: 18px; /* Button Border Radius */ - --button-border-radius: 30px; + --button-border-radius: 6px; /* Button Transitions */ - --button-transition: all 0.2s ease; + --button-transition: all 0.15s ease; } /* Base Button Styles */ @@ -66,17 +66,17 @@ } .button:focus { - box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb), 0.3); + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.25); } .button:disabled { cursor: not-allowed; - opacity: 0.6; + opacity: 0.5; } .button.loading { cursor: not-allowed; - opacity: 0.7; + opacity: 0.65; } .button.loading .buttonIcon { @@ -104,7 +104,7 @@ } .buttonSecondary:hover:not(:disabled) { - background: var(--color-secondary-hover); + background: var(--color-gray); color: white; } @@ -211,17 +211,17 @@ /* Responsive Design */ @media (max-width: 768px) { .buttonSm { - padding: 4px 8px; + padding: 4px 10px; font-size: 11px; } .buttonMd { - padding: 8px 16px; + padding: 6px 14px; font-size: 13px; } .buttonLg { - padding: 10px 20px; - font-size: 15px; + padding: 8px 18px; + font-size: 14px; } -} \ No newline at end of file +} diff --git a/src/styles/pages.module.css b/src/styles/pages.module.css index 876be42..90fd4cd 100644 --- a/src/styles/pages.module.css +++ b/src/styles/pages.module.css @@ -52,10 +52,10 @@ .pageSubtitle { font-size: 1rem; font-weight: 600; - color: var(--color-secondary); + color: var(--color-text-secondary, #4A5568); margin: 0.5rem 0 0 0; font-family: var(--font-family); - letter-spacing: 0.5px; + letter-spacing: 0.3px; display: flex; align-items: center; gap: 0.5rem; @@ -230,7 +230,7 @@ /* Horizontal divider lines */ .horizontalDivider { width: calc(100% + 60px); - background-color: var(--color-primary); + background-color: var(--color-border, #E2E8F0); height: 1px; margin-left: -25px; margin-bottom: 0; @@ -465,7 +465,7 @@ } .chatHistoryNewChatButton { - border-radius: 25px !important; + border-radius: var(--button-border-radius, 6px) !important; } .chatHistoryEmpty { @@ -532,14 +532,14 @@ resize: none !important; flex: 1; box-sizing: border-box; - border-radius: 25px !important; - border: 1px solid var(--color-primary) !important; + border-radius: 8px !important; + border: 1px solid var(--color-border, #E2E8F0) !important; box-shadow: none !important; } .textAreaFixed :global(textarea:focus) { border: 1px solid var(--color-secondary) !important; - box-shadow: 0 0 8px rgba(0, 0, 0, 0.15) !important; + box-shadow: 0 0 0 2px rgba(var(--color-secondary-rgb, 74, 111, 165), 0.15) !important; outline: none !important; } diff --git a/src/styles/themes/light.css b/src/styles/themes/light.css index 80deceb..5ce307b 100644 --- a/src/styles/themes/light.css +++ b/src/styles/themes/light.css @@ -1,47 +1,47 @@ :root { - /* Original color definitions */ --color-bg: #F8F9FA; - --color-surface: #EFEDE5; - --color-text: #3A3A3A; + --color-surface: #EEF0F2; + --color-text: #2D3748; - --color-primary: #C7C5B2; - --color-primary-hover: #D9D7C6; - --color-primary-disabled: #E3E2D8; + --color-primary: #4A6FA5; + --color-primary-hover: #3D5D8A; + --color-primary-disabled: rgba(74, 111, 165, 0.3); - --color-secondary: #F25843; - --color-secondary-hover: #FF6A55; - --color-secondary-disabled: #F5B0A4; + --color-secondary: #4A6FA5; + --color-secondary-hover: #3D5D8A; + --color-secondary-disabled: rgba(74, 111, 165, 0.35); + --color-secondary-rgb: 74, 111, 165; - --color-red: #dc3545; - --color-red-hover: #f5c6cb; - --color-red-disabled: #f8d7da; + --color-red: #C53030; + --color-red-hover: #E2B6B6; + --color-red-disabled: #F5D5D5; - --color-secondary-red: #B94A55; - --color-secondary-red-hover: #D46872; - --color-secondary-red-disabled: #E8B7BA; + --color-secondary-red: #C53030; + --color-secondary-red-hover: #9B2C2C; + --color-secondary-red-disabled: #E2B6B6; - --color-gray: #6F7373; - --color-gray-hover: #565A5A; - --color-gray-disabled: #B7BBBA; + --color-gray: #718096; + --color-gray-hover: #4A5568; + --color-gray-disabled: #CBD5E0; - --color-medium-gray: #E0DDD3; - --color-medium-gray-hover: #D1CEC5; - --color-medium-gray-disabled: #E0DDD380; + --color-medium-gray: #E2E8F0; + --color-medium-gray-hover: #CBD5E0; + --color-medium-gray-disabled: rgba(226, 232, 240, 0.5); - --color-highlight-gray: #F5F3ED; - --color-highlight-gray-hover: #E6E3DC; - --color-highlight-gray-disabled: #F5F3ED80; + --color-highlight-gray: #F7FAFC; + --color-highlight-gray-hover: #EDF2F7; + --color-highlight-gray-disabled: rgba(247, 250, 252, 0.5); - --color-success: #10b981; - --color-success-hover: #059669; - --color-success-disabled: #a7f3d0; + --color-success: #38A169; + --color-success-hover: #2F855A; + --color-success-disabled: #C6F6D5; - --color-text-primary: #3A3A3A; /*to be deleted*/ + --color-text-primary: #2D3748; --font-family: "DM Sans", sans-serif; - --object-radius-large: 30px; - --object-radius-medium: 15px; - --object-radius-small: 5px; + --object-radius-large: 10px; + --object-radius-medium: 8px; + --object-radius-small: 4px; } /* ============================================== */ @@ -50,40 +50,40 @@ :root, .light-theme { /* Background colors */ --bg-primary: #ffffff; - --bg-secondary: #F8F9FA; - --bg-dark: #f5f5f5; + --bg-secondary: #F7FAFC; + --bg-dark: #EDF2F7; /* Surface colors */ - --surface-color: #f8f9fa; - --surface-dark: #f0f0f0; + --surface-color: #F7FAFC; + --surface-dark: #EDF2F7; /* Text colors */ - --text-primary: #1a1a1a; - --text-secondary: #666666; - --text-tertiary: #888888; - --text-primary-dark: #1a1a1a; - --text-secondary-dark: #666666; - --text-tertiary-dark: #888888; + --text-primary: #1A202C; + --text-secondary: #4A5568; + --text-tertiary: #718096; + --text-primary-dark: #1A202C; + --text-secondary-dark: #4A5568; + --text-tertiary-dark: #718096; /* Border colors */ - --border-color: #e0e0e0; - --border-dark: #d0d0d0; + --border-color: #E2E8F0; + --border-dark: #CBD5E0; /* Primary accent color */ - --primary-color: #F25843; - --primary-color-dark: #D94A37; - --primary-color-light: rgba(242, 88, 67, 0.2); - --primary-light: rgba(242, 88, 67, 0.12); - --primary-dark-bg: rgba(242, 88, 67, 0.08); + --primary-color: #4A6FA5; + --primary-color-dark: #3D5D8A; + --primary-color-light: rgba(74, 111, 165, 0.15); + --primary-light: rgba(74, 111, 165, 0.1); + --primary-dark-bg: rgba(74, 111, 165, 0.06); /* Hover backgrounds */ - --hover-bg: rgba(0, 0, 0, 0.04); - --hover-bg-dark: rgba(0, 0, 0, 0.06); + --hover-bg: rgba(0, 0, 0, 0.03); + --hover-bg-dark: rgba(0, 0, 0, 0.05); /* Error color */ - --error-color: #dc2626; + --error-color: #C53030; - /* Legacy / inline-style aliases (override :root beige --color-primary) */ + /* Legacy aliases */ --color-primary: var(--primary-color); --color-primary-hover: var(--primary-color-dark); --color-primary-disabled: var(--primary-color-light); @@ -97,68 +97,68 @@ /* DARK THEME */ /* ============================================== */ .dark-theme { - --color-bg: #181818; - --color-surface: #1E1D1A; - --color-text: #E5E7EB; + --color-bg: #1A202C; + --color-surface: #2D3748; + --color-text: #E2E8F0; --color-primary: var(--primary-color); --color-primary-hover: var(--primary-color-dark); - --color-primary-disabled: rgba(242, 88, 67, 0.35); + --color-primary-disabled: rgba(74, 111, 165, 0.35); - --color-secondary: #F25843; - --color-secondary-hover: #FF715C; - --color-secondary-disabled: #6E3E36; + --color-secondary: #5A8AC5; + --color-secondary-hover: #4A7AB5; + --color-secondary-disabled: #2C4A6E; + --color-secondary-rgb: 90, 138, 197; - --color-red: #dc3545; - --color-red-hover: #f5c6cb; - --color-red-disabled: #f8d7da; + --color-red: #FC8181; + --color-red-hover: #FEB2B2; + --color-red-disabled: #742A2A; - --color-secondary-red: #D65D6A; - --color-secondary-red-hover: #E17683; - --color-secondary-red-disabled: #70363C; + --color-secondary-red: #FC8181; + --color-secondary-red-hover: #FEB2B2; + --color-secondary-red-disabled: #742A2A; - /* Readable neutrals on dark (was #181818 — same as bg, illegible) */ - --color-gray: #9ca3af; - --color-gray-hover: #d1d5db; - --color-gray-disabled: #57534e; + --color-gray: #A0AEC0; + --color-gray-hover: #CBD5E0; + --color-gray-disabled: #4A5568; /* Background colors */ - --bg-primary: #181818; - --bg-secondary: #1E1D1A; - --bg-dark: #0a0a0a; + --bg-primary: #1A202C; + --bg-secondary: #2D3748; + --bg-dark: #171923; /* Surface colors */ - --surface-color: #1E1D1A; - --surface-dark: #1a1a1a; + --surface-color: #2D3748; + --surface-dark: #1A202C; /* Text colors */ - --text-primary: #E5E7EB; - --text-secondary: #C7C5B2; - --text-tertiary: #9CA3AF; - --text-primary-dark: #E5E7EB; - --text-secondary-dark: #C7C5B2; - --text-tertiary-dark: #9CA3AF; + --text-primary: #E2E8F0; + --text-secondary: #A0AEC0; + --text-tertiary: #718096; + --text-primary-dark: #E2E8F0; + --text-secondary-dark: #A0AEC0; + --text-tertiary-dark: #718096; /* Border colors */ - --border-color: rgba(199, 197, 178, 0.15); - --border-dark: rgba(199, 197, 178, 0.15); + --border-color: rgba(226, 232, 240, 0.12); + --border-dark: rgba(226, 232, 240, 0.12); /* Primary accent color */ - --primary-color: #F25843; - --primary-color-dark: #D94A37; - --primary-color-light: rgba(242, 88, 67, 0.3); - --primary-light: #FF9A8A; /* Lighter red for text on dark backgrounds */ - --primary-dark-bg: rgba(242, 88, 67, 0.15); /* Semi-transparent red for backgrounds */ + --primary-color: #5A8AC5; + --primary-color-dark: #4A7AB5; + --primary-color-light: rgba(90, 138, 197, 0.3); + --primary-light: #7BA7D7; + --primary-dark-bg: rgba(90, 138, 197, 0.12); /* Hover backgrounds */ - --hover-bg: rgba(255, 255, 255, 0.06); - --hover-bg-dark: rgba(255, 255, 255, 0.06); + --hover-bg: rgba(255, 255, 255, 0.05); + --hover-bg-dark: rgba(255, 255, 255, 0.05); /* Error color */ - --error-color: #ef4444; + --error-color: #FC8181; --color-border: var(--border-color); - --bg-card: #252422; - --bg-input: #2c2926; - --bg-hover: rgba(255, 255, 255, 0.08); + --bg-card: #2D3748; + --bg-input: #2D3748; + --bg-hover: rgba(255, 255, 255, 0.06); } diff --git a/src/types/mandate.ts b/src/types/mandate.ts index d92a974..af66307 100644 --- a/src/types/mandate.ts +++ b/src/types/mandate.ts @@ -259,7 +259,9 @@ export const FEATURE_REGISTRY: Record = { views: [ { code: 'editor', label: { de: 'Editor', en: 'Editor' }, path: 'editor' }, { code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' }, + { code: 'templates', label: { de: 'Vorlagen', en: 'Templates' }, path: 'templates' }, { code: 'workflows-tasks', label: { de: 'Tasks', en: 'Tasks' }, path: 'workflows-tasks' }, + { code: 'dashboard', label: { de: 'Dashboard', en: 'Dashboard' }, path: 'dashboard' }, ] }, neutralization: {