diff --git a/frontend/src/app.css b/frontend/src/app.css index cd5bc65..eb93c94 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -1213,6 +1213,22 @@ button.capture-shell__nav-item { border-left: 1.5px solid var(--border2); } +/* Gleich breite Segment-Buttons (z. B. mobile Rahmenprogramm-Tabs) */ +.planning-segment-group--equal { + flex: 1; + min-width: 0; +} +.planning-segment-group--equal .planning-segment-group__btn { + flex: 1; + min-width: 0; +} + +/* Etwas größere Segmente (Planung: Liste / Kalender) */ +.planning-segment-group--comfort .planning-segment-group__btn { + padding: 10px 18px; + font-size: 0.92rem; +} + /* Ausklappbare Kontext-Hilfe (Filterzeile Planung) */ .planning-filter-help { flex: 1 1 100%; @@ -1246,6 +1262,109 @@ button.capture-shell__nav-item { } } +/* Rahmenprogramm-Editor: Kurz-Einstieg ausklappbar */ +.framework-edit-intro { + margin-bottom: 1rem; +} +.framework-edit-intro__summary { + cursor: pointer; + font-size: 0.88rem; + font-weight: 600; + color: var(--accent-dark); + list-style: none; + user-select: none; + padding: 10px 12px; + border-radius: 10px; + border: 1px dashed var(--border2); + background: var(--surface2); +} +.framework-edit-intro__summary::-webkit-details-marker { + display: none; +} +.framework-edit-intro__body { + margin-top: 10px; + padding: 12px 14px; + font-size: 0.88rem; + line-height: 1.55; + color: var(--text2); + border-radius: 10px; + border: 1px solid var(--border); + background: var(--surface); +} +@media (prefers-color-scheme: dark) { + .framework-edit-intro__summary { + color: var(--accent); + } +} + +/* Admin: Zuordnungsmatrix (Stilrichtungen ↔ Zielgruppen) */ +.admin-assignments-wrap { + background: var(--surface); + border-radius: 12px; + padding: 20px; +} +.admin-assignments-wrap__title { + margin-top: 0; + font-size: 1.15rem; + font-weight: 700; +} +.admin-assignments-matrix-container { + overflow-x: auto; + margin-top: 20px; + -webkit-overflow-scrolling: touch; +} +.admin-assignments-matrix { + width: 100%; + border-collapse: collapse; + min-width: 600px; +} +.admin-assignments-matrix th, +.admin-assignments-matrix td { + border: 1px solid var(--border); + padding: 12px; +} +.admin-assignments-matrix th { + background: var(--surface2); + font-weight: 600; + color: var(--text1); +} +.admin-assignments-matrix__corner { + position: sticky; + left: 0; + background: var(--surface); + z-index: 2; +} +.admin-assignments-matrix__row-label { + position: sticky; + left: 0; + background: var(--surface); + z-index: 1; + padding: 12px; + font-weight: 500; +} +.admin-assignments-matrix tbody tr:hover { + background: var(--surface2); +} +.admin-assignments-matrix__focus-header td { + background: var(--surface2); + padding: 8px 12px; + font-weight: 600; + color: var(--text2); +} +.admin-assignments-matrix__th-narrow { + text-align: center; + padding: 12px; +} +@media (max-width: 768px) { + .admin-assignments-matrix { + font-size: 14px; + } + .admin-assignments-matrix th, + .admin-assignments-matrix td { + padding: 8px; + } +} + /* Admin: Split-Layout wie .analysis-split (nur Gruppen in der Nav) */ .admin-shell { width: 100%; @@ -2962,36 +3081,26 @@ button.capture-shell__nav-item { } .framework-edit__tabbar { display: flex; - gap: 6px; + align-items: stretch; + gap: 8px; margin-bottom: 14px; - padding: 2px 0 12px; + padding: 6px 0 12px; border-bottom: 1px solid var(--border); flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; + position: sticky; + top: 0; + z-index: 6; + background: var(--bg); } .framework-edit__tabbar::-webkit-scrollbar { display: none; } -.framework-edit__tab { - flex: 1 1 0; +.framework-edit__tabbar .planning-segment-group { + flex: 1; 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__plan-stack { display: flex; diff --git a/frontend/src/components/admin/AssignmentsTab.jsx b/frontend/src/components/admin/AssignmentsTab.jsx index 83e00b6..8fcd875 100644 --- a/frontend/src/components/admin/AssignmentsTab.jsx +++ b/frontend/src/components/admin/AssignmentsTab.jsx @@ -5,22 +5,24 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e const [saving, setSaving] = useState(false) if (loading) { - return
+ return ( +
+
+
+ ) } async function toggleAssignment(styleDirectionId, targetGroupId, currentlyAssigned) { setSaving(true) try { if (currentlyAssigned) { - // Find and delete the assignment const assignment = assignments.find( - a => a.style_direction_id === styleDirectionId && a.target_group_id === targetGroupId + (a) => a.style_direction_id === styleDirectionId && a.target_group_id === targetGroupId ) if (assignment) { await api.deleteStyleDirectionTargetGroup(assignment.id) } } else { - // Create new assignment await api.createStyleDirectionTargetGroup({ style_direction_id: styleDirectionId, target_group_id: targetGroupId, @@ -37,11 +39,10 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e function isAssigned(styleDirectionId, targetGroupId) { return assignments.some( - a => a.style_direction_id === styleDirectionId && a.target_group_id === targetGroupId + (a) => a.style_direction_id === styleDirectionId && a.target_group_id === targetGroupId ) } - // Group style directions by focus area const groupedStyles = styleDirections.reduce((acc, sd) => { const key = sd.focus_area_name || 'Ohne Fokusbereich' if (!acc[key]) acc[key] = [] @@ -50,30 +51,30 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e }, {}) return ( -
-

Zuordnungen: Stilrichtungen ↔ Zielgruppen

- {error &&
{error}
} +
+

Zuordnungen: Stilrichtungen ↔ Zielgruppen

+ {error &&
{error}
} {targetGroups.length === 0 && ( -
- Keine Zielgruppen vorhanden. Bitte erst im Tab "Kataloge" anlegen. +
+ Keine Zielgruppen vorhanden. Bitte zuerst unter Kataloge anlegen.
)} {styleDirections.length === 0 && ( -
- Keine Stilrichtungen vorhanden. Bitte erst im Tab "Hierarchie" anlegen. +
+ Keine Stilrichtungen vorhanden. Bitte zuerst unter Hierarchie anlegen.
)} {targetGroups.length > 0 && styleDirections.length > 0 && ( -
- +
+
- - {targetGroups.map(tg => ( - + {targetGroups.map((tg) => ( + ))} @@ -82,17 +83,18 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e {Object.entries(groupedStyles).map(([focusAreaName, styles]) => ( - - + - {styles.map(sd => ( + {styles.map((sd) => ( - - {targetGroups.map(tg => { + + {targetGroups.map((tg) => { const assigned = isAssigned(sd.id, tg.id) return ( ) @@ -114,45 +117,6 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e
Stilrichtung + Stilrichtung {tg.name}
+
{focusAreaName}
- {sd.name} - {sd.name} @@ -101,7 +103,8 @@ function AssignmentsTab({ styleDirections, targetGroups, assignments, loading, e checked={assigned} onChange={() => toggleAssignment(sd.id, tg.id, assigned)} disabled={saving} - style={{ width: '20px', height: '20px', cursor: 'pointer' }} + aria-label={`${sd.name} — ${tg.name}`} + style={{ width: '20px', height: '20px', cursor: 'pointer', accentColor: 'var(--accent)' }} />
)} - -
) } diff --git a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx index 8c2de40..8d7cb57 100644 --- a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx +++ b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx @@ -663,52 +663,39 @@ export default function TrainingFrameworkProgramEditPage() {

{isNew ? 'Neues Rahmenprogramm' : 'Rahmenprogramm bearbeiten'}

-
-

+

+ + Kurz erklärt: Was ist ein Rahmenprogramm? + +
Rahmenprogramm (Bibliothek): Wiederverwendbare Vorlage mit - Zielen und Session‑Slots. Zuordnung zu Gruppe oder Kalendertermin erfolgt aus der{' '} + Zielen und Session‑Slots. Die Zuordnung zu Gruppe oder Kalendertermin erfolgt aus der{' '} Gruppen‑Planung („Übernahme“). Pro Slot planst du den Ablauf wie bei einer Trainingsseinheit:{' '} Abschnitte, Übungen mit Varianten und Dauer, Zwischen‑Anmerkungen. -

-
+
+ -
+
+ {[ + { id: 'meta', label: 'Stammdaten' }, + { id: 'plan', label: 'Plan (Ziele & Sessions)' }, + ].map((t) => ( + - ))} + onClick={() => setFrameworkTab(t.id)} + > + {t.label} + + ))} +
-

Trainingsrahmenprogramme

-

- Wiederverwendbare Vorlagen für Ziele und Sessions. Die Verknüpfung mit{' '} - konkreten Gruppeneinheiten erfolgt aus der Planung der Gruppe (Übernahme - mit Bezug zum Rahmen). +

+ Trainingsrahmenprogramme +

+

+ Vorlagen für Ziele und Sessions — die Verknüpfung mit Gruppenterminen erfolgt in der{' '} + + Trainingsplanung + + .

+
+ Mehr zur Übernahme in die Planung +
+ Unter Planung wählst du eine Gruppe und übernimmst Slots aus einem Rahmenprogramm in + echte Termine. So bleibt die Bibliothek wiederverwendbar, ohne dass Einzelgruppen fest verdrahtet sind. +
+
@@ -904,17 +891,10 @@ function TrainingPlanningPage() { return prev || new Date().toISOString().slice(0, 7) }) }} - style={{ - border: 'none', - borderLeft: '1.5px solid var(--border2)', - padding: '10px 20px', - fontWeight: 600, - fontSize: '0.92rem', - cursor: 'pointer', - background: planView === 'calendar' ? 'var(--accent-dark)' : 'transparent', - color: planView === 'calendar' ? '#fff' : 'var(--text1)', - whiteSpace: 'nowrap', - }} + className={ + 'planning-segment-group__btn' + + (planView === 'calendar' ? ' planning-segment-group__btn--active' : '') + } > Kalender @@ -927,8 +907,8 @@ function TrainingPlanningPage() {

- Wähle eine Trainingsgruppe, lege dann Termine mit Inhalt (Abschnitte und Übungen) an — ein Plan entsteht aus einer oder mehreren{' '} - Trainingseinheiten im gewählten Zeitraum. + Wähle eine Trainingsgruppe und lege Trainingseinheiten für den Zeitraum an (Inhalt: Abschnitte + und Übungen).