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);
}
/* 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 {
max-width: 800px;
margin: 0 auto;
}
@media (min-width: 1024px) {
@media (min-width: 900px) {
.framework-edit {
max-width: min(1200px, 100%);
}
@ -2772,12 +2772,42 @@ a.analysis-split__nav-item {
.framework-edit__goals-slots {
display: block;
}
@media (max-width: 1023px) {
@media (max-width: 899px) {
.framework-edit .framework-edit__panel:not(.framework-edit__panel--active) {
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 {
.desktop-sidebar,
.bottom-nav,

View File

@ -4,6 +4,9 @@ import api from '../utils/api'
import ExercisePickerModal from '../components/ExercisePickerModal'
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() {
return { title: '', notes: '' }
}
@ -178,11 +181,13 @@ export default function TrainingFrameworkProgramEditPage() {
/** 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
typeof window !== 'undefined'
? window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`).matches
: false
)
useEffect(() => {
const mq = window.matchMedia('(min-width: 1024px)')
const mq = window.matchMedia(`(min-width: ${FRAMEWORK_DESKTOP_MIN_PX}px)`)
const apply = () => setDesktopLayout(!!mq.matches)
apply()
mq.addEventListener('change', apply)
@ -444,6 +449,10 @@ export default function TrainingFrameworkProgramEditPage() {
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) {
return (
<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' }}>
<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.
und pro Slot eine <strong>Übungsliste</strong> (Stückliste). Eine <strong>volle EinheitenStruktur</strong> wie
in der Trainingsplanung (Abschnitte, Notizen, Mikrovorlage pro Slot) ist im Konzept optional (
<strong>CURR010</strong>: <code>training_plan_template_id</code> pro Slot) in der DB derzeit{' '}
<strong>noch nicht</strong> umgesetzt.{' '}
<strong>Übernahme</strong> in konkrete Trainingseinheiten mit Referenz auf den Rahmen (Kopie, editierbar){' '}
ist <strong>Stufe3 / Lineage</strong> vorgesehen. Die SlotSpalten unten sind die geplanten
SessionPositionen <strong>ohne feste Termine</strong> am Rahmen.
</p>
</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: 'goals', label: 'Ziele' },
@ -500,7 +531,7 @@ export default function TrainingFrameworkProgramEditPage() {
'framework-edit__panel framework-edit__panel--meta card' +
(panelActive('meta') ? ' framework-edit__panel--active' : '')
}
style={{ marginBottom: '1rem' }}
style={{ marginBottom: '1rem', ...(panelVisibilityStyle('meta') || {}) }}
>
<h3 className="card-title">Stammdaten</h3>
<div className="form-row">
@ -607,13 +638,25 @@ export default function TrainingFrameworkProgramEditPage() {
</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
className={
'framework-edit__panel framework-edit__panel--goals card' +
(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' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}>
@ -681,7 +724,7 @@ export default function TrainingFrameworkProgramEditPage() {
'framework-edit__panel framework-edit__panel--slots card' +
(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' }}>
<h3 className="card-title" style={{ marginBottom: 0 }}>
@ -694,15 +737,21 @@ export default function TrainingFrameworkProgramEditPage() {
{form.slots.length === 0 ? (
<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>
) : 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) => (
<div
key={si}
className="card"
style={{ marginBottom: '12px', background: 'var(--surface)', borderStyle: 'dashed' }}
className="card framework-slot-card"
style={{ marginBottom: 0, background: 'var(--surface)', borderStyle: 'dashed' }}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginBottom: '10px' }}>
<button type="button" className="btn btn-secondary" onClick={() => moveSlot(si, -1)}>
@ -845,6 +894,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

@ -57,7 +57,11 @@ export default function TrainingFrameworkProgramsListPage() {
<h1 style={{ marginBottom: '0.35rem' }}>Trainingsrahmenprogramme</h1>
<p style={{ color: 'var(--text2)', fontSize: '0.95rem', maxWidth: '36rem' }}>
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>
</div>
<Link

View File

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