Trainingsplanung und Rahmenplanung #9

Merged
Lars merged 29 commits from develop into main 2026-05-05 16:05:01 +02:00
16 changed files with 130 additions and 58 deletions
Showing only changes of commit 83ee300192 - Show all commits

View File

@ -71,6 +71,76 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
} }
} }
.app-logo { font-size: 18px; font-weight: 700; color: var(--accent); letter-spacing: -0.02em; } .app-logo { font-size: 18px; font-weight: 700; color: var(--accent); letter-spacing: -0.02em; }
/* === Seiten-Inhalt: einheitlich volle Breite bis 1023px; ab Desktop optionale Max-Breite === */
.app-page {
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
}
@media (min-width: 1024px) {
.app-page--constrained-lg {
max-width: 1200px;
margin-left: auto;
margin-right: auto;
}
.app-page--constrained-md {
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
.app-page--constrained-sm {
max-width: 720px;
margin-left: auto;
margin-right: auto;
}
.app-page--reading {
max-width: 640px;
margin-left: auto;
margin-right: auto;
}
}
/* Form-Grids: minmax(0,…) verhindert Grid-Overflow; eine Spalte bis zum ersten Breakpoint */
.responsive-grid-2 {
display: grid;
gap: 12px;
grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 480px) {
.responsive-grid-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.responsive-grid-3 {
display: grid;
gap: 1rem;
grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 560px) {
.responsive-grid-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.responsive-grid-4 {
display: grid;
gap: 1rem;
grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 560px) {
.responsive-grid-4 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 900px) {
.responsive-grid-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
/* unten: Tab-Leiste + Abstand nach oben zur Leiste + Home-Indicator (iPhone) */ /* unten: Tab-Leiste + Abstand nach oben zur Leiste + Home-Indicator (iPhone) */
.app-main { .app-main {
flex: 1; flex: 1;
@ -163,6 +233,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
gap: 8px; gap: 8px;
padding: 0; padding: 0;
margin-bottom: 16px; margin-bottom: 16px;
min-width: 0;
} }
.form-label { .form-label {
@ -183,6 +254,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
.form-input { .form-input {
width: 100%; width: 100%;
min-width: 0;
padding: 10px 12px; padding: 10px 12px;
text-align: left; text-align: left;
font-family: var(--font); font-family: var(--font);
@ -2741,14 +2813,16 @@ a.analysis-split__nav-item {
/* Rahmenprogramm bearbeiten — Mobile: Stammdaten | Plan; Desktop: untereinander Ziele → Slots (synchron zu FRAMEWORK_DESKTOP_MIN_PX) */ /* Rahmenprogramm bearbeiten — Mobile: Stammdaten | Plan; Desktop: untereinander Ziele → Slots (synchron zu FRAMEWORK_DESKTOP_MIN_PX) */
.framework-edit { .framework-edit {
max-width: 800px; max-width: 100%;
margin: 0 auto; margin: 0;
width: 100%; width: 100%;
min-width: 0; min-width: 0;
} }
@media (min-width: 900px) { @media (min-width: 900px) {
.framework-edit { .framework-edit {
max-width: min(1200px, 100%); max-width: min(1200px, 100%);
margin-left: auto;
margin-right: auto;
} }
.framework-edit__tabbar { .framework-edit__tabbar {
display: none !important; display: none !important;
@ -2834,6 +2908,12 @@ a.analysis-split__nav-item {
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
@media (max-width: 1023px) {
.framework-slots-board-outer {
scrollbar-gutter: auto;
}
}
.framework-slots-board { .framework-slots-board {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -98,7 +98,7 @@ function AccountSettingsPage() {
} }
return ( return (
<div className="page-padding" style={{ padding: '1rem', maxWidth: '640px', margin: '0 auto' }}> <div className="page-padding app-page app-page--reading" style={{ padding: '1rem' }}>
<h1 style={{ marginBottom: '0.35rem', fontSize: '1.5rem' }}>Einstellungen</h1> <h1 style={{ marginBottom: '0.35rem', fontSize: '1.5rem' }}>Einstellungen</h1>
<p style={{ color: 'var(--text2)', marginBottom: '1.25rem', fontSize: '0.95rem' }}> <p style={{ color: 'var(--text2)', marginBottom: '1.25rem', fontSize: '0.95rem' }}>
Konto &amp; Sicherheit Konto &amp; Sicherheit

View File

@ -313,7 +313,7 @@ export default function AdminCatalogsPage() {
} }
return ( return (
<div style={{ padding: '16px', maxWidth: '1200px', margin: '0 auto' }}> <div className="app-page app-page--constrained-lg">
<AdminPageNav /> <AdminPageNav />
<h1 style={{ marginBottom: '24px' }}>Stammdaten-Kataloge</h1> <h1 style={{ marginBottom: '24px' }}>Stammdaten-Kataloge</h1>

View File

@ -93,7 +93,7 @@ function AdminHierarchyPage() {
] ]
return ( return (
<div style={{ padding: '20px' }}> <div className="app-page app-page--constrained-lg">
<AdminPageNav /> <AdminPageNav />
<h1 style={{ marginTop: 0 }}>Admin: Katalog-Hierarchie</h1> <h1 style={{ marginTop: 0 }}>Admin: Katalog-Hierarchie</h1>

View File

@ -143,8 +143,7 @@ function ClubsPage() {
} }
return ( return (
<div style={{ padding: '2rem' }}> <div className="app-page app-page--constrained-lg">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '0.75rem' }}>Vereinsverwaltung</h1> <h1 style={{ marginBottom: '0.75rem' }}>Vereinsverwaltung</h1>
<p style={{ color: 'var(--text2)', marginBottom: '1.35rem', maxWidth: '46rem', lineHeight: 1.55 }}> <p style={{ color: 'var(--text2)', marginBottom: '1.35rem', maxWidth: '46rem', lineHeight: 1.55 }}>
Für die Trainingsplanung wird mindestens ein <strong>Verein</strong> und eine <strong>Trainingsgruppe</strong> gebraucht. Für die Trainingsplanung wird mindestens ein <strong>Verein</strong> und eine <strong>Trainingsgruppe</strong> gebraucht.
@ -697,7 +696,6 @@ function ClubsPage() {
</div> </div>
)} )}
</div> </div>
</div>
) )
} }

View File

@ -30,7 +30,7 @@ function Dashboard() {
if (loading) { if (loading) {
return ( return (
<div style={{ padding: '2rem', textAlign: 'center' }}> <div className="app-page" style={{ padding: '2rem 0', textAlign: 'center' }}>
<div className="spinner"></div> <div className="spinner"></div>
<p>Laden...</p> <p>Laden...</p>
</div> </div>
@ -38,8 +38,7 @@ function Dashboard() {
} }
return ( return (
<div style={{ minHeight: '100vh', background: 'var(--bg)', padding: '2rem' }}> <div className="app-page app-page--constrained-lg">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1>Dashboard</h1> <h1>Dashboard</h1>
<p style={{ color: 'var(--text2)', marginTop: '0.5rem' }}> <p style={{ color: 'var(--text2)', marginTop: '0.5rem' }}>
Willkommen, {user?.name || user?.email}! Willkommen, {user?.name || user?.email}!
@ -56,7 +55,7 @@ function Dashboard() {
{/* Status Grid */} {/* Status Grid */}
<div style={{ <div style={{
display: 'grid', display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))',
gap: '1rem', gap: '1rem',
marginBottom: '1.5rem' marginBottom: '1.5rem'
}}> }}>
@ -123,7 +122,6 @@ function Dashboard() {
</div> </div>
)} )}
</div> </div>
</div>
) )
} }

View File

@ -144,7 +144,7 @@ function ExerciseDetailPage() {
if (error) { if (error) {
const msg = error.message || String(error) const msg = error.message || String(error)
return ( return (
<div style={{ padding: '1rem', maxWidth: '640px', margin: '0 auto' }}> <div style={{ padding: '1rem' }} className="app-page app-page--reading">
<div className="card"> <div className="card">
<h2>Übung</h2> <h2>Übung</h2>
<p style={{ color: 'var(--danger)' }}>{msg}</p> <p style={{ color: 'var(--danger)' }}>{msg}</p>

View File

@ -699,7 +699,7 @@ function ExerciseFormPage() {
} }
return ( return (
<div style={{ padding: '12px', maxWidth: '720px', margin: '0 auto' }}> <div style={{ padding: '12px' }} className="app-page app-page--constrained-sm">
<div style={{ marginBottom: '12px' }}> <div style={{ marginBottom: '12px' }}>
<button type="button" className="btn btn-secondary" onClick={() => navigate('/exercises')}> <button type="button" className="btn btn-secondary" onClick={() => navigate('/exercises')}>
Übersicht Übersicht

View File

@ -352,7 +352,7 @@ function ExercisesListPage() {
} }
return ( return (
<div style={{ padding: '12px', maxWidth: '1200px', margin: '0 auto' }}> <div className="app-page app-page--constrained-lg">
<div <div
style={{ style={{
display: 'flex', display: 'flex',

View File

@ -103,7 +103,7 @@ export default function MediaWikiImportPage() {
} }
return ( return (
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}> <div className="app-page app-page--constrained-lg">
<AdminPageNav /> <AdminPageNav />
<h1>MediaWiki Import (Semantic MediaWiki)</h1> <h1>MediaWiki Import (Semantic MediaWiki)</h1>

View File

@ -143,8 +143,7 @@ function SkillsPage() {
const methodsByCategory = groupByCategory(methods) const methodsByCategory = groupByCategory(methods)
return ( return (
<div style={{ padding: '2rem' }}> <div className="app-page app-page--constrained-lg">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '1.5rem' }}>Fähigkeiten & Methoden</h1> <h1 style={{ marginBottom: '1.5rem' }}>Fähigkeiten & Methoden</h1>
{/* Tabs */} {/* Tabs */}
@ -510,7 +509,6 @@ function SkillsPage() {
</div> </div>
)} )}
</div> </div>
</div>
) )
} }

View File

@ -95,7 +95,7 @@ export default function TrainerContextsPage() {
} }
return ( return (
<div style={{ padding: '24px', maxWidth: '1200px', margin: '0 auto' }}> <div className="app-page app-page--constrained-lg">
<h1>Meine Trainer-Bereiche</h1> <h1>Meine Trainer-Bereiche</h1>
<p style={{ color: 'var(--text2)', marginBottom: '32px' }}> <p style={{ color: 'var(--text2)', marginBottom: '32px' }}>
Definiere deine Tätigkeitsbereiche für fokussierte Ansichten und Filter. Definiere deine Tätigkeitsbereiche für fokussierte Ansichten und Filter.

View File

@ -542,7 +542,7 @@ export default function TrainingFrameworkProgramEditPage() {
if (loading) { if (loading) {
return ( return (
<div style={{ padding: '2rem', textAlign: 'center' }}> <div className="app-page" style={{ padding: '2rem 0', textAlign: 'center' }}>
<div className="spinner" /> <div className="spinner" />
<p>Laden</p> <p>Laden</p>
</div> </div>
@ -550,7 +550,7 @@ export default function TrainingFrameworkProgramEditPage() {
} }
return ( return (
<div style={{ padding: '2rem' }}> <div className="app-page">
<div className="framework-edit"> <div className="framework-edit">
<p style={{ marginBottom: '0.75rem' }}> <p style={{ marginBottom: '0.75rem' }}>
<Link to="/planning/framework-programs" style={{ color: 'var(--accent-dark)' }}> <Link to="/planning/framework-programs" style={{ color: 'var(--accent-dark)' }}>
@ -672,7 +672,7 @@ export default function TrainingFrameworkProgramEditPage() {
</div> </div>
)} )}
<div className="form-row" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> <div className="responsive-grid-2" style={{ marginBottom: '16px' }}>
<div> <div>
<label className="form-label">Zeitraum von</label> <label className="form-label">Zeitraum von</label>
<input <input
@ -693,7 +693,7 @@ export default function TrainingFrameworkProgramEditPage() {
</div> </div>
</div> </div>
<div className="form-row" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> <div className="responsive-grid-2" style={{ marginBottom: '16px' }}>
<div> <div>
<label className="form-label">Sichtbarkeit</label> <label className="form-label">Sichtbarkeit</label>
<select <select
@ -736,6 +736,8 @@ export default function TrainingFrameworkProgramEditPage() {
<div <div
style={{ style={{
display: 'flex', display: 'flex',
flexWrap: 'wrap',
gap: '8px',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: '0.75rem', marginBottom: '0.75rem',
@ -817,7 +819,7 @@ export default function TrainingFrameworkProgramEditPage() {
</div> </div>
<div className="card framework-plan-slots" style={{ marginBottom: '1.5rem' }}> <div className="card framework-plan-slots" style={{ marginBottom: '1.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}> <h3 className="card-title" style={{ marginBottom: 0 }}>
SessionSlots & Übungen SessionSlots & Übungen
</h3> </h3>

View File

@ -41,8 +41,7 @@ export default function TrainingFrameworkProgramsListPage() {
} }
return ( return (
<div style={{ padding: '2rem' }}> <div className="app-page app-page--constrained-md">
<div style={{ maxWidth: '900px', margin: '0 auto' }}>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
@ -155,6 +154,5 @@ export default function TrainingFrameworkProgramsListPage() {
</ul> </ul>
)} )}
</div> </div>
</div>
) )
} }

View File

@ -524,7 +524,7 @@ function TrainingPlanningPage() {
if (loading) { if (loading) {
return ( return (
<div style={{ padding: '2rem', textAlign: 'center' }}> <div className="app-page" style={{ padding: '2rem 0', textAlign: 'center' }}>
<div className="spinner"></div> <div className="spinner"></div>
<p>Laden...</p> <p>Laden...</p>
</div> </div>
@ -534,8 +534,7 @@ function TrainingPlanningPage() {
const selectedGroup = groups.find((g) => g.id === parseInt(selectedGroupId, 10)) const selectedGroup = groups.find((g) => g.id === parseInt(selectedGroupId, 10))
return ( return (
<div style={{ padding: '2rem' }}> <div className="app-page app-page--constrained-lg">
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '0.35rem' }}>Trainingsplanung</h1> <h1 style={{ marginBottom: '0.35rem' }}>Trainingsplanung</h1>
<p style={{ color: 'var(--text2)', fontSize: '0.95rem', marginBottom: '1.25rem' }}> <p style={{ color: 'var(--text2)', fontSize: '0.95rem', marginBottom: '1.25rem' }}>
Wähle eine Trainingsgruppe, lege dann Termine mit Inhalt (Abschnitte und Übungen) an ein Plan entsteht aus einer oder mehreren{' '} Wähle eine Trainingsgruppe, lege dann Termine mit Inhalt (Abschnitte und Übungen) an ein Plan entsteht aus einer oder mehreren{' '}
@ -730,12 +729,14 @@ function TrainingPlanningPage() {
<div <div
style={{ style={{
display: 'flex', display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'start', alignItems: 'flex-start',
gap: '12px',
marginBottom: '1rem' marginBottom: '1rem'
}} }}
> >
<div> <div style={{ minWidth: 0, flex: '1 1 200px' }}>
<h3 style={{ marginBottom: '0.25rem' }}> <h3 style={{ marginBottom: '0.25rem' }}>
{unit.planned_date} {unit.planned_date}
{unit.planned_time_start && {unit.planned_time_start &&
@ -790,7 +791,16 @@ function TrainingPlanningPage() {
</div> </div>
</div> </div>
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}> <div
style={{
display: 'flex',
gap: '0.5rem',
flexWrap: 'wrap',
justifyContent: 'flex-end',
flex: '1 1 180px',
minWidth: 0
}}
>
<Link <Link
to={`/planning/run/${unit.id}`} to={`/planning/run/${unit.id}`}
className="btn btn-secondary" className="btn btn-secondary"
@ -849,12 +859,14 @@ function TrainingPlanningPage() {
style={{ style={{
background: 'var(--surface)', background: 'var(--surface)',
borderRadius: '12px', borderRadius: '12px',
padding: '2rem', padding: 'clamp(12px, 3vw, 2rem)',
maxWidth: '960px', maxWidth: 'min(960px, 100%)',
width: '100%', width: '100%',
maxHeight: '92vh', maxHeight: '92vh',
overflowY: 'auto', overflowY: 'auto',
margin: '1rem' margin: 'max(0px, env(safe-area-inset-top, 0px)) auto',
boxSizing: 'border-box',
minWidth: 0
}} }}
> >
<h2 style={{ marginBottom: '1rem' }}> <h2 style={{ marginBottom: '1rem' }}>
@ -885,14 +897,7 @@ function TrainingPlanningPage() {
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<h3 style={{ marginBottom: '1rem' }}>Planung</h3> <h3 style={{ marginBottom: '1rem' }}>Planung</h3>
<div <div className="responsive-grid-3" style={{ marginBottom: '1rem' }}>
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}
>
<div className="form-row"> <div className="form-row">
<label className="form-label">Datum *</label> <label className="form-label">Datum *</label>
<input <input
@ -1064,12 +1069,13 @@ function TrainingPlanningPage() {
key={`ex-${sIdx}-${iIdx}`} key={`ex-${sIdx}-${iIdx}`}
style={{ style={{
display: 'grid', display: 'grid',
gridTemplateColumns: '32px minmax(0,1fr) 88px auto', gridTemplateColumns: '32px minmax(0, 1fr) minmax(0, 72px) 40px',
gap: '6px', gap: '6px',
alignItems: 'start', alignItems: 'start',
marginTop: '0.75rem', marginTop: '0.75rem',
paddingTop: '0.5rem', paddingTop: '0.5rem',
borderTop: '1px solid rgba(0,0,0,0.06)' borderTop: '1px solid rgba(0,0,0,0.06)',
minWidth: 0
}} }}
> >
<div style={{ display: 'flex', flexDirection: 'column', paddingTop: '6px' }}> <div style={{ display: 'flex', flexDirection: 'column', paddingTop: '6px' }}>
@ -1267,14 +1273,7 @@ function TrainingPlanningPage() {
<> <>
<h3 style={{ marginTop: '0.5rem', marginBottom: '1rem' }}>Durchführung</h3> <h3 style={{ marginTop: '0.5rem', marginBottom: '1rem' }}>Durchführung</h3>
<div <div className="responsive-grid-4" style={{ marginBottom: '1rem' }}>
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}
>
<div className="form-row"> <div className="form-row">
<label className="form-label">Tatsächliches Datum</label> <label className="form-label">Tatsächliches Datum</label>
<input <input
@ -1408,7 +1407,6 @@ function TrainingPlanningPage() {
onClose={() => setPlanningPeekExerciseId(null)} onClose={() => setPlanningPeekExerciseId(null)}
/> />
</div> </div>
</div>
) )
} }

View File

@ -141,7 +141,7 @@ export default function TrainingUnitRunPage() {
} }
return ( return (
<div className="training-run-page" style={{ maxWidth: '720px', margin: '0 auto', paddingBottom: '2rem' }}> <div className="training-run-page app-page app-page--constrained-sm" style={{ paddingBottom: '2rem' }}>
<ExercisePeekModal <ExercisePeekModal
open={peekExerciseId != null} open={peekExerciseId != null}
exerciseId={peekExerciseId} exerciseId={peekExerciseId}