feat: update layout and styles for TrainingFrameworkProgramEditPage
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 41s

- Adjusted responsive design breakpoints for mobile and desktop views in app.css.
- Introduced a new horizontal layout for slots in the framework editor, enhancing usability on wider screens.
- Updated versioning for TrainingFrameworkProgramEditPage to 1.2.0 to reflect recent changes.
- Improved tab visibility and layout management based on screen size in TrainingFrameworkProgramEditPage.jsx.
- Enhanced user guidance in TrainingFrameworkProgramsListPage with additional context on layout behavior.
This commit is contained in:
Lars 2026-05-05 12:08:03 +02:00
parent eade9af2fe
commit 88fc9d9ba5
4 changed files with 106 additions and 22 deletions

View File

@ -2713,12 +2713,12 @@ a.analysis-split__nav-item {
accent-color: var(--accent); accent-color: var(--accent);
} }
/* Rahmenprogramm bearbeiten — Mobile Tabs, Desktop Ziele | Slots nebeneinander */ /* Rahmenprogramm bearbeiten — Mobile Tabs, Desktop Ziele | Slots nebeneinander (synchron zu FRAMEWORK_DESKTOP_MIN_PX im Editor) */
.framework-edit { .framework-edit {
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
} }
@media (min-width: 1024px) { @media (min-width: 900px) {
.framework-edit { .framework-edit {
max-width: min(1200px, 100%); max-width: min(1200px, 100%);
} }
@ -2772,12 +2772,42 @@ a.analysis-split__nav-item {
.framework-edit__goals-slots { .framework-edit__goals-slots {
display: block; display: block;
} }
@media (max-width: 1023px) { @media (max-width: 899px) {
.framework-edit .framework-edit__panel:not(.framework-edit__panel--active) { .framework-edit .framework-edit__panel:not(.framework-edit__panel--active) {
display: none !important; display: none !important;
} }
} }
/* Rahmen-Editor: Slots (= SessionSpalten) horizontal, scrollbar */
.framework-slots-board {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
gap: 12px;
align-items: stretch;
overflow-x: auto;
overflow-y: hidden;
padding: 4px 2px 12px;
margin: 0 -4px;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x proximity;
}
.framework-slots-board .framework-slot-card {
flex: 0 0 min(320px, calc(100vw - 48px));
min-width: min(320px, calc(100vw - 48px));
max-height: min(70vh, 720px);
overflow-x: hidden;
overflow-y: auto;
scroll-snap-align: start;
box-sizing: border-box;
}
@media (min-width: 900px) {
.framework-slots-board .framework-slot-card {
flex-basis: 300px;
min-width: 280px;
}
}
@media print { @media print {
.desktop-sidebar, .desktop-sidebar,
.bottom-nav, .bottom-nav,

View File

@ -4,6 +4,9 @@ import api from '../utils/api'
import ExercisePickerModal from '../components/ExercisePickerModal' import ExercisePickerModal from '../components/ExercisePickerModal'
import ExercisePeekModal from '../components/ExercisePeekModal' import ExercisePeekModal from '../components/ExercisePeekModal'
/** Unter dieser Breite: Registerkarten; darüber: Ziele | Slots nebeneinander (muss zu app.css passen) */
const FRAMEWORK_DESKTOP_MIN_PX = 900
function emptyGoal() { function emptyGoal() {
return { title: '', notes: '' } return { title: '', notes: '' }
} }
@ -178,11 +181,13 @@ export default function TrainingFrameworkProgramEditPage() {
/** Nur schmal: welcher Block sichtbar — Desktop zeigt Stammdaten + zwei Spalten Ziele|Slots */ /** Nur schmal: welcher Block sichtbar — Desktop zeigt Stammdaten + zwei Spalten Ziele|Slots */
const [frameworkTab, setFrameworkTab] = useState('meta') const [frameworkTab, setFrameworkTab] = useState('meta')
const [desktopLayout, setDesktopLayout] = useState( const [desktopLayout, setDesktopLayout] = useState(
typeof window !== 'undefined' ? window.matchMedia('(min-width: 1024px)').matches : false typeof window !== 'undefined'
? window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`).matches
: false
) )
useEffect(() => { useEffect(() => {
const mq = window.matchMedia('(min-width: 1024px)') const mq = window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`)
const apply = () => setDesktopLayout(!!mq.matches) const apply = () => setDesktopLayout(!!mq.matches)
apply() apply()
mq.addEventListener('change', apply) mq.addEventListener('change', apply)
@ -444,6 +449,10 @@ export default function TrainingFrameworkProgramEditPage() {
const panelActive = (key) => desktopLayout || frameworkTab === key const panelActive = (key) => desktopLayout || frameworkTab === key
/** Schmale Ansicht: Sichtbarkeit per Inline (falls globales CSS nicht greift / altes Bundle) */
const panelVisibilityStyle = (key) =>
desktopLayout ? undefined : { display: panelActive(key) ? 'block' : 'none' }
if (loading) { if (loading) {
return ( return (
<div style={{ padding: '2rem', textAlign: 'center' }}> <div style={{ padding: '2rem', textAlign: 'center' }}>
@ -467,16 +476,38 @@ export default function TrainingFrameworkProgramEditPage() {
<div className="card" style={{ marginBottom: '1rem', background: 'var(--surface2)', borderStyle: 'dashed' }}> <div className="card" style={{ marginBottom: '1rem', background: 'var(--surface2)', borderStyle: 'dashed' }}>
<p style={{ fontSize: '0.88rem', color: 'var(--text2)', lineHeight: 1.55, margin: 0 }}> <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 <strong style={{ color: 'var(--text1)' }}>Stand dieser Funktion:</strong> Der Rahmen speichert Ziele, Slots
und Übungslisten noch <strong>ohne</strong> die feinere{' '} und pro Slot eine <strong>Übungsliste</strong> (Stückliste). Eine <strong>volle EinheitenStruktur</strong> wie
<strong>TrainingsplanStruktur pro Einheit</strong> (Abschnitte wie in der Einheitenplanung) und{' '} in der Trainingsplanung (Abschnitte, Notizen, Mikrovorlage pro Slot) ist im Konzept optional (
<strong>ohne Übernahme</strong> in die nächste konkrete Trainingseinheit (geplanter Schritt Warenkorb). <strong>CURR010</strong>: <code>training_plan_template_id</code> pro Slot) in der DB derzeit{' '}
<strong> Ziele</strong> lassen sich noch nicht einzelnen Einheiten oder Übungen zuordnen;{' '} <strong>noch nicht</strong> umgesetzt.{' '}
<strong>Progressionsketten</strong> aus dem ÜbungsGraph sind hier noch nicht eingebunden dafür sind <strong>Übernahme</strong> in konkrete Trainingseinheiten mit Referenz auf den Rahmen (Kopie, editierbar){' '}
API/DatenErweiterungen und ein Folgerelease vorgesehen. ist <strong>Stufe3 / Lineage</strong> vorgesehen. Die SlotSpalten unten sind die geplanten
SessionPositionen <strong>ohne feste Termine</strong> am Rahmen.
</p> </p>
</div> </div>
<div className="framework-edit__tabbar" role="tablist" aria-label="Bereiche"> <div
className="framework-edit__tabbar"
role="tablist"
aria-label="Bereiche"
style={
desktopLayout
? { display: 'none' }
: {
display: 'flex',
gap: 6,
marginBottom: 14,
padding: '6px 0 12px',
borderBottom: '2px solid var(--accent)',
flexWrap: 'nowrap',
overflowX: 'auto',
position: 'sticky',
top: 0,
zIndex: 6,
background: 'var(--bg)',
}
}
>
{[ {[
{ id: 'meta', label: 'Stammdaten' }, { id: 'meta', label: 'Stammdaten' },
{ id: 'goals', label: 'Ziele' }, { id: 'goals', label: 'Ziele' },
@ -500,7 +531,7 @@ export default function TrainingFrameworkProgramEditPage() {
'framework-edit__panel framework-edit__panel--meta card' + 'framework-edit__panel framework-edit__panel--meta card' +
(panelActive('meta') ? ' framework-edit__panel--active' : '') (panelActive('meta') ? ' framework-edit__panel--active' : '')
} }
style={{ marginBottom: '1rem' }} style={{ marginBottom: '1rem', ...(panelVisibilityStyle('meta') || {}) }}
> >
<h3 className="card-title">Stammdaten</h3> <h3 className="card-title">Stammdaten</h3>
<div className="form-row"> <div className="form-row">
@ -607,13 +638,25 @@ export default function TrainingFrameworkProgramEditPage() {
</div> </div>
</div> </div>
<div className="framework-edit__goals-slots"> <div
className="framework-edit__goals-slots"
style={
desktopLayout
? {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
gap: 16,
alignItems: 'start',
}
: undefined
}
>
<div <div
className={ className={
'framework-edit__panel framework-edit__panel--goals card' + 'framework-edit__panel framework-edit__panel--goals card' +
(panelActive('goals') ? ' framework-edit__panel--active' : '') (panelActive('goals') ? ' framework-edit__panel--active' : '')
} }
style={{ marginBottom: '1rem' }} style={{ marginBottom: '1rem', ...(panelVisibilityStyle('goals') || {}) }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}> <h3 className="card-title" style={{ marginBottom: 0 }}>
@ -681,7 +724,7 @@ export default function TrainingFrameworkProgramEditPage() {
'framework-edit__panel framework-edit__panel--slots card' + 'framework-edit__panel framework-edit__panel--slots card' +
(panelActive('slots') ? ' framework-edit__panel--active' : '') (panelActive('slots') ? ' framework-edit__panel--active' : '')
} }
style={{ marginBottom: '1.5rem' }} style={{ marginBottom: '1.5rem', ...(panelVisibilityStyle('slots') || {}) }}
> >
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}> <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.75rem' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}> <h3 className="card-title" style={{ marginBottom: 0 }}>
@ -694,15 +737,21 @@ export default function TrainingFrameworkProgramEditPage() {
{form.slots.length === 0 ? ( {form.slots.length === 0 ? (
<p style={{ color: 'var(--text2)', fontSize: '0.9rem' }}> <p style={{ color: 'var(--text2)', fontSize: '0.9rem' }}>
Noch keine Slots mit <strong>+ Slot</strong> legst du z.B. Woche 1 / Einheit A an und ordnest Übungen zu. Noch keine Slots mit <strong>+ Slot</strong> legst du EinheitenSpalten an (z.B. Woche 1, Einheit
A) und ordnest Übungen zu. Spalten kannst du horizontal scrollen.
</p> </p>
) : null} ) : (
<p style={{ fontSize: '0.8rem', color: 'var(--text3)', marginBottom: '10px' }}>
Slots = geplante Einheiten/Sessions im Überblick nach rechts scrollen, wenn es viele sind.
</p>
)}
<div className="framework-slots-board">
{form.slots.map((slot, si) => ( {form.slots.map((slot, si) => (
<div <div
key={si} key={si}
className="card" className="card framework-slot-card"
style={{ marginBottom: '12px', background: 'var(--surface)', borderStyle: 'dashed' }} style={{ marginBottom: 0, background: 'var(--surface)', borderStyle: 'dashed' }}
> >
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginBottom: '10px' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginBottom: '10px' }}>
<button type="button" className="btn btn-secondary" onClick={() => moveSlot(si, -1)}> <button type="button" className="btn btn-secondary" onClick={() => moveSlot(si, -1)}>
@ -845,6 +894,7 @@ export default function TrainingFrameworkProgramEditPage() {
))} ))}
</div> </div>
</div> </div>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
<button type="button" className="btn btn-primary" disabled={saving} onClick={handleSave}> <button type="button" className="btn btn-primary" disabled={saving} onClick={handleSave}>

View File

@ -57,7 +57,11 @@ export default function TrainingFrameworkProgramsListPage() {
<h1 style={{ marginBottom: '0.35rem' }}>Trainingsrahmenprogramme</h1> <h1 style={{ marginBottom: '0.35rem' }}>Trainingsrahmenprogramme</h1>
<p style={{ color: 'var(--text2)', fontSize: '0.95rem', maxWidth: '36rem' }}> <p style={{ color: 'var(--text2)', fontSize: '0.95rem', maxWidth: '36rem' }}>
Mehrere Entwicklungsziele und Übungen über SessionSlots verteilen als Vorlage in der Bibliothek oder Mehrere Entwicklungsziele und Übungen über SessionSlots verteilen als Vorlage in der Bibliothek oder
im Kontext einer Gruppe. im Kontext einer Gruppe.{' '}
<span style={{ color: 'var(--text3)', fontSize: '0.88rem' }}>
In der <strong>Bearbeitungsansicht</strong> gibt es auf schmalen Fenstern Registerkarten, auf breiten
Bildschirmen zwei Spalten (Ziele | Slots).
</span>
</p> </p>
</div> </div>
<Link <Link

View File

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