(null);
@@ -71,6 +78,13 @@ export const CommcoachCoachingView: React.FC = () => {
}
}, [coach.session]);
+ useEffect(() => {
+ if (!instanceId) return;
+ getPersonasApi(request, instanceId)
+ .then(p => setPersonas(p))
+ .catch(() => {});
+ }, [instanceId, request]);
+
useEffect(() => {
if (!coach.session || coach.isMuted) {
if (speechRecognitionRef.current) {
@@ -284,8 +298,36 @@ export const CommcoachCoachingView: React.FC = () => {
{coach.selectedContext?.title}
{coach.selectedContext?.description || 'Starte eine neue Coaching-Session zu diesem Thema.'}
-
)}
diff --git a/src/pages/views/commcoach/CommcoachDashboardView.module.css b/src/pages/views/commcoach/CommcoachDashboardView.module.css
index bfea6ac..54899b2 100644
--- a/src/pages/views/commcoach/CommcoachDashboardView.module.css
+++ b/src/pages/views/commcoach/CommcoachDashboardView.module.css
@@ -114,6 +114,32 @@
border-radius: 10px;
}
+.badgeGrid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+}
+
+.badgeCard {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem 0.9rem;
+ background: var(--bg-card, #fff);
+ border: 1px solid var(--border-color, #e0e0e0);
+ border-radius: 20px;
+ font-size: 0.85rem;
+}
+
+.badgeIcon {
+ font-size: 1.1rem;
+}
+
+.badgeLabel {
+ font-weight: 500;
+ color: var(--text-primary, #333);
+}
+
.tipCard {
background: var(--bg-card, #fff);
border: 1px solid var(--border-color, #e0e0e0);
diff --git a/src/pages/views/commcoach/CommcoachDashboardView.tsx b/src/pages/views/commcoach/CommcoachDashboardView.tsx
index 403c764..d30b8f3 100644
--- a/src/pages/views/commcoach/CommcoachDashboardView.tsx
+++ b/src/pages/views/commcoach/CommcoachDashboardView.tsx
@@ -99,6 +99,27 @@ export const CommcoachDashboardView: React.FC = () => {
)}
+ {/* Level + Badges */}
+ {(dashboard.level || (dashboard.badges && dashboard.badges.length > 0)) && (
+
+
+ {dashboard.level
+ ? `Level ${dashboard.level.number}: ${dashboard.level.label}`
+ : 'Auszeichnungen'}
+
+ {dashboard.badges && dashboard.badges.length > 0 && (
+
+ {dashboard.badges.map(b => (
+
+
{_badgeIcon(b.icon)}
+
{b.label || b.badgeKey}
+
+ ))}
+
+ )}
+
+ )}
+
{/* Quick Start */}
Tipp des Tages
@@ -125,6 +146,15 @@ function _categoryLabel(category: string): string {
return labels[category] || category;
}
+function _badgeIcon(icon?: string): string {
+ const icons: Record = {
+ star: '\u2605', fire: '\u{1F525}', trophy: '\u{1F3C6}',
+ medal: '\u{1F3C5}', layers: '\u{1F4DA}', theater: '\u{1F3AD}',
+ compass: '\u{1F9ED}', 'check-circle': '\u2714',
+ };
+ return icons[icon || 'star'] || '\u2605';
+}
+
function _formatDate(isoStr: string): string {
try {
const d = new Date(isoStr);
diff --git a/src/pages/views/commcoach/CommcoachDossierView.module.css b/src/pages/views/commcoach/CommcoachDossierView.module.css
index cc79280..8372ef0 100644
--- a/src/pages/views/commcoach/CommcoachDossierView.module.css
+++ b/src/pages/views/commcoach/CommcoachDossierView.module.css
@@ -287,3 +287,116 @@
color: var(--text-secondary, #666);
line-height: 1.4;
}
+
+/* Score History */
+.scoreHistory {
+ margin-top: 0.5rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.scoreHistoryLabel {
+ font-size: 0.75rem;
+ color: var(--text-secondary, #888);
+}
+
+.scoreHistoryPoints {
+ display: flex;
+ gap: 0.4rem;
+ flex-wrap: wrap;
+}
+
+.scoreHistoryPoint {
+ padding: 0.15rem 0.4rem;
+ background: var(--bg-hover, #f0f0f0);
+ border-radius: 4px;
+ font-size: 0.7rem;
+ color: var(--text-secondary, #666);
+}
+
+/* Export Button */
+.btnExport {
+ padding: 0.4rem 0.75rem;
+ background: transparent;
+ border: 1px solid var(--border-color, #ddd);
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.8rem;
+ color: var(--text-primary, #333);
+ text-decoration: none;
+ display: inline-block;
+}
+
+.btnExport:hover {
+ border-color: var(--primary-color, #F25843);
+ color: var(--primary-color, #F25843);
+}
+
+.headerActions {
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+}
+
+/* Session Export */
+.sessionExport {
+ margin-left: 0.5rem;
+ font-size: 0.75rem;
+ color: var(--primary-color, #F25843);
+ text-decoration: none;
+}
+
+.sessionExport:hover {
+ text-decoration: underline;
+}
+
+/* Documents */
+.uploadLabel {
+ padding: 0.5rem 1rem;
+ background: var(--primary-color, #F25843);
+ color: #fff;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.85rem;
+ display: inline-block;
+}
+
+.uploadLabel:hover { filter: brightness(1.08); }
+
+.documentList {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.documentItem {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.75rem;
+ padding: 0.75rem;
+ background: var(--bg-card, #fff);
+ border: 1px solid var(--border-color, #e0e0e0);
+ border-radius: 8px;
+}
+
+.documentInfo { flex: 1; }
+
+.documentName {
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: var(--text-primary, #333);
+}
+
+.documentMeta {
+ font-size: 0.75rem;
+ color: var(--text-secondary, #888);
+ margin-top: 0.2rem;
+}
+
+.documentSummary {
+ font-size: 0.8rem;
+ color: var(--text-secondary, #666);
+ margin-top: 0.4rem;
+ line-height: 1.4;
+}
diff --git a/src/pages/views/commcoach/CommcoachDossierView.tsx b/src/pages/views/commcoach/CommcoachDossierView.tsx
index d0ac345..1737590 100644
--- a/src/pages/views/commcoach/CommcoachDossierView.tsx
+++ b/src/pages/views/commcoach/CommcoachDossierView.tsx
@@ -6,13 +6,26 @@
import React, { useState, useCallback, useEffect } from 'react';
import { useCommcoach } from '../../../hooks/useCommcoach';
+import { useApiRequest } from '../../../hooks/useApi';
+import { useInstanceId } from '../../../hooks/useCurrentInstance';
+import {
+ getDossierExportUrl, getSessionExportUrl,
+ getDocumentsApi, uploadDocumentApi, deleteDocumentApi,
+ getScoreHistoryApi,
+ type CoachingDocument,
+} from '../../../api/commcoachApi';
import ReactMarkdown from 'react-markdown';
import styles from './CommcoachDossierView.module.css';
export const CommcoachDossierView: React.FC = () => {
const coach = useCommcoach();
+ const { request } = useApiRequest();
+ const instanceId = useInstanceId();
const [newTaskTitle, setNewTaskTitle] = useState('');
- const [activeTab, setActiveTab] = useState<'sessions' | 'tasks' | 'scores'>('tasks');
+ const [activeTab, setActiveTab] = useState<'sessions' | 'tasks' | 'scores' | 'documents'>('tasks');
+ const [documents, setDocuments] = useState([]);
+ const [uploading, setUploading] = useState(false);
+ const [scoreHistory, setScoreHistory] = useState>>({});
useEffect(() => {
if (!coach.selectedContextId && coach.contexts.length > 0) {
@@ -20,6 +33,41 @@ export const CommcoachDossierView: React.FC = () => {
}
}, [coach.contexts, coach.selectedContextId, coach.selectContext]);
+ useEffect(() => {
+ if (!instanceId || !coach.selectedContextId) return;
+ getDocumentsApi(request, instanceId, coach.selectedContextId)
+ .then(d => setDocuments(d))
+ .catch(() => {});
+ getScoreHistoryApi(request, instanceId, coach.selectedContextId)
+ .then(h => setScoreHistory(h))
+ .catch(() => {});
+ }, [instanceId, request, coach.selectedContextId]);
+
+ const handleUpload = useCallback(async (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file || !instanceId || !coach.selectedContextId) return;
+ setUploading(true);
+ try {
+ const doc = await uploadDocumentApi(instanceId, coach.selectedContextId, file);
+ setDocuments(prev => [doc, ...prev]);
+ } catch {
+ // upload failed
+ } finally {
+ setUploading(false);
+ e.target.value = '';
+ }
+ }, [instanceId, coach.selectedContextId]);
+
+ const handleDeleteDocument = useCallback(async (docId: string) => {
+ if (!instanceId) return;
+ try {
+ await deleteDocumentApi(request, instanceId, docId);
+ setDocuments(prev => prev.filter(d => d.id !== docId));
+ } catch {
+ // delete failed
+ }
+ }, [instanceId, request]);
+
const handleAddTask = useCallback(async () => {
if (!newTaskTitle.trim()) return;
await coach.addTask(newTaskTitle);
@@ -65,6 +113,26 @@ export const CommcoachDossierView: React.FC = () => {
)}
+ {instanceId && coach.selectedContextId && (
+ <>
+
+ Export MD
+
+
+ Export PDF
+
+ >
+ )}
coach.archiveContext(coach.selectedContextId!)}>
Archivieren
@@ -91,6 +159,12 @@ export const CommcoachDossierView: React.FC = () => {
>
Bewertungen ({coach.scores.length})
+
setActiveTab('documents')}
+ >
+ Dokumente ({documents.length})
+
{/* Tasks Tab */}
@@ -166,6 +240,18 @@ export const CommcoachDossierView: React.FC = () => {
)}
{s.messageCount} Nachrichten | {Math.round(s.durationSeconds / 60)} Min.
+ {s.personaId &&
| Persona}
+ {instanceId && s.status === 'completed' && (
+
e.stopPropagation()}
+ >
+ Export
+
+ )}
))}
@@ -196,6 +282,58 @@ export const CommcoachDossierView: React.FC = () => {
{group.latest.evidence && (
{group.latest.evidence}
)}
+ {scoreHistory[group.dimension] && scoreHistory[group.dimension].length > 1 && (
+
+
Verlauf:
+
+ {scoreHistory[group.dimension].map((entry, i) => (
+
+ {Math.round(entry.score)}
+
+ ))}
+
+
+ )}
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* Documents Tab */}
+ {activeTab === 'documents' && (
+
+
+
+
+ {documents.length === 0 ? (
+
Keine Dokumente. Lade Dateien hoch, um sie mit diesem Kontext zu verknuepfen.
+ ) : (
+
+ {documents.map(doc => (
+
+
+
{doc.fileName}
+
+ {_formatFileSize(doc.fileSize)} | {doc.createdAt ? new Date(doc.createdAt).toLocaleDateString('de-CH') : ''}
+
+ {doc.summary && (
+
{doc.summary}
+ )}
+
+
handleDeleteDocument(doc.id)}>
+ x
+
))}
@@ -228,6 +366,12 @@ function _groupScoresByDimension(scores: any[]): ScoreGroup[] {
return Object.values(groups);
}
+function _formatFileSize(bytes: number): string {
+ if (bytes < 1024) return `${bytes} B`;
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
+}
+
function _dimensionLabel(dim: string): string {
const labels: Record
= {
empathy: 'Einfuehlungsvermoegen',