feat: implement mobile and desktop layouts for framework editing
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 40s

- Added responsive design for the framework editing page, including mobile tabs and a grid layout for goals and slots on desktop.
- Introduced state management for tab selection and layout detection based on screen size.
- Enhanced user interface with a tabbed navigation system for better organization of content.
- Updated styles in app.css to support new layout features and improve overall aesthetics.
This commit is contained in:
Lars 2026-05-05 11:53:45 +02:00
parent 17f0513821
commit eade9af2fe
3 changed files with 137 additions and 6 deletions

View File

@ -2713,6 +2713,71 @@ a.analysis-split__nav-item {
accent-color: var(--accent);
}
/* Rahmenprogramm bearbeiten — Mobile Tabs, Desktop Ziele | Slots nebeneinander */
.framework-edit {
max-width: 800px;
margin: 0 auto;
}
@media (min-width: 1024px) {
.framework-edit {
max-width: min(1200px, 100%);
}
.framework-edit__tabbar {
display: none !important;
}
.framework-edit__goals-slots {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
align-items: start;
}
/* breit: alle Bereiche sichtbar */
.framework-edit__panel {
display: block !important;
}
}
.framework-edit__tabbar {
display: flex;
gap: 6px;
margin-bottom: 14px;
padding: 2px 0 12px;
border-bottom: 1px solid var(--border);
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.framework-edit__tabbar::-webkit-scrollbar {
display: none;
}
.framework-edit__tab {
flex: 1 1 0;
min-width: 0;
padding: 10px 8px;
border: 1px solid var(--border2);
border-radius: 10px;
background: var(--surface2);
color: var(--text2);
font-size: 12px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
font-family: var(--font);
}
.framework-edit__tab--active {
background: var(--accent-light);
color: var(--accent-dark);
border-color: var(--accent);
}
.framework-edit__goals-slots {
display: block;
}
@media (max-width: 1023px) {
.framework-edit .framework-edit__panel:not(.framework-edit__panel--active) {
display: none !important;
}
}
@media print {
.desktop-sidebar,
.bottom-nav,

View File

@ -175,6 +175,19 @@ export default function TrainingFrameworkProgramEditPage() {
const [units, setUnits] = useState([])
const [pickerSlotIdx, setPickerSlotIdx] = useState(null)
const [peekId, setPeekId] = useState(null)
/** Nur schmal: welcher Block sichtbar — Desktop zeigt Stammdaten + zwei Spalten Ziele|Slots */
const [frameworkTab, setFrameworkTab] = useState('meta')
const [desktopLayout, setDesktopLayout] = useState(
typeof window !== 'undefined' ? window.matchMedia('(min-width: 1024px)').matches : false
)
useEffect(() => {
const mq = window.matchMedia('(min-width: 1024px)')
const apply = () => setDesktopLayout(!!mq.matches)
apply()
mq.addEventListener('change', apply)
return () => mq.removeEventListener('change', apply)
}, [])
const loadMeta = useCallback(async () => {
try {
@ -429,6 +442,8 @@ export default function TrainingFrameworkProgramEditPage() {
}
}
const panelActive = (key) => desktopLayout || frameworkTab === key
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
@ -440,16 +455,53 @@ export default function TrainingFrameworkProgramEditPage() {
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '800px', margin: '0 auto' }}>
<div className="framework-edit">
<p style={{ marginBottom: '0.75rem' }}>
<Link to="/planning/framework-programs" style={{ color: 'var(--accent-dark)' }}>
Alle Rahmenprogramme
</Link>
</p>
<h1 style={{ marginBottom: '1rem' }}>{isNew ? 'Neues Rahmenprogramm' : 'Rahmenprogramm bearbeiten'}</h1>
<h1 style={{ marginBottom: '0.75rem' }}>{isNew ? 'Neues Rahmenprogramm' : 'Rahmenprogramm bearbeiten'}</h1>
<div className="card" style={{ marginBottom: '1rem' }}>
<div className="card" style={{ marginBottom: '1rem', background: 'var(--surface2)', borderStyle: 'dashed' }}>
<p style={{ fontSize: '0.88rem', color: 'var(--text2)', lineHeight: 1.55, margin: 0 }}>
<strong style={{ color: 'var(--text1)' }}>Stand dieser Funktion:</strong> Der Rahmen speichert Ziele, Slots
und Übungslisten noch <strong>ohne</strong> die feinere{' '}
<strong>TrainingsplanStruktur pro Einheit</strong> (Abschnitte wie in der Einheitenplanung) und{' '}
<strong>ohne Übernahme</strong> in die nächste konkrete Trainingseinheit (geplanter Schritt Warenkorb).
<strong> Ziele</strong> lassen sich noch nicht einzelnen Einheiten oder Übungen zuordnen;{' '}
<strong>Progressionsketten</strong> aus dem ÜbungsGraph sind hier noch nicht eingebunden dafür sind
API/DatenErweiterungen und ein Folgerelease vorgesehen.
</p>
</div>
<div className="framework-edit__tabbar" role="tablist" aria-label="Bereiche">
{[
{ id: 'meta', label: 'Stammdaten' },
{ id: 'goals', label: 'Ziele' },
{ id: 'slots', label: 'Slots & Übungen' },
].map((t) => (
<button
key={t.id}
type="button"
role="tab"
aria-selected={frameworkTab === t.id}
className={'framework-edit__tab' + (frameworkTab === t.id ? ' framework-edit__tab--active' : '')}
onClick={() => setFrameworkTab(t.id)}
>
{t.label}
</button>
))}
</div>
<div
className={
'framework-edit__panel framework-edit__panel--meta card' +
(panelActive('meta') ? ' framework-edit__panel--active' : '')
}
style={{ marginBottom: '1rem' }}
>
<h3 className="card-title">Stammdaten</h3>
<div className="form-row">
<label className="form-label">Titel *</label>
@ -555,7 +607,14 @@ export default function TrainingFrameworkProgramEditPage() {
</div>
</div>
<div className="card" style={{ marginBottom: '1rem' }}>
<div className="framework-edit__goals-slots">
<div
className={
'framework-edit__panel framework-edit__panel--goals card' +
(panelActive('goals') ? ' framework-edit__panel--active' : '')
}
style={{ marginBottom: '1rem' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}>
Entwicklungsziele
@ -617,7 +676,13 @@ export default function TrainingFrameworkProgramEditPage() {
))}
</div>
<div className="card" style={{ marginBottom: '1.5rem' }}>
<div
className={
'framework-edit__panel framework-edit__panel--slots card' +
(panelActive('slots') ? ' framework-edit__panel--active' : '')
}
style={{ marginBottom: '1.5rem' }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}>
SessionSlots & Übungen
@ -779,6 +844,7 @@ export default function TrainingFrameworkProgramEditPage() {
</div>
))}
</div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
<button type="button" className="btn btn-primary" disabled={saving} onClick={handleSave}>

View File

@ -12,7 +12,7 @@ export const PAGE_VERSIONS = {
SkillsPage: "1.0.0",
TrainingPlanningPage: "1.3.1",
TrainingFrameworkProgramsListPage: "1.0.0",
TrainingFrameworkProgramEditPage: "1.0.1",
TrainingFrameworkProgramEditPage: "1.1.0",
TrainingUnitRunPage: "1.1.0",
TrainingCoachPage: "1.0.0",
AdminCatalogsPage: "2.2.0", // Updated: Frontend API Calls & Field Names für renamed tables