added functionality to see all users as a user

This commit is contained in:
idittrich-valueon 2025-06-19 19:09:26 +02:00
parent b827c3e00b
commit 8ca6f28bcd
12 changed files with 256 additions and 53 deletions

View file

@ -11,7 +11,7 @@ import { AuthProvider } from './auth/authProvider';
import { ProtectedRoute } from './auth/ProtectedRoute'; import { ProtectedRoute } from './auth/ProtectedRoute';
import Home from './pages/Home'; import Home from './pages/Home';
import Dateien from './pages/Dateien/Dateien'; import Dateien from './pages/Dateien/Dateien';
import TeamBereich from './pages/Mitglieder/TeamBereich'; import TeamBereich from './pages/TeamBereich/TeamBereich';
import Dashboard from './pages/Dashboard'; import Dashboard from './pages/Dashboard';
import Einstellungen from './pages/Einstellungen/Einstellungen'; import Einstellungen from './pages/Einstellungen/Einstellungen';
// Import the global light theme CSS variables as default // Import the global light theme CSS variables as default

View file

@ -7,11 +7,34 @@ const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
currentWorkflowId, currentWorkflowId,
workflowStatus, workflowStatus,
workflowCompleted, workflowCompleted,
onStartNewWorkflow onStartNewWorkflow,
handleRetry,
shouldShowRetryButton
}) => { }) => {
return ( return (
<AnimatePresence> <AnimatePresence>
{currentWorkflowId && !workflowCompleted && ( {currentWorkflowId && shouldShowRetryButton() && (
<motion.div
className={styles.workflow_status}
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<div className={styles.retry_container}>
<span className={styles.failed_message}>
Workflow failed.
</span>
<button
onClick={handleRetry}
className={styles.retry_button}
>
Nochmal versuchen
</button>
</div>
</motion.div>
)}
{currentWorkflowId && !workflowCompleted && !shouldShowRetryButton() && (
<motion.div <motion.div
className={styles.workflow_status} className={styles.workflow_status}
initial={{ opacity: 0, scale: 0.8 }} initial={{ opacity: 0, scale: 0.8 }}

View file

@ -328,20 +328,62 @@
.workflow_status { .workflow_status {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
width: 100%; align-items: center;
color: var(--color-secondary); padding: 10px;
margin: 5px 0;
} }
.workflow_status p { .workflow_status p {
margin: 0; margin: 0;
color: var(--color-secondary); font-size: 14px;
font-size: 13px; color: var(--color-gray);
font-style: italic;
font-family: var(--font-family); font-family: var(--font-family);
} }
.retry_container {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background-color: var(--color-red-disabled);
border: 1px solid var(--color-red);
border-radius: 12px;
}
.failed_message {
font-size: 14px;
color: var(--color-red);
font-family: var(--font-family);
font-weight: 500;
}
.retry_button {
background-color: var(--color-primary);
color: var(--color-bg);
border: none;
border-radius: 8px;
padding: 8px 16px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
font-family: var(--font-family);
transition: background-color 0.2s ease, transform 0.2s ease;
white-space: nowrap;
}
.retry_button:hover {
background-color: var(--color-primary-hover);
transform: translateY(-1px);
}
.retry_button:disabled {
background-color: var(--color-gray-disabled);
cursor: not-allowed;
transform: none;
opacity: 0.6;
}
.completion_message { .completion_message {
padding: 10px 12px; padding: 10px 12px;
background-color: var(--color-secondary-disabled); background-color: var(--color-secondary-disabled);

View file

@ -40,10 +40,12 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
handleFileAttach, handleFileAttach,
handleFileRemove, handleFileRemove,
handleFilesSelect, handleFilesSelect,
handleRetry,
// Workflow state // Workflow state
isWorkflowRunning, isWorkflowRunning,
isStoppingWorkflow isStoppingWorkflow,
shouldShowRetryButton
} = useChatLogic({ } = useChatLogic({
selectedPrompt, selectedPrompt,
onPromptUsed, onPromptUsed,
@ -73,6 +75,8 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
messagesLoading={messagesLoading} messagesLoading={messagesLoading}
onStartNewWorkflow={startNewWorkflow} onStartNewWorkflow={startNewWorkflow}
messagesEndRef={messagesEndRef} messagesEndRef={messagesEndRef}
handleRetry={handleRetry}
shouldShowRetryButton={shouldShowRetryButton}
/> />
<ChatInput <ChatInput
inputValue={inputValue} inputValue={inputValue}

View file

@ -15,7 +15,9 @@ const MessageList: React.FC<MessageListProps> = ({
messagesError, messagesError,
messagesLoading, messagesLoading,
onStartNewWorkflow, onStartNewWorkflow,
messagesEndRef messagesEndRef,
handleRetry,
shouldShowRetryButton
}) => { }) => {
return ( return (
<motion.div <motion.div
@ -65,6 +67,8 @@ const MessageList: React.FC<MessageListProps> = ({
workflowStatus={workflowStatus} workflowStatus={workflowStatus}
workflowCompleted={workflowCompleted} workflowCompleted={workflowCompleted}
onStartNewWorkflow={onStartNewWorkflow} onStartNewWorkflow={onStartNewWorkflow}
handleRetry={handleRetry}
shouldShowRetryButton={shouldShowRetryButton}
/> />
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />

View file

@ -229,6 +229,55 @@ export const useChatLogic = ({
const isStoppingWorkflow = currentWorkflowId ? stoppingWorkflows.has(currentWorkflowId) : false; const isStoppingWorkflow = currentWorkflowId ? stoppingWorkflows.has(currentWorkflowId) : false;
const handleRetry = async () => {
if (!currentWorkflowId || !messages.length) {
console.error('No workflow ID or messages available for retry');
return;
}
// Find the last user message to retry
const userMessages = messages.filter(msg => msg.role === 'user');
const lastUserMessage = userMessages[userMessages.length - 1];
if (!lastUserMessage) {
console.error('No user message found to retry');
return;
}
console.log('Retrying workflow with last user message:', lastUserMessage.content);
try {
// Extract file IDs if available from the message
const fileIds = lastUserMessage.fileIds || [];
// Start the workflow again with the same prompt and files
const result = await startWorkflow({
prompt: lastUserMessage.content,
listFileId: fileIds
}, currentWorkflowId);
if (result.success) {
console.log('Workflow retry started successfully');
// Reset workflow completion state to resume polling
setWorkflowCompleted(false);
} else {
console.error('Failed to retry workflow:', result.error);
}
} catch (error) {
console.error('Error retrying workflow:', error);
}
};
const shouldShowRetryButton = () => {
if (!workflowStatus) return false;
const statusLower = workflowStatus.status.toLowerCase();
return statusLower === 'error' ||
statusLower === 'failed' ||
statusLower === 'stopped' ||
statusLower === 'cancelled';
};
return { return {
// State // State
inputValue, inputValue,
@ -257,9 +306,11 @@ export const useChatLogic = ({
handleFileAttach, handleFileAttach,
handleFileRemove, handleFileRemove,
handleFilesSelect, handleFilesSelect,
handleRetry,
// Workflow state // Workflow state
isWorkflowRunning, isWorkflowRunning,
isStoppingWorkflow isStoppingWorkflow,
shouldShowRetryButton
}; };
}; };

View file

@ -62,6 +62,8 @@ export interface MessageListProps {
messagesLoading: boolean; messagesLoading: boolean;
onStartNewWorkflow: () => void; onStartNewWorkflow: () => void;
messagesEndRef: React.RefObject<HTMLDivElement | null>; messagesEndRef: React.RefObject<HTMLDivElement | null>;
handleRetry: () => Promise<void>;
shouldShowRetryButton: () => boolean;
} }
export interface WorkflowStatusDisplayProps { export interface WorkflowStatusDisplayProps {
@ -69,4 +71,6 @@ export interface WorkflowStatusDisplayProps {
workflowStatus: WorkflowStatus | null; workflowStatus: WorkflowStatus | null;
workflowCompleted: boolean; workflowCompleted: boolean;
onStartNewWorkflow: () => void; onStartNewWorkflow: () => void;
handleRetry: () => Promise<void>;
shouldShowRetryButton: () => boolean;
} }

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { FaArrowRight } from 'react-icons/fa'; import { FaArrowRight } from 'react-icons/fa';
import { AiOutlineDelete } from 'react-icons/ai'; import { AiOutlineDelete } from 'react-icons/ai';
import { motion } from 'framer-motion';
import { useWorkflowOperations, useWorkflowMessages, Workflow } from '../../../../hooks/useWorkflows'; import { useWorkflowOperations, useWorkflowMessages, Workflow } from '../../../../hooks/useWorkflows';
import styles from './DashboardChatHistoryItem.module.css'; import styles from './DashboardChatHistoryItem.module.css';
@ -50,16 +51,8 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status.toLowerCase()) { switch (status.toLowerCase()) {
case 'completed':
case 'finished':
case 'done':
return 'var(--color-secondary)';
case 'running':
case 'processing':
return 'var(--color-gray)';
case 'error': case 'error':
case 'failed': case 'failed':
return 'var(--color-red)';
case 'stopped': case 'stopped':
case 'cancelled': case 'cancelled':
return 'var(--color-red)'; return 'var(--color-red)';
@ -70,13 +63,6 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
const getStatusBackgroundColor = (status: string) => { const getStatusBackgroundColor = (status: string) => {
switch (status.toLowerCase()) { switch (status.toLowerCase()) {
case 'completed':
case 'finished':
case 'done':
return 'var(--color-secondary-disabled)';
case 'running':
case 'processing':
return 'var(--color-gray-disabled)';
case 'error': case 'error':
case 'failed': case 'failed':
case 'stopped': case 'stopped':
@ -87,6 +73,59 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
} }
}; };
const shouldShowStatus = (status: string) => {
const statusLower = status.toLowerCase();
return statusLower === 'error' ||
statusLower === 'failed' ||
statusLower === 'stopped' ||
statusLower === 'cancelled';
};
const isRunning = (status: string) => {
const statusLower = status.toLowerCase();
return statusLower === 'running' || statusLower === 'processing';
};
const renderStatusIndicator = () => {
if (isRunning(workflow.status)) {
return (
<motion.div
className={styles.loadingSpinner}
animate={{ rotate: 360 }}
transition={{
duration: 1,
repeat: Infinity,
ease: "linear"
}}
style={{
width: '16px',
height: '16px',
border: '2px solid var(--color-gray-disabled)',
borderTop: '2px solid var(--color-secondary)',
borderRadius: '50%',
display: 'inline-block'
}}
/>
);
}
if (shouldShowStatus(workflow.status)) {
return (
<span
className={styles.workflowStatus}
style={{
color: getStatusColor(workflow.status),
backgroundColor: getStatusBackgroundColor(workflow.status)
}}
>
{workflow.status.toUpperCase()}
</span>
);
}
return null;
};
const truncateMessage = (message: string, maxLength: number = 150) => { const truncateMessage = (message: string, maxLength: number = 150) => {
if (message.length <= maxLength) return message; if (message.length <= maxLength) return message;
return message.substring(0, maxLength) + '...'; return message.substring(0, maxLength) + '...';
@ -101,15 +140,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
Workflow {workflow.id.substring(0, 8)}... Workflow {workflow.id.substring(0, 8)}...
</h3> </h3>
<div className={styles.workflowMeta}> <div className={styles.workflowMeta}>
<span {renderStatusIndicator()}
className={styles.workflowStatus}
style={{
color: getStatusColor(workflow.status),
backgroundColor: getStatusBackgroundColor(workflow.status)
}}
>
{workflow.status.toUpperCase()}
</span>
{workflow.currentRound && ( {workflow.currentRound && (
<span className={styles.workflowRound}> <span className={styles.workflowRound}>
Round {workflow.currentRound} Round {workflow.currentRound}

View file

@ -5,7 +5,6 @@
flex-direction: column; flex-direction: column;
align-self: stretch; align-self: stretch;
border-radius: 30px; border-radius: 30px;
border: 1px solid var(--color-gray-disabled);
background: var(--color-bg); background: var(--color-bg);
position: relative; position: relative;
box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10); box-shadow: 0px 2px 6px 0px rgba(194, 194, 194, 0.10);

View file

@ -1,15 +0,0 @@
import styles from './TeamBereich.module.css'
import MitgliederItem from '../../components/Mitglieder/MitgliederItem';
import { IoPersonAddSharp } from "react-icons/io5";
import { useOrgUsers } from '../../hooks/useUsers';
function TeamBereich () {
return (
<h1>Team-Bereich</h1>
);
}
export default TeamBereich;

View file

@ -66,7 +66,7 @@
.membersList { .membersList {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: 0; margin-top: 30px;
width: 100%; width: 100%;
overflow-y: auto; /* Enable vertical scrolling */ overflow-y: auto; /* Enable vertical scrolling */
/* Space for the header line */ /* Space for the header line */

View file

@ -0,0 +1,60 @@
import styles from './TeamBereich.module.css'
import MitgliederItem from '../../components/Mitglieder/MitgliederItem';
import { IoPersonAddSharp } from "react-icons/io5";
import { useOrgUsers } from '../../hooks/useUsers';
function TeamBereich () {
const { users, loading, error, refetch } = useOrgUsers();
if (loading) {
return (
<div className={styles.mitgliederContainer}>
<div className={styles.header}>
<h1>Team-Bereich</h1>
</div>
<p>Lade Mitglieder...</p>
</div>
);
}
if (error) {
return (
<div className={styles.mitgliederContainer}>
<div className={styles.header}>
<h1>Team-Bereich</h1>
</div>
<p>Fehler beim Laden der Mitglieder: {error}</p>
</div>
);
}
return (
<div className={styles.mitgliederContainer}>
<div className={styles.header}>
<h1>Team-Bereich</h1>
<button className={styles.mitglieder_hinzufügen_button}>
<IoPersonAddSharp className={styles.add_icon} />
Mitglied hinzufügen
</button>
</div>
<div className={styles.horizontalLineLight}></div>
<ul className={styles.membersList}>
{users.map((user) => (
<MitgliederItem
key={user.id}
user={user}
refetchUsers={refetch}
totalUsers={users.length}
/>
))}
</ul>
{users.length === 0 && (
<p>Keine Mitglieder gefunden.</p>
)}
</div>
);
}
export default TeamBereich;