diff --git a/frontend/src/app.css b/frontend/src/app.css index 9feb71f..8e66dd1 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -6548,6 +6548,266 @@ html.modal-scroll-locked .app-main { min-width: 0; } +/* Übungsformular — Klassifikation & Meta-Chips */ +.exercise-form-meta-panel { + margin: 4px 0 16px; + padding: 14px; + border: 1px solid var(--border); + border-radius: 12px; + background: var(--surface2); +} +.exercise-form-meta-panel__title { + margin: 0 0 12px; + font-size: 1rem; + font-weight: 700; +} +.exercise-form-meta-panel__grid { + display: grid; + grid-template-columns: 1fr; + gap: 10px; +} +@media (min-width: 720px) { + .exercise-form-meta-panel__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} +.exercise-meta-block { + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 10px; + background: var(--surface); +} +.exercise-meta-block--skills { + margin-top: 10px; +} +.exercise-meta-block__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 6px; +} +.exercise-meta-block__title { + margin: 0; + font-size: 13px; + font-weight: 700; + color: var(--text1); +} +.exercise-meta-block__add { + font-size: 11px; + padding: 3px 8px; + flex-shrink: 0; +} +.exercise-meta-block__hint, +.exercise-meta-block__empty { + margin: 0 0 8px; + font-size: 12px; + color: var(--text3); + line-height: 1.35; +} +.exercise-meta-block__empty { + margin-bottom: 0; +} +.exercise-meta-block__chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.exercise-catalog-chip { + display: inline-flex; + align-items: center; + gap: 2px; + max-width: 100%; + padding: 2px 4px 2px 2px; + border-radius: 999px; + border: 1px solid var(--border); + background: var(--surface2); +} +.exercise-catalog-chip__select { + border: none; + background: transparent; + padding: 4px 8px; + font-size: 12px; + font-family: inherit; + color: var(--text1); + min-width: 0; + max-width: min(220px, 72vw); + cursor: pointer; +} +.exercise-catalog-chip__select:focus { + outline: none; +} +.exercise-catalog-chip__primary, +.exercise-catalog-chip__remove { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: none; + border-radius: 999px; + background: transparent; + color: var(--text3); + font-size: 12px; + line-height: 1; + cursor: pointer; + flex-shrink: 0; +} +.exercise-catalog-chip__primary--on { + color: var(--accent-dark); +} +.exercise-catalog-chip__remove:hover, +.exercise-catalog-chip__primary:hover { + background: color-mix(in srgb, var(--border) 40%, transparent); + color: var(--text1); +} +.exercise-skills-add { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: stretch; + margin-bottom: 10px; +} +.exercise-skills-add .skill-tree-select { + flex: 1 1 180px; + min-width: 0; +} +.exercise-skills-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.exercise-skill-chip { + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--surface2); +} +@media (min-width: 640px) { + .exercise-skill-chip { + flex-direction: row; + align-items: center; + justify-content: flex-start; + gap: 12px; + } +} +.exercise-skill-chip__identity { + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); +} +.exercise-skill-chip__name { + display: block; + font-size: 13px; + font-weight: 700; + color: var(--text1); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.exercise-skill-chip__path { + display: block; + margin-top: 2px; + font-size: 11px; + color: var(--text3); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.exercise-skill-chip__controls { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 8px 10px; + flex: 1 1 auto; + min-width: 0; +} +.exercise-skill-chip__field { + display: flex; + flex-direction: column; + gap: 3px; + min-width: 0; +} +.exercise-skill-chip__caption { + font-size: 10px; + font-weight: 600; + color: var(--text3); + text-transform: uppercase; + letter-spacing: 0.04em; +} +.exercise-skill-chip__levels { + display: flex; + flex-wrap: nowrap; + align-items: flex-end; + gap: 6px; +} +.exercise-skill-level-select { + width: 52px; + min-width: 52px; + padding: 5px 6px; + font-size: 13px; + text-align: center; +} +.exercise-skill-chip__dash { + padding-bottom: 7px; + color: var(--text3); + font-size: 12px; + font-weight: 600; +} +.exercise-intensity-segment { + display: inline-flex; + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + background: var(--surface); +} +.exercise-intensity-segment__btn { + padding: 5px 8px; + border: none; + border-right: 1px solid var(--border); + background: transparent; + font-size: 11px; + font-family: inherit; + color: var(--text2); + cursor: pointer; + white-space: nowrap; +} +.exercise-intensity-segment__btn:last-child { + border-right: none; +} +.exercise-intensity-segment__btn--active { + background: color-mix(in srgb, var(--accent) 16%, var(--surface)); + color: var(--accent-dark); + font-weight: 700; +} +.exercise-skill-chip__remove { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + margin-left: auto; + padding: 0; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--surface); + color: var(--text3); + font-size: 12px; + cursor: pointer; + flex-shrink: 0; +} +.exercise-skill-chip__remove:hover { + color: var(--danger); + border-color: color-mix(in srgb, var(--danger) 35%, var(--border)); +} + .skills-editor-row { display: grid; grid-template-columns: 1fr auto; diff --git a/frontend/src/components/exercises/ExerciseCatalogAssocEditor.jsx b/frontend/src/components/exercises/ExerciseCatalogAssocEditor.jsx new file mode 100644 index 0000000..c536181 --- /dev/null +++ b/frontend/src/components/exercises/ExerciseCatalogAssocEditor.jsx @@ -0,0 +1,97 @@ +import React from 'react' + +/** + * Kompakte Katalog-Zuordnung (Fokus, Stile, Zielgruppen …) als Chip-Zeilen. + */ +export default function ExerciseCatalogAssocEditor({ + title, + rows, + setRows, + options, + idKey, + emptyLabel, + showPrimary = true, +}) { + const setPrimary = (idx) => { + setRows(rows.map((r, i) => ({ ...r, is_primary: i === idx }))) + } + const updateRow = (idx, patch) => { + const next = rows.map((r, i) => (i === idx ? { ...r, ...patch } : r)) + if (patch.is_primary === true) { + next.forEach((r, i) => { + if (i !== idx) r.is_primary = false + }) + } + setRows(next) + } + const addRow = () => setRows([...rows, { [idKey]: '', is_primary: rows.length === 0 }]) + const removeRow = (idx) => { + const next = rows.filter((_, i) => i !== idx) + if (next.length && showPrimary && !next.some((r) => r.is_primary)) next[0].is_primary = true + setRows(next) + } + + const optionLabel = (o) => { + const parts = [] + if (o.icon) parts.push(o.icon) + parts.push(o.name) + if (o.abbreviation) parts.push(`(${o.abbreviation})`) + return parts.join(' ') + } + + return ( +
{emptyLabel}
+ ) : ( +{emptyLabel}
- )} - {rows.map((row, idx) => ( -Je Übung mehrere Fähigkeiten mit Intensität und Niveau (von–bis).
+ +Noch keine Fähigkeit zugeordnet.
+ ) : ( +