From ff1caba925c0b8d2e9eb071492e6d151557cf168 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Wed, 4 Feb 2026 14:09:51 +0100 Subject: [PATCH] admin pages streamlined --- src/App.tsx | 32 +-- src/api/workflowApi.ts | 17 +- src/hooks/playground/useDashboardInputForm.ts | 8 +- src/hooks/playground/useWorkflowLifecycle.ts | 16 +- src/hooks/useUserMandates.ts | 10 +- src/hooks/useWorkflows.ts | 7 +- src/pages/FeatureView.tsx | 15 ++ src/pages/migrate/MigratePages.module.css | 223 ------------------ src/pages/migrate/PekPage.tsx | 39 --- src/pages/migrate/SpeechPage.tsx | 39 --- src/pages/migrate/index.ts | 2 - src/pages/workflows/AutomationsPage.tsx | 2 +- src/pages/workflows/PlaygroundPage.tsx | 7 +- src/types/mandate.ts | 19 ++ 14 files changed, 82 insertions(+), 354 deletions(-) delete mode 100644 src/pages/migrate/MigratePages.module.css delete mode 100644 src/pages/migrate/PekPage.tsx delete mode 100644 src/pages/migrate/SpeechPage.tsx delete mode 100644 src/pages/migrate/index.ts diff --git a/src/App.tsx b/src/App.tsx index 95e21b5..0a7591b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,15 +43,9 @@ import { GDPRPage } from './pages/GDPR'; import { FeatureViewPage } from './pages/FeatureView'; import { AccessManagementHub, AdminMandatesPage, AdminUsersPage, AdminUserMandatesPage, AdminFeatureAccessPage, AdminInvitationsPage, AdminMandateRolesPage, AdminFeatureRolesPage, AdminFeatureInstanceUsersPage, AdminMandateRolePermissionsPage, AdminUserAccessOverviewPage } from './pages/admin'; -// Workflow Pages (global) -import { PlaygroundPage, WorkflowsPage, AutomationsPage, AutomationTemplatesPage } from './pages/workflows'; - // Basedata Pages (global) import { PromptsPage, FilesPage, ConnectionsPage } from './pages/basedata'; -// Migrate Pages (temporary - to be migrated to feature instances) -import { PekPage, SpeechPage } from './pages/migrate'; - function App() { // Load saved theme preference and set app name on app mount useEffect(() => { @@ -107,16 +101,6 @@ function App() { } /> } /> - {/* ============================================== */} - {/* WORKFLOWS ROUTES (global) */} - {/* ============================================== */} - - } /> - } /> - } /> - } /> - - {/* ============================================== */} {/* BASISDATEN ROUTES (global) */} {/* ============================================== */} @@ -126,13 +110,6 @@ function App() { } /> - {/* ============================================== */} - {/* MIGRATE TO FEATURES (temporary) */} - {/* ============================================== */} - } /> - } /> - } /> - {/* ============================================== */} {/* FEATURE-INSTANZ ROUTES */} {/* /mandates/:mandateId/:featureCode/:instanceId */} @@ -159,6 +136,15 @@ function App() { } /> } /> + {/* Chat Playground Feature Views */} + } /> + } /> + + {/* Automation Feature Views */} + } /> + } /> + } /> + {/* Catch-all für unbekannte Sub-Pfade */} } /> diff --git a/src/api/workflowApi.ts b/src/api/workflowApi.ts index 2e813f5..2d5110f 100644 --- a/src/api/workflowApi.ts +++ b/src/api/workflowApi.ts @@ -231,17 +231,18 @@ export async function fetchWorkflowLogs( /** * Fetch unified chat data (messages, logs, stats, documents) - * Endpoint: GET /api/chat/playground/{workflowId}/chatData + * Endpoint: GET /api/chatplayground/{instanceId}/{workflowId}/chatData * Query params: afterTimestamp (optional) - fetch only data created after this time */ export async function fetchChatData( request: ApiRequestFunction, + instanceId: string, workflowId: string, afterTimestamp?: number ): Promise { const params = afterTimestamp ? { afterTimestamp: afterTimestamp.toString() } : undefined; const requestConfig = { - url: `/api/chat/playground/${workflowId}/chatData`, + url: `/api/chatplayground/${instanceId}/${workflowId}/chatData`, method: 'get' as const, params }; @@ -314,11 +315,12 @@ export async function fetchChatData( /** * Start a new workflow or continue an existing one - * Endpoint: POST /api/chat/playground/start - * Query params: workflowId (optional), workflowMode (default: "Actionplan") + * Endpoint: POST /api/chatplayground/{instanceId}/start + * Query params: workflowId (optional), workflowMode (default: "Dynamic") */ export async function startWorkflowApi( request: ApiRequestFunction, + instanceId: string, workflowData: StartWorkflowRequest, options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' } ): Promise { @@ -345,7 +347,7 @@ export async function startWorkflowApi( }; const requestConfig = { - url: '/api/chat/playground/start', + url: `/api/chatplayground/${instanceId}/start`, method: 'post' as const, data: requestBody, params: params // Always include workflowMode @@ -368,14 +370,15 @@ export async function startWorkflowApi( /** * Stop a running workflow - * Endpoint: POST /api/chat/playground/{workflowId}/stop + * Endpoint: POST /api/chatplayground/{instanceId}/{workflowId}/stop */ export async function stopWorkflowApi( request: ApiRequestFunction, + instanceId: string, workflowId: string ): Promise { await request({ - url: `/api/chat/playground/${workflowId}/stop`, + url: `/api/chatplayground/${instanceId}/${workflowId}/stop`, method: 'post' }); } diff --git a/src/hooks/playground/useDashboardInputForm.ts b/src/hooks/playground/useDashboardInputForm.ts index 096c85b..84517fa 100644 --- a/src/hooks/playground/useDashboardInputForm.ts +++ b/src/hooks/playground/useDashboardInputForm.ts @@ -23,7 +23,7 @@ export interface WorkflowFile { source?: 'user_uploaded' | 'ai_created'; } -export function useDashboardInputForm() { +export function useDashboardInputForm(instanceId: string) { const [inputValue, setInputValue] = useState(''); const [pendingFiles, setPendingFiles] = useState([]); const [isFileAttachmentPopupOpen, setIsFileAttachmentPopupOpen] = useState(false); @@ -54,7 +54,7 @@ export function useDashboardInputForm() { resetWorkflow, selectWorkflow, setWorkflowStatusOptimistic - } = useWorkflowLifecycle(); + } = useWorkflowLifecycle(instanceId); // Dashboard log tree hook const { @@ -824,7 +824,7 @@ export function useDashboardInputForm() { }; } -export function createDashboardHook() { - return () => useDashboardInputForm(); +export function createDashboardHook(instanceId: string) { + return () => useDashboardInputForm(instanceId); } diff --git a/src/hooks/playground/useWorkflowLifecycle.ts b/src/hooks/playground/useWorkflowLifecycle.ts index bc2dbc5..c06b601 100644 --- a/src/hooks/playground/useWorkflowLifecycle.ts +++ b/src/hooks/playground/useWorkflowLifecycle.ts @@ -18,7 +18,7 @@ interface UnifiedChatDataItem { createdAt: number; } -export function useWorkflowLifecycle() { +export function useWorkflowLifecycle(instanceId: string) { const [workflowId, setWorkflowId] = useState(null); const [workflowStatus, setWorkflowStatus] = useState('idle'); const [currentRound, setCurrentRound] = useState(undefined); @@ -341,7 +341,7 @@ export function useWorkflowLifecycle() { } // Fetch unified chat data - const chatData = await fetchChatData(request, id, afterTimestamp); + const chatData = await fetchChatData(request, instanceId, id, afterTimestamp); console.log('📊 Processed chat data:', { messagesCount: chatData.messages?.length || 0, @@ -419,7 +419,7 @@ export function useWorkflowLifecycle() { // Reset lastRenderedTimestamp to fetch all historical data lastRenderedTimestampRef.current = null; try { - const chatData = await fetchChatData(request, id, undefined); + const chatData = await fetchChatData(request, instanceId, id, undefined); console.log('📥 loadWorkflowData: Fetched unified chat data:', { messagesCount: chatData.messages?.length || 0, logsCount: chatData.logs?.length || 0 @@ -514,7 +514,7 @@ export function useWorkflowLifecycle() { options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' } ) => { try { - const result = await startWorkflow(workflowData, options); + const result = await startWorkflow(instanceId, workflowData, options); if (result.success && result.data) { const workflow = result.data as Workflow; @@ -529,7 +529,7 @@ export function useWorkflowLifecycle() { } catch (error: any) { return { success: false, error: error.message || 'Failed to start workflow' }; } - }, [startWorkflow, updateWorkflowStatus]); + }, [instanceId, startWorkflow, updateWorkflowStatus]); const handleStopWorkflow = useCallback(async () => { if (!workflowId) { @@ -537,7 +537,7 @@ export function useWorkflowLifecycle() { } try { - const result = await stopWorkflow(workflowId); + const result = await stopWorkflow(instanceId, workflowId); if (result.success) { updateWorkflowStatus('stopped'); @@ -548,7 +548,7 @@ export function useWorkflowLifecycle() { } catch (error: any) { return { success: false, error: error.message || 'Failed to stop workflow' }; } - }, [workflowId, stopWorkflow, updateWorkflowStatus]); + }, [instanceId, workflowId, stopWorkflow, updateWorkflowStatus]); const resetWorkflow = useCallback(() => { setWorkflowId(null); @@ -597,7 +597,7 @@ export function useWorkflowLifecycle() { // Always fetch unified chat data to get all messages and logs (regardless of status) // This ensures completed workflows also show their logs try { - const chatData = await fetchChatData(request, workflowIdToSelect, undefined); + const chatData = await fetchChatData(request, instanceId, workflowIdToSelect, undefined); console.log('📥 selectWorkflow: Fetched unified chat data:', { messagesCount: chatData.messages?.length || 0, logsCount: chatData.logs?.length || 0, diff --git a/src/hooks/useUserMandates.ts b/src/hooks/useUserMandates.ts index 61758dd..8c4ec4f 100644 --- a/src/hooks/useUserMandates.ts +++ b/src/hooks/useUserMandates.ts @@ -226,7 +226,7 @@ export function useUserMandates() { }, []); /** - * Fetch all available roles (global and mandate-specific) + * Fetch all available roles (global and mandate-specific, excluding feature-instance roles) */ const fetchRoles = useCallback(async (mandateId?: string): Promise => { try { @@ -238,13 +238,15 @@ export function useUserMandates() { roles = response.data; } - // Filter to global roles and roles for this mandate + // Filter to global roles and mandate-specific roles only + // Exclude feature-instance roles (they have featureInstanceId set) if (mandateId) { return roles.filter(r => - !r.mandateId || r.mandateId === mandateId + !r.featureInstanceId && (!r.mandateId || r.mandateId === mandateId) ); } - return roles; + // Without mandateId, return only global roles (no mandateId and no featureInstanceId) + return roles.filter(r => !r.mandateId && !r.featureInstanceId); } catch (err: any) { console.error('Error fetching roles:', err); return []; diff --git a/src/hooks/useWorkflows.ts b/src/hooks/useWorkflows.ts index 942d1e5..79c12cc 100644 --- a/src/hooks/useWorkflows.ts +++ b/src/hooks/useWorkflows.ts @@ -432,6 +432,7 @@ export function useWorkflowOperations() { }; const startWorkflow = async ( + instanceId: string, workflowData: StartWorkflowRequest, options?: { workflowId?: string; workflowMode?: 'Dynamic' | 'Automation' } ) => { @@ -439,7 +440,7 @@ export function useWorkflowOperations() { setStartingWorkflow(true); try { - const response = await startWorkflowApi(request, workflowData, options); + const response = await startWorkflowApi(request, instanceId, workflowData, options); return { success: true, data: response }; } catch (error: any) { const errorMessage = error.message || 'Failed to start workflow'; @@ -450,12 +451,12 @@ export function useWorkflowOperations() { } }; - const stopWorkflow = async (workflowId: string) => { + const stopWorkflow = async (instanceId: string, workflowId: string) => { setStopError(null); setStoppingWorkflows(prev => new Set(prev).add(workflowId)); try { - await stopWorkflowApi(request, workflowId); + await stopWorkflowApi(request, instanceId, workflowId); return { success: true }; } catch (error: any) { const errorMessage = error.message || 'Failed to stop workflow'; diff --git a/src/pages/FeatureView.tsx b/src/pages/FeatureView.tsx index d49700c..d1ce616 100644 --- a/src/pages/FeatureView.tsx +++ b/src/pages/FeatureView.tsx @@ -25,6 +25,12 @@ import { ChatbotConversationsView } from './views/chatbot/ChatbotConversationsVi // RealEstate Views import { RealEstatePekView, RealEstateProjectsView, RealEstateParcelsView, RealEstateInstanceRolesPlaceholder } from './views/realestate'; +// Chat Playground Views (reusing existing workflow pages) +import { PlaygroundPage, WorkflowsPage } from './workflows'; + +// Automation Views (reusing existing workflow pages) +import { AutomationsPage, AutomationTemplatesPage } from './workflows'; + import styles from './FeatureView.module.css'; // ============================================================================= @@ -103,6 +109,15 @@ const VIEW_COMPONENTS: Record> = { parcels: RealEstateParcelsView, 'instance-roles': RealEstateInstanceRolesPlaceholder, }, + chatplayground: { + playground: PlaygroundPage, + workflows: WorkflowsPage, + }, + automation: { + definitions: AutomationsPage, + templates: AutomationTemplatesPage, + logs: () => , + }, }; // ============================================================================= diff --git a/src/pages/migrate/MigratePages.module.css b/src/pages/migrate/MigratePages.module.css deleted file mode 100644 index 95b409f..0000000 --- a/src/pages/migrate/MigratePages.module.css +++ /dev/null @@ -1,223 +0,0 @@ -/* MigratePages.module.css - Styles for migrate-to-feature pages */ - -.page { - padding: 2rem; - max-width: 1200px; - margin: 0 auto; - height: calc(100vh - 4rem); - display: flex; - flex-direction: column; -} - -.header { - margin-bottom: 1.5rem; -} - -.header h1 { - font-size: 1.75rem; - font-weight: 600; - color: var(--color-text-primary, #1a1a2e); - margin: 0 0 0.5rem 0; -} - -.subtitle { - color: var(--color-text-secondary, #6b7280); - margin: 0; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.migrateTag { - display: inline-block; - padding: 0.25rem 0.5rem; - background: var(--color-warning-bg, #fef3c7); - color: var(--color-warning, #d97706); - font-size: 0.65rem; - font-weight: 700; - border-radius: 4px; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.content { - flex: 1; - display: flex; - align-items: center; - justify-content: center; -} - -/* Placeholder for migrate pages */ -.placeholder { - text-align: center; - padding: 3rem; - background: var(--color-surface, #ffffff); - border: 2px dashed var(--color-border, #e5e7eb); - border-radius: 12px; - max-width: 500px; -} - -.placeholderIcon { - font-size: 4rem; - margin-bottom: 1rem; -} - -.placeholder h2 { - margin: 0 0 1rem 0; - color: var(--color-text-primary, #1a1a2e); -} - -.placeholder p { - color: var(--color-text-secondary, #6b7280); - margin: 0 0 0.5rem 0; -} - -.hint { - font-size: 0.875rem; - color: var(--color-text-tertiary, #9ca3af); - margin-top: 1rem !important; -} - -/* Chat container for ChatbotPage */ -.chatContainer { - flex: 1; - display: flex; - flex-direction: column; - background: var(--color-surface, #ffffff); - border: 1px solid var(--color-border, #e5e7eb); - border-radius: 8px; - overflow: hidden; -} - -.messagesArea { - flex: 1; - overflow-y: auto; - padding: 1rem; - display: flex; - flex-direction: column; - gap: 1rem; -} - -.emptyChat { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - color: var(--color-text-secondary, #6b7280); -} - -.message { - max-width: 70%; - padding: 0.75rem 1rem; - border-radius: 12px; -} - -.message.user { - align-self: flex-end; - background: var(--color-primary, #4f46e5); - color: white; - border-bottom-right-radius: 4px; -} - -.message.assistant { - align-self: flex-start; - background: var(--color-surface-secondary, #f3f4f6); - color: var(--color-text-primary, #1a1a2e); - border-bottom-left-radius: 4px; -} - -.message.system { - align-self: center; - background: var(--color-warning-bg, #fef3c7); - color: var(--color-warning, #d97706); - font-size: 0.875rem; -} - -.messageContent { - word-wrap: break-word; -} - -.messageTime { - font-size: 0.7rem; - opacity: 0.7; - margin-top: 0.25rem; -} - -/* Typing indicator */ -.typing { - display: flex; - gap: 4px; - padding: 0.5rem 0; -} - -.typing span { - width: 8px; - height: 8px; - background: var(--color-text-secondary, #6b7280); - border-radius: 50%; - animation: typing 1s infinite; -} - -.typing span:nth-child(2) { - animation-delay: 0.2s; -} - -.typing span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes typing { - 0%, 100% { - opacity: 0.3; - transform: scale(0.8); - } - 50% { - opacity: 1; - transform: scale(1); - } -} - -/* Input area */ -.inputArea { - display: flex; - gap: 0.5rem; - padding: 1rem; - border-top: 1px solid var(--color-border, #e5e7eb); - background: var(--color-surface-secondary, #f9fafb); -} - -.chatInput { - flex: 1; - padding: 0.75rem 1rem; - border: 1px solid var(--color-border, #e5e7eb); - border-radius: 24px; - font-size: 0.875rem; - background: var(--color-surface, #ffffff); -} - -.chatInput:focus { - outline: none; - border-color: var(--color-primary, #4f46e5); - box-shadow: 0 0 0 3px var(--color-primary-light, rgba(79, 70, 229, 0.1)); -} - -.sendButton { - padding: 0.75rem 1.5rem; - background: var(--color-primary, #4f46e5); - color: white; - border: none; - border-radius: 24px; - font-size: 0.875rem; - font-weight: 500; - cursor: pointer; - transition: background 0.2s; -} - -.sendButton:hover:not(:disabled) { - background: var(--color-primary-dark, #4338ca); -} - -.sendButton:disabled { - opacity: 0.5; - cursor: not-allowed; -} diff --git a/src/pages/migrate/PekPage.tsx b/src/pages/migrate/PekPage.tsx deleted file mode 100644 index 797b8b8..0000000 --- a/src/pages/migrate/PekPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * PekPage - * - * PEK (Projekt-Entwicklungs-Koordination) page - temporary global page. - * TODO: Migrate to feature instance. - */ - -import React from 'react'; -import styles from './MigratePages.module.css'; - -export const PekPage: React.FC = () => { - return ( -
-
-

PEK

-

- MIGRATE TO FEATURE - Projekt-Entwicklungs-Koordination -

-
- -
-
-
📊
-

PEK-Modul

-

- Dieses Modul wird zu einer Feature-Instanz migriert. -

-

- Nach der Migration wird PEK als Feature pro Mandant verfügbar sein, - mit instanz-spezifischen Daten und Berechtigungen. -

-
-
-
- ); -}; - -export default PekPage; diff --git a/src/pages/migrate/SpeechPage.tsx b/src/pages/migrate/SpeechPage.tsx deleted file mode 100644 index 9bfda2c..0000000 --- a/src/pages/migrate/SpeechPage.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * SpeechPage - * - * Speech recognition and transcription page - temporary global page. - * TODO: Migrate to feature instance. - */ - -import React from 'react'; -import styles from './MigratePages.module.css'; - -export const SpeechPage: React.FC = () => { - return ( -
-
-

Speech

-

- MIGRATE TO FEATURE - Spracherkennung und Transkription -

-
- -
-
-
🎤
-

Speech-Modul

-

- Dieses Modul wird zu einer Feature-Instanz migriert. -

-

- Nach der Migration wird Speech als Feature pro Mandant verfügbar sein, - mit instanz-spezifischen Transkriptionen und Einstellungen. -

-
-
-
- ); -}; - -export default SpeechPage; diff --git a/src/pages/migrate/index.ts b/src/pages/migrate/index.ts deleted file mode 100644 index c7ed0d3..0000000 --- a/src/pages/migrate/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { PekPage } from './PekPage'; -export { SpeechPage } from './SpeechPage'; diff --git a/src/pages/workflows/AutomationsPage.tsx b/src/pages/workflows/AutomationsPage.tsx index a0c4171..0d5e773 100644 --- a/src/pages/workflows/AutomationsPage.tsx +++ b/src/pages/workflows/AutomationsPage.tsx @@ -426,7 +426,7 @@ export const AutomationsPage: React.FC = () => { try { await request({ - url: `/api/chat/playground/${executionModal.workflowId}/stop`, + url: `/api/workflows/${executionModal.workflowId}/stop`, method: 'post', }); diff --git a/src/pages/workflows/PlaygroundPage.tsx b/src/pages/workflows/PlaygroundPage.tsx index 1598aea..ba20467 100644 --- a/src/pages/workflows/PlaygroundPage.tsx +++ b/src/pages/workflows/PlaygroundPage.tsx @@ -11,6 +11,7 @@ import { useSearchParams } from 'react-router-dom'; import { useDashboardInputForm } from '../../hooks/usePlayground'; import { useResizablePanels } from '../../hooks/useResizablePanels'; import { usePrompts } from '../../hooks/usePrompts'; +import { useCurrentInstance } from '../../hooks/useCurrentInstance'; import { FaComment, FaTasks, FaPaperPlane, FaStop, FaFile, FaPlus, FaMicrophone, FaSquare, FaFileAlt } from 'react-icons/fa'; import { useToast } from '../../contexts/ToastContext'; import { useVoiceLanguage, VoiceLanguageSelect, Messages } from '../../components/UiComponents'; @@ -23,8 +24,12 @@ export const PlaygroundPage: React.FC = () => { const [searchParams] = useSearchParams(); const urlWorkflowId = searchParams.get('workflowId'); + // Get feature instance context + const { instance } = useCurrentInstance(); + const instanceId = instance?.id || ''; + // Main hook for input form and data - const hookData = useDashboardInputForm(); + const hookData = useDashboardInputForm(instanceId); const { inputValue, onInputChange, diff --git a/src/types/mandate.ts b/src/types/mandate.ts index fee57e1..8c5fa5b 100644 --- a/src/types/mandate.ts +++ b/src/types/mandate.ts @@ -240,6 +240,25 @@ export const FEATURE_REGISTRY: Record = { { code: 'instance-roles', label: { de: 'Rollen & Rechte', en: 'Roles & Permissions' }, path: 'instance-roles', adminOnly: true }, ] }, + chatplayground: { + code: 'chatplayground', + label: { de: 'Chat Playground', en: 'Chat Playground' }, + icon: 'message', + views: [ + { code: 'playground', label: { de: 'Playground', en: 'Playground' }, path: 'playground' }, + { code: 'workflows', label: { de: 'Workflows', en: 'Workflows' }, path: 'workflows' }, + ] + }, + automation: { + code: 'automation', + label: { de: 'Automatisierung', en: 'Automation' }, + icon: 'settings', + views: [ + { code: 'definitions', label: { de: 'Definitionen', en: 'Definitions' }, path: 'definitions' }, + { code: 'templates', label: { de: 'Vorlagen', en: 'Templates' }, path: 'templates' }, + { code: 'logs', label: { de: 'Protokolle', en: 'Logs' }, path: 'logs' }, + ] + }, }; // =============================================================================