refactor: enhance layout and responsiveness across multiple pages
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 40s

- Updated app.css to improve responsive design, introducing new classes for consistent page widths and grid layouts.
- Refactored various page components to utilize the new layout classes, ensuring better adaptability on different screen sizes.
- Adjusted padding and margin properties for improved visual consistency and user experience across the application.
This commit is contained in:
Lars 2026-05-05 12:39:15 +02:00
parent b1af59b134
commit 83ee300192
16 changed files with 130 additions and 58 deletions

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; }
/* === 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) */
.app-main {
flex: 1;
@ -163,6 +233,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
gap: 8px;
padding: 0;
margin-bottom: 16px;
min-width: 0;
}
.form-label {
@ -183,6 +254,7 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
.form-input {
width: 100%;
min-width: 0;
padding: 10px 12px;
text-align: left;
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) */
.framework-edit {
max-width: 800px;
margin: 0 auto;
max-width: 100%;
margin: 0;
width: 100%;
min-width: 0;
}
@media (min-width: 900px) {
.framework-edit {
max-width: min(1200px, 100%);
margin-left: auto;
margin-right: auto;
}
.framework-edit__tabbar {
display: none !important;
@ -2834,6 +2908,12 @@ a.analysis-split__nav-item {
scrollbar-gutter: stable;
}
@media (max-width: 1023px) {
.framework-slots-board-outer {
scrollbar-gutter: auto;
}
}
.framework-slots-board {
display: flex;
flex-direction: row;

View File

@ -98,7 +98,7 @@ function AccountSettingsPage() {
}
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>
<p style={{ color: 'var(--text2)', marginBottom: '1.25rem', fontSize: '0.95rem' }}>
Konto &amp; Sicherheit

View File

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

View File

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

View File

@ -143,8 +143,7 @@ function ClubsPage() {
}
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="app-page app-page--constrained-lg">
<h1 style={{ marginBottom: '0.75rem' }}>Vereinsverwaltung</h1>
<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.
@ -696,7 +695,6 @@ function ClubsPage() {
</div>
</div>
)}
</div>
</div>
)
}

View File

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

View File

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

View File

@ -699,7 +699,7 @@ function ExerciseFormPage() {
}
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' }}>
<button type="button" className="btn btn-secondary" onClick={() => navigate('/exercises')}>
Übersicht

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -524,7 +524,7 @@ function TrainingPlanningPage() {
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<div className="app-page" style={{ padding: '2rem 0', textAlign: 'center' }}>
<div className="spinner"></div>
<p>Laden...</p>
</div>
@ -534,8 +534,7 @@ function TrainingPlanningPage() {
const selectedGroup = groups.find((g) => g.id === parseInt(selectedGroupId, 10))
return (
<div style={{ padding: '2rem' }}>
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
<div className="app-page app-page--constrained-lg">
<h1 style={{ marginBottom: '0.35rem' }}>Trainingsplanung</h1>
<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{' '}
@ -730,12 +729,14 @@ function TrainingPlanningPage() {
<div
style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
alignItems: 'start',
alignItems: 'flex-start',
gap: '12px',
marginBottom: '1rem'
}}
>
<div>
<div style={{ minWidth: 0, flex: '1 1 200px' }}>
<h3 style={{ marginBottom: '0.25rem' }}>
{unit.planned_date}
{unit.planned_time_start &&
@ -790,7 +791,16 @@ function TrainingPlanningPage() {
</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
to={`/planning/run/${unit.id}`}
className="btn btn-secondary"
@ -849,12 +859,14 @@ function TrainingPlanningPage() {
style={{
background: 'var(--surface)',
borderRadius: '12px',
padding: '2rem',
maxWidth: '960px',
padding: 'clamp(12px, 3vw, 2rem)',
maxWidth: 'min(960px, 100%)',
width: '100%',
maxHeight: '92vh',
overflowY: 'auto',
margin: '1rem'
margin: 'max(0px, env(safe-area-inset-top, 0px)) auto',
boxSizing: 'border-box',
minWidth: 0
}}
>
<h2 style={{ marginBottom: '1rem' }}>
@ -885,14 +897,7 @@ function TrainingPlanningPage() {
<form onSubmit={handleSubmit}>
<h3 style={{ marginBottom: '1rem' }}>Planung</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}
>
<div className="responsive-grid-3" style={{ marginBottom: '1rem' }}>
<div className="form-row">
<label className="form-label">Datum *</label>
<input
@ -1064,12 +1069,13 @@ function TrainingPlanningPage() {
key={`ex-${sIdx}-${iIdx}`}
style={{
display: 'grid',
gridTemplateColumns: '32px minmax(0,1fr) 88px auto',
gridTemplateColumns: '32px minmax(0, 1fr) minmax(0, 72px) 40px',
gap: '6px',
alignItems: 'start',
marginTop: '0.75rem',
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' }}>
@ -1267,14 +1273,7 @@ function TrainingPlanningPage() {
<>
<h3 style={{ marginTop: '0.5rem', marginBottom: '1rem' }}>Durchführung</h3>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr 1fr 1fr',
gap: '1rem',
marginBottom: '1rem'
}}
>
<div className="responsive-grid-4" style={{ marginBottom: '1rem' }}>
<div className="form-row">
<label className="form-label">Tatsächliches Datum</label>
<input
@ -1407,7 +1406,6 @@ function TrainingPlanningPage() {
exerciseId={planningPeekExerciseId}
onClose={() => setPlanningPeekExerciseId(null)}
/>
</div>
</div>
)
}

View File

@ -141,7 +141,7 @@ export default function TrainingUnitRunPage() {
}
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
open={peekExerciseId != null}
exerciseId={peekExerciseId}