frontend_nyla/src/pages/views/commcoach/CommcoachDashboardView.tsx
2026-04-16 14:20:29 +02:00

195 lines
7.1 KiB
TypeScript

/**
* CommCoach Dashboard View
*
* Shows KPIs, streak, active contexts, and quick-start coaching entry.
*/
import React, { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useCommcoachDashboard } from '../../../hooks/useCommcoachDashboard';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import styles from './CommcoachDashboardView.module.css';
import { useLanguage } from '../../../providers/language/LanguageContext';
export const CommcoachDashboardView: React.FC = () => {
const { t } = useLanguage();
const navigate = useNavigate();
const { mandateId, instanceId } = useCurrentInstance();
const { dashboard, loading, error } = useCommcoachDashboard();
const handleContextClick = (contextId: string) => {
if (mandateId && instanceId) {
navigate(`/mandates/${mandateId}/commcoach/${instanceId}/coaching?context=${contextId}`);
}
};
const _handleNewTopic = useCallback(() => {
if (mandateId && instanceId) {
navigate(`/mandates/${mandateId}/commcoach/${instanceId}/coaching?newContext=true`);
}
}, [mandateId, instanceId, navigate]);
const _categoryLabel = useCallback(
(category: string) => {
const labels: Record<string, string> = {
leadership: t('Führung'),
conflict: t('Konflikt'),
negotiation: t('Verhandlung'),
presentation: t('Präsentation'),
feedback: t('Feedback'),
delegation: t('Delegation'),
changeManagement: t('Change Management'),
custom: t('Individuell'),
};
return labels[category] || category;
},
[t],
);
if (loading && !dashboard) {
return <div className={styles.loading}>{t('Dashboard wird geladen…')}</div>;
}
if (error) {
return <div className={styles.error}>{error}</div>;
}
if (!dashboard) {
return <div className={styles.empty}>{t('Keine Daten verfügbar')}</div>;
}
return (
<div className={styles.dashboard}>
{/* KPI Cards */}
<div className={styles.kpiGrid}>
<div className={styles.kpiCard}>
<div className={styles.kpiValue}>{dashboard.streakDays}</div>
<div className={styles.kpiLabel}>{t('Tage in Folge')}</div>
<div className={styles.kpiSub}>{t('Rekord:')} {dashboard.longestStreak}</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiValue}>{dashboard.totalSessions}</div>
<div className={styles.kpiLabel}>{t('Sessions')}</div>
<div className={styles.kpiSub}>{dashboard.totalMinutes} {t('Min. gesamt')}</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiValue}>
{dashboard.averageScore != null ? Math.round(dashboard.averageScore) : '--'}
</div>
<div className={styles.kpiLabel}>{t('Kompetenz-Score')}</div>
<div className={styles.kpiSub}>{t('Durchschnitt')}</div>
</div>
<div className={styles.kpiCard}>
<div className={styles.kpiValue}>
{dashboard.goalProgress != null ? `${dashboard.goalProgress}%` : '--'}
</div>
<div className={styles.kpiLabel}>{t('Zielfortschritt')}</div>
<div className={styles.kpiSub}>
{t('{count} offene Aufgaben', { count: dashboard.openTasks })}
</div>
</div>
</div>
{/* Active Contexts */}
<div className={styles.section}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h3 className={styles.sectionTitle} style={{ margin: 0 }}>{t('Aktive Coaching-Themen')}</h3>
<button className={styles.newTopicBtn} onClick={_handleNewTopic}>
+ {t('Neues Thema')}
</button>
</div>
{dashboard.contexts.length === 0 ? (
<div className={styles.emptyState}>
<p>{t('Noch keine Coaching-Themen angelegt.')}</p>
<p>{t('Klicken Sie auf "Neues Thema" um zu starten.')}</p>
</div>
) : (
<div className={styles.contextGrid}>
{dashboard.contexts.map(ctx => (
<div
key={ctx.id}
className={styles.contextCard}
onClick={() => handleContextClick(ctx.id)}
role="button"
tabIndex={0}
onKeyDown={e => e.key === 'Enter' && handleContextClick(ctx.id)}
>
<div className={styles.contextTitle}>{ctx.title}</div>
<div className={styles.contextMeta}>
<span className={styles.contextCategory}>{_categoryLabel(ctx.category)}</span>
<span>
{ctx.sessionCount} {t('Sessions')}
</span>
{ctx.goalProgress != null && (
<span>{t('Ziele: {pct}%', { pct: ctx.goalProgress })}</span>
)}
</div>
{ctx.lastSessionAt && (
<div className={styles.contextLast}>
{t('Letzte Session:')} {_formatDate(ctx.lastSessionAt)}
</div>
)}
</div>
))}
</div>
)}
</div>
{/* Level + Badges */}
{(dashboard.level || (dashboard.badges && dashboard.badges.length > 0)) && (
<div className={styles.section}>
<h3 className={styles.sectionTitle}>
{dashboard.level
? t('Level {num}: {label}', {
num: dashboard.level.number,
label: dashboard.level.label,
})
: t('Auszeichnungen')}
</h3>
{dashboard.badges && dashboard.badges.length > 0 && (
<div className={styles.badgeGrid}>
{dashboard.badges.map(b => (
<div key={b.id} className={styles.badgeCard} title={b.description || b.badgeKey}>
<div className={styles.badgeIcon}>{_badgeIcon(b.icon)}</div>
<div className={styles.badgeLabel}>{b.label || b.badgeKey}</div>
</div>
))}
</div>
)}
</div>
)}
{/* Quick Start */}
<div className={styles.section}>
<h3 className={styles.sectionTitle}>{t('Tipp des Tages')}</h3>
<div className={styles.tipCard}>
<p>
{t(
'Konsistenz schlägt Intensität. Auch 10 Minuten tägliches Coaching-Gespräch bringt messbare Fortschritte in deiner Kommunikationskompetenz.',
)}
</p>
</div>
</div>
</div>
);
};
function _badgeIcon(icon?: string): string {
const icons: Record<string, string> = {
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);
return d.toLocaleDateString('de-CH', { day: '2-digit', month: '2-digit', year: 'numeric' });
} catch { return isoStr; }
}
export default CommcoachDashboardView;