Merge pull request #38 from valueonag/feat/demo-system-readieness

fixes comcoach
This commit is contained in:
Patrick Motsch 2026-04-16 14:21:29 +02:00 committed by GitHub
commit 378135efca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 285 additions and 3 deletions

View file

@ -149,3 +149,90 @@
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.6; line-height: 1.6;
} }
/* ============================================================ */
/* MOBILE RESPONSIVE */
/* ============================================================ */
@media (max-width: 768px) {
.dashboard {
padding: 0.75rem;
}
.kpiGrid {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
.kpiCard {
padding: 0.85rem;
}
.kpiValue {
font-size: 1.5rem;
}
.contextGrid {
grid-template-columns: 1fr;
}
.badgeGrid {
gap: 0.5rem;
}
.badgeCard {
padding: 0.4rem 0.7rem;
font-size: 0.8rem;
}
}
@media (max-width: 400px) {
.kpiGrid {
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.kpiCard {
padding: 0.65rem;
border-radius: 8px;
}
.kpiValue {
font-size: 1.25rem;
}
.kpiLabel {
font-size: 0.75rem;
}
}
.newTopicBtn {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 1.25rem;
background: var(--primary-color, #F25843);
color: #fff;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 500;
}
.newTopicBtn:hover { filter: brightness(1.08); }
@media (max-width: 768px) {
.dashboard { padding: 0.75rem; }
.kpiGrid {
grid-template-columns: repeat(2, 1fr);
gap: 0.65rem;
}
.kpiCard { padding: 0.9rem; }
.kpiValue { font-size: 1.5rem; }
.kpiLabel { font-size: 0.78rem; }
.kpiSub { font-size: 0.7rem; }
.contextGrid {
grid-template-columns: 1fr;
gap: 0.65rem;
}
.badgeGrid { gap: 0.5rem; }
.badgeCard { padding: 0.4rem 0.65rem; font-size: 0.8rem; }
.sectionTitle { font-size: 1rem; }
.tipCard { padding: 0.9rem; font-size: 0.85rem; }
}

View file

@ -25,6 +25,12 @@ export const CommcoachDashboardView: React.FC = () => {
} }
}; };
const _handleNewTopic = useCallback(() => {
if (mandateId && instanceId) {
navigate(`/mandates/${mandateId}/commcoach/${instanceId}/coaching?newContext=true`);
}
}, [mandateId, instanceId, navigate]);
const _categoryLabel = useCallback( const _categoryLabel = useCallback(
(category: string) => { (category: string) => {
const labels: Record<string, string> = { const labels: Record<string, string> = {
@ -88,11 +94,16 @@ export const CommcoachDashboardView: React.FC = () => {
{/* Active Contexts */} {/* Active Contexts */}
<div className={styles.section}> <div className={styles.section}>
<h3 className={styles.sectionTitle}>{t('Aktive Coaching-Themen')}</h3> <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 ? ( {dashboard.contexts.length === 0 ? (
<div className={styles.emptyState}> <div className={styles.emptyState}>
<p>{t('Noch keine Coaching-Themen angelegt.')}</p> <p>{t('Noch keine Coaching-Themen angelegt.')}</p>
<p>{t('Wechseln Sie zum Tab Coaching, um ein Thema anzulegen.')}</p> <p>{t('Klicken Sie auf "Neues Thema" um zu starten.')}</p>
</div> </div>
) : ( ) : (
<div className={styles.contextGrid}> <div className={styles.contextGrid}>

View file

@ -22,6 +22,21 @@
min-width: 36px; min-width: 36px;
} }
@media (max-width: 768px) {
.dossierLayout {
flex-direction: column;
height: calc(100vh - var(--mobile-topbar-height, 56px));
}
.udbSidebar {
display: none;
}
.udbSidebarCollapsed {
display: none;
}
}
.udbToggle { .udbToggle {
position: absolute; position: absolute;
top: 8px; top: 8px;
@ -51,9 +66,17 @@
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
min-height: 0;
overflow: hidden; overflow: hidden;
} }
@media (max-width: 768px) {
.dossier {
overflow-y: auto;
overflow-x: hidden;
}
}
/* Context Selector */ /* Context Selector */
.contextSelector { .contextSelector {
display: flex; display: flex;
@ -65,6 +88,29 @@
align-items: center; align-items: center;
} }
@media (max-width: 768px) {
.contextSelector {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding: 0.5rem 0.75rem;
}
.contextSelector::-webkit-scrollbar { display: none; }
}
@media (max-width: 768px) {
.contextSelector {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding: 0.5rem 0.75rem;
gap: 0.4rem;
scrollbar-width: none;
}
.contextSelector::-webkit-scrollbar { display: none; }
}
.contextChip { .contextChip {
display: flex; display: flex;
align-items: center; align-items: center;
@ -166,6 +212,31 @@
flex-shrink: 0; flex-shrink: 0;
} }
@media (max-width: 768px) {
.header {
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
}
.headerActions {
flex-wrap: wrap;
}
}
@media (max-width: 768px) {
.header {
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
}
.headerActions {
width: 100%;
justify-content: flex-start;
flex-wrap: wrap;
}
.title { font-size: 1.1rem; }
}
.title { .title {
font-size: 1.3rem; font-size: 1.3rem;
font-weight: 600; font-weight: 600;
@ -273,6 +344,36 @@
padding: 0 1rem; padding: 0 1rem;
} }
@media (max-width: 768px) {
.tabs {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
padding: 0 0.5rem;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
white-space: nowrap;
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
}
@media (max-width: 768px) {
.tabs {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding: 0 0.5rem;
scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab {
white-space: nowrap;
padding: 0.5rem 0.9rem;
font-size: 0.8rem;
}
}
.tab { .tab {
padding: 0.6rem 1.25rem; padding: 0.6rem 1.25rem;
background: transparent; background: transparent;
@ -325,6 +426,12 @@
.personaSelector { margin-bottom: 1rem; } .personaSelector { margin-bottom: 1rem; }
.personaLabel { font-size: 0.85rem; font-weight: 500; color: var(--text-primary, #333); display: block; margin-bottom: 0.5rem; } .personaLabel { font-size: 0.85rem; font-weight: 500; color: var(--text-primary, #333); display: block; margin-bottom: 0.5rem; }
.personaGrid { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; } .personaGrid { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; }
@media (max-width: 768px) {
.personaGrid { gap: 0.35rem; }
.personaChip { font-size: 0.75rem; padding: 0.3rem 0.6rem; }
.sessionStart { padding: 1rem; }
}
.personaChip { .personaChip {
display: flex; align-items: center; gap: 0.3rem; display: flex; align-items: center; gap: 0.3rem;
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
@ -350,6 +457,17 @@
.sessionLabel { font-size: 0.85rem; font-weight: 500; color: var(--text-primary, #333); } .sessionLabel { font-size: 0.85rem; font-weight: 500; color: var(--text-primary, #333); }
.sessionActions { display: flex; gap: 0.5rem; } .sessionActions { display: flex; gap: 0.5rem; }
@media (max-width: 768px) {
.sessionHeader {
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
}
.sessionActions {
flex-wrap: wrap;
}
}
/* Messages */ /* Messages */
.messages { .messages {
flex: 1; flex: 1;
@ -361,9 +479,25 @@
} }
.message { max-width: 80%; } .message { max-width: 80%; }
@media (max-width: 768px) {
.message { max-width: 92%; }
.messages { padding: 0.75rem 0.5rem; gap: 0.5rem; }
}
.messageUser { align-self: flex-end; } .messageUser { align-self: flex-end; }
.messageAssistant { align-self: flex-start; } .messageAssistant { align-self: flex-start; }
@media (max-width: 768px) {
.message { max-width: 92%; }
.messages { padding: 0.75rem; gap: 0.5rem; }
.sessionHeader {
flex-wrap: wrap;
gap: 0.4rem;
padding: 0.4rem 0.75rem;
}
.sessionActions { flex-wrap: wrap; gap: 0.3rem; }
}
.messageBubble { .messageBubble {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 12px; border-radius: 12px;
@ -528,6 +662,25 @@
.textInputRow { display: flex; gap: 0.5rem; align-items: flex-end; } .textInputRow { display: flex; gap: 0.5rem; align-items: flex-end; }
@media (max-width: 768px) {
.inputArea {
padding: 0.5rem 0.5rem calc(env(safe-area-inset-bottom, 0px) + 0.5rem);
}
.textInputRow {
gap: 0.35rem;
}
.sendBtn {
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
}
}
@media (max-width: 768px) {
.inputArea { padding: 0.5rem 0.75rem; }
.textInputRow { gap: 0.35rem; }
.sendBtn { padding: 0.5rem 0.75rem; font-size: 0.8rem; }
}
.textInput { .textInput {
flex: 1; min-width: 0; flex: 1; min-width: 0;
padding: 0.6rem 0.75rem; padding: 0.6rem 0.75rem;

View file

@ -7,6 +7,7 @@
*/ */
import React, { useState, useRef, useCallback, useEffect } from 'react'; import React, { useState, useRef, useCallback, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useCommcoach } from '../../../hooks/useCommcoach'; import { useCommcoach } from '../../../hooks/useCommcoach';
import { type TtsEvent } from '../../../hooks/useTtsPlayback'; import { type TtsEvent } from '../../../hooks/useTtsPlayback';
import { useApiRequest } from '../../../hooks/useApi'; import { useApiRequest } from '../../../hooks/useApi';
@ -73,6 +74,7 @@ export const CommcoachDossierView: React.FC<CommcoachDossierViewProps> = ({ pers
const mandateId = persistentMandateId || routeMandateId; const mandateId = persistentMandateId || routeMandateId;
const coach = useCommcoach(instanceId); const coach = useCommcoach(instanceId);
const { request } = useApiRequest(); const { request } = useApiRequest();
const [searchParams, setSearchParams] = useSearchParams();
const [activeTab, setActiveTab] = useState<TabKey>('coaching'); const [activeTab, setActiveTab] = useState<TabKey>('coaching');
const [showNewContext, setShowNewContext] = useState(false); const [showNewContext, setShowNewContext] = useState(false);
@ -144,6 +146,14 @@ export const CommcoachDossierView: React.FC<CommcoachDossierViewProps> = ({ pers
} }
}, [coach.contexts, coach.selectedContextId, coach.selectContext]); }, [coach.contexts, coach.selectedContextId, coach.selectContext]);
useEffect(() => {
if (searchParams.get('newContext') === 'true') {
setShowNewContext(true);
searchParams.delete('newContext');
setSearchParams(searchParams, { replace: true });
}
}, [searchParams, setSearchParams]);
// Load scores, personas when context changes // Load scores, personas when context changes
useEffect(() => { useEffect(() => {
if (!instanceId || !coach.selectedContextId) return; if (!instanceId || !coach.selectedContextId) return;

View file

@ -41,7 +41,6 @@ export const CommcoachKeepAlive: React.FC<CommcoachKeepAliveProps> = ({ isVisibl
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
overflow: 'hidden',
}} }}
> >
<CommcoachDossierView <CommcoachDossierView

View file

@ -154,3 +154,25 @@
cursor: not-allowed; cursor: not-allowed;
opacity: 0.8; opacity: 0.8;
} }
@media (max-width: 768px) {
.settings {
padding: 0.75rem;
max-width: 100%;
}
.statsGrid {
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
}
.voiceRow {
flex-direction: column;
}
}
@media (max-width: 768px) {
.settings { padding: 0.75rem; max-width: 100%; }
.statsGrid { grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.statItem { padding: 0.5rem; }
.statValue { font-size: 1.2rem; }
.voiceRow { flex-direction: column; }
}