frontend_nyla/src/pages/views/commcoach/CommcoachDossierView.tsx
2026-03-02 21:24:28 +01:00

242 lines
9 KiB
TypeScript

/**
* CommCoach Dossier View
*
* Shows context detail: sessions timeline, tasks checklist, scores, insights.
*/
import React, { useState, useCallback, useEffect } from 'react';
import { useCommcoach } from '../../../hooks/useCommcoach';
import ReactMarkdown from 'react-markdown';
import styles from './CommcoachDossierView.module.css';
export const CommcoachDossierView: React.FC = () => {
const coach = useCommcoach();
const [newTaskTitle, setNewTaskTitle] = useState('');
const [activeTab, setActiveTab] = useState<'sessions' | 'tasks' | 'scores'>('tasks');
useEffect(() => {
if (!coach.selectedContextId && coach.contexts.length > 0) {
coach.selectContext(coach.contexts[0].id);
}
}, [coach.contexts, coach.selectedContextId, coach.selectContext]);
const handleAddTask = useCallback(async () => {
if (!newTaskTitle.trim()) return;
await coach.addTask(newTaskTitle);
setNewTaskTitle('');
}, [newTaskTitle, coach]);
if (coach.loadingContexts) {
return <div className={styles.empty}><p>Lade...</p></div>;
}
if (coach.contexts.length === 0) {
return (
<div className={styles.empty}>
<p>Noch keine Coaching-Themen vorhanden. Erstelle zuerst eines im Coaching-Tab.</p>
</div>
);
}
return (
<div className={styles.dossier}>
{/* Context Selector */}
<div className={styles.contextSelector}>
{coach.contexts.map(ctx => (
<button
key={ctx.id}
className={`${styles.contextChip} ${ctx.id === coach.selectedContextId ? styles.contextChipActive : ''}`}
onClick={() => coach.selectContext(ctx.id)}
>
{ctx.title}
</button>
))}
</div>
{!coach.selectedContextId ? (
<div className={styles.empty}><p>Waehle ein Coaching-Thema.</p></div>
) : (<>
{/* Context Header */}
<div className={styles.header}>
<div>
<h2 className={styles.title}>{coach.selectedContext?.title}</h2>
{coach.selectedContext?.description && (
<p className={styles.description}>{coach.selectedContext.description}</p>
)}
</div>
<div className={styles.headerActions}>
<button className={styles.btnArchive} onClick={() => coach.archiveContext(coach.selectedContextId!)}>
Archivieren
</button>
</div>
</div>
{/* Tab Navigation */}
<div className={styles.tabs}>
<button
className={`${styles.tab} ${activeTab === 'tasks' ? styles.tabActive : ''}`}
onClick={() => setActiveTab('tasks')}
>
Aufgaben ({coach.tasks.length})
</button>
<button
className={`${styles.tab} ${activeTab === 'sessions' ? styles.tabActive : ''}`}
onClick={() => setActiveTab('sessions')}
>
Sessions ({coach.sessions.length})
</button>
<button
className={`${styles.tab} ${activeTab === 'scores' ? styles.tabActive : ''}`}
onClick={() => setActiveTab('scores')}
>
Bewertungen ({coach.scores.length})
</button>
</div>
{/* Tasks Tab */}
{activeTab === 'tasks' && (
<div className={styles.tabContent}>
<div className={styles.addTaskRow}>
<input
className={styles.addTaskInput}
placeholder="Neue Aufgabe..."
value={newTaskTitle}
onChange={e => setNewTaskTitle(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAddTask()}
/>
<button className={styles.addTaskBtn} onClick={handleAddTask} disabled={!newTaskTitle.trim()}>
Hinzufuegen
</button>
</div>
{coach.tasks.length === 0 ? (
<div className={styles.emptyTab}>Noch keine Aufgaben. Der Coach schlaegt waehrend Sessions Aufgaben vor.</div>
) : (
<div className={styles.taskList}>
{coach.tasks.map(task => (
<div key={task.id} className={`${styles.taskItem} ${task.status === 'done' ? styles.taskDone : ''}`}>
<button
className={styles.taskCheck}
onClick={() => coach.toggleTaskStatus(task.id, task.status)}
>
{task.status === 'done' ? '\u2713' : '\u25CB'}
</button>
<div className={styles.taskContent}>
<div className={styles.taskTitle}>{task.title}</div>
{task.description && <div className={styles.taskDesc}>{task.description}</div>}
</div>
<div className={styles.taskMeta}>
<span className={`${styles.taskPriority} ${styles[`priority_${task.priority}`]}`}>
{task.priority}
</span>
</div>
<button className={styles.taskDelete} onClick={() => coach.removeTask(task.id)}>
x
</button>
</div>
))}
</div>
)}
</div>
)}
{/* Sessions Tab */}
{activeTab === 'sessions' && (
<div className={styles.tabContent}>
{coach.sessions.length === 0 ? (
<div className={styles.emptyTab}>Noch keine abgeschlossenen Sessions.</div>
) : (
<div className={styles.sessionTimeline}>
{coach.sessions.map(s => (
<div key={s.id} className={styles.sessionItem}>
<div className={styles.sessionItemHeader}>
<span className={`${styles.sessionStatus} ${styles[`status_${s.status}`]}`}>
{s.status === 'completed' ? 'Abgeschlossen' : s.status === 'active' ? 'Aktiv' : 'Abgebrochen'}
</span>
<span className={styles.sessionDate}>
{s.startedAt ? new Date(s.startedAt).toLocaleDateString('de-CH') : ''}
</span>
{s.competenceScore != null && (
<span className={styles.sessionScore}>Score: {Math.round(s.competenceScore)}</span>
)}
</div>
{s.summary && (
<div className={styles.sessionSummary}>
<ReactMarkdown>{s.summary}</ReactMarkdown>
</div>
)}
<div className={styles.sessionMeta}>
{s.messageCount} Nachrichten | {Math.round(s.durationSeconds / 60)} Min.
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Scores Tab */}
{activeTab === 'scores' && (
<div className={styles.tabContent}>
{coach.scores.length === 0 ? (
<div className={styles.emptyTab}>Noch keine Bewertungen. Schliesse eine Session ab, um Scores zu erhalten.</div>
) : (
<div className={styles.scoreList}>
{_groupScoresByDimension(coach.scores).map(group => (
<div key={group.dimension} className={styles.scoreGroup}>
<div className={styles.scoreDimension}>
<span className={styles.scoreDimensionLabel}>{_dimensionLabel(group.dimension)}</span>
<span className={styles.scoreLatest}>{Math.round(group.latest.score)}/100</span>
<span className={`${styles.scoreTrend} ${styles[`trend_${group.latest.trend}`]}`}>
{group.latest.trend === 'improving' ? 'steigend' : group.latest.trend === 'declining' ? 'sinkend' : 'stabil'}
</span>
</div>
<div className={styles.scoreBar}>
<div className={styles.scoreBarFill} style={{ width: `${group.latest.score}%` }} />
</div>
{group.latest.evidence && (
<div className={styles.scoreEvidence}>{group.latest.evidence}</div>
)}
</div>
))}
</div>
)}
</div>
)}
</>)}
</div>
);
};
interface ScoreGroup {
dimension: string;
latest: { score: number; trend: string; evidence?: string; createdAt?: string };
history: Array<{ score: number; createdAt?: string }>;
}
function _groupScoresByDimension(scores: any[]): ScoreGroup[] {
const groups: Record<string, ScoreGroup> = {};
for (const s of scores) {
const dim = s.dimension;
if (!groups[dim]) {
groups[dim] = { dimension: dim, latest: s, history: [] };
}
groups[dim].history.push({ score: s.score, createdAt: s.createdAt });
if (s.createdAt > (groups[dim].latest.createdAt || '')) {
groups[dim].latest = s;
}
}
return Object.values(groups);
}
function _dimensionLabel(dim: string): string {
const labels: Record<string, string> = {
empathy: 'Einfuehlungsvermoegen',
clarity: 'Klarheit',
assertiveness: 'Durchsetzung',
listening: 'Zuhoeren',
selfReflection: 'Selbstreflexion',
};
return labels[dim] || dim;
}
export default CommcoachDossierView;