UX - Filter #12
|
|
@ -1425,6 +1425,29 @@ button.capture-shell__nav-item {
|
||||||
.admin-catalog-section {
|
.admin-catalog-section {
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exercises-page-toolbar-tabs,
|
||||||
|
.skills-page__tabs-scroll {
|
||||||
|
margin-left: calc(-1 * max(12px, env(safe-area-inset-left, 0px)));
|
||||||
|
margin-right: calc(-1 * max(12px, env(safe-area-inset-right, 0px)));
|
||||||
|
padding-left: max(12px, env(safe-area-inset-left, 0px));
|
||||||
|
padding-right: max(12px, env(safe-area-inset-right, 0px));
|
||||||
|
padding-bottom: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercises-page-toolbar-tabs::-webkit-scrollbar,
|
||||||
|
.skills-page__tabs-scroll::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exercises-page-mode-switch,
|
||||||
|
.skills-page-mode-switch {
|
||||||
|
width: max(100%, min(20rem, 100vw - 24px));
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trainingsplanung: kompakte Segmente (Gruppe / Verein) */
|
/* Trainingsplanung: kompakte Segmente (Gruppe / Verein) */
|
||||||
|
|
@ -2302,6 +2325,427 @@ button.capture-shell__nav-item {
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skills-page-modal.admin-modal-sheet {
|
||||||
|
max-width: min(600px, 100vw - 32px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin Hierarchie: Detail-Panel */
|
||||||
|
.detail-panel__title {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.detail-panel__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.detail-panel__context {
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
.detail-panel__unknown {
|
||||||
|
padding: 20px;
|
||||||
|
color: var(--text3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Seite Fähigkeiten & Methoden */
|
||||||
|
.skills-page__loading {
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.skills-page__tabs-scroll {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.skills-page__intro-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.skills-page__intro-row p {
|
||||||
|
margin: 0;
|
||||||
|
flex: 1 1 12rem;
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
.skills-page__empty {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
.skills-page__category {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.skills-page__category-title {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.skills-page__card-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.skills-page__card-grid--methods {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
}
|
||||||
|
.skills-page-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.skills-page-card__head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__meta-block {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__title--method {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__abbr {
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.skills-page-card__badge {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.skills-page-card__meta-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__chip {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--text2);
|
||||||
|
}
|
||||||
|
.skills-page-card__desc {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.skills-page-card__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
.skills-page-card__grow {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.skills-page-modal__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.skills-page-modal__submit {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Übungsliste: Kopf, Modus-Segmente, Hinweise */
|
||||||
|
.exercises-page__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.exercises-page__title {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.exercises-page-toolbar-tabs {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.exercises-page-mode-switch,
|
||||||
|
.skills-page-mode-switch {
|
||||||
|
width: 100%;
|
||||||
|
max-width: min(100%, 28rem);
|
||||||
|
}
|
||||||
|
.exercise-search-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text3);
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
.exercise-search-hint .btn {
|
||||||
|
margin-left: 6px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.exercise-search-bar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.exercise-search-bar__primary {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.exercise-bulk-toolbar {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.exercise-bulk-toolbar__meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text3);
|
||||||
|
line-height: 1.4;
|
||||||
|
flex: 1 1 200px;
|
||||||
|
}
|
||||||
|
.exercises-list-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.exercise-card-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.exercise-card-layout__check {
|
||||||
|
margin-top: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
accent-color: var(--accent);
|
||||||
|
}
|
||||||
|
.exercise-card-body-flex {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.exercise-card-title {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.exercise-card-title a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.exercise-card-title a:hover {
|
||||||
|
color: var(--accent-dark);
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.exercise-card-title a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.exercise-card-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.exercise-card-summary {
|
||||||
|
color: var(--text2);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.exercises-meta-line {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text2);
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
.exercises-meta-line--muted {
|
||||||
|
color: var(--text3);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.exercises-load-more {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.exercises-empty-text {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text2);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin Hierarchie-Baum (Fokusbereich) */
|
||||||
|
.focus-tree-root {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.focus-tree-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 4px 4px 4px 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.12s, border-color 0.12s;
|
||||||
|
}
|
||||||
|
.focus-tree-header:hover {
|
||||||
|
background: var(--surface2);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
.focus-tree-header--selected {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.focus-tree-header--selected:hover {
|
||||||
|
background: color-mix(in srgb, var(--accent) 94%, #000);
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.focus-tree-toggle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
min-height: 36px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text2);
|
||||||
|
cursor: pointer;
|
||||||
|
align-self: center;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.focus-tree-toggle:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.focus-tree-header--selected .focus-tree-toggle {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.focus-tree-header--selected .focus-tree-toggle:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
.focus-tree-header__label {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 8px 6px 2px;
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 8px;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.focus-tree-emoji {
|
||||||
|
flex-shrink: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.focus-tree-children {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-left: 8px;
|
||||||
|
padding-left: 12px;
|
||||||
|
border-left: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
.focus-tree-group {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.focus-tree-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.focus-tree-group__head {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: var(--text3);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.focus-tree-add-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.focus-tree-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--surface2);
|
||||||
|
color: var(--text1);
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
line-height: 1.35;
|
||||||
|
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
||||||
|
}
|
||||||
|
.focus-tree-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.focus-tree-item:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.focus-tree-item--selected {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.focus-tree-item--selected:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
.focus-tree-item__abbr {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.focus-tree-item__meta {
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 4px;
|
||||||
|
line-height: 1.35;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
.focus-tree-item--selected .focus-tree-item__abbr,
|
||||||
|
.focus-tree-item--selected .focus-tree-item__meta {
|
||||||
|
opacity: 0.95;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.exercise-filter-chips-row {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin-left: -2px;
|
||||||
|
margin-right: -2px;
|
||||||
|
padding-left: 2px;
|
||||||
|
padding-right: 2px;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
.exercise-filter-chips-row::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.focus-tree-children {
|
||||||
|
margin-left: 4px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.exercise-filter-modal.admin-modal-sheet {
|
.exercise-filter-modal.admin-modal-sheet {
|
||||||
max-width: min(920px, calc(100dvw - 16px));
|
max-width: min(920px, calc(100dvw - 16px));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ function DetailPanel({ item, onUpdate, focusAreas }) {
|
||||||
return <TrainingTypeDetail item={item} onUpdate={onUpdate} focusAreas={focusAreas} />
|
return <TrainingTypeDetail item={item} onUpdate={onUpdate} focusAreas={focusAreas} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div style={{ padding: '20px', color: 'var(--text3)' }}>Unbekannter Typ: {type}</div>
|
return <div className="detail-panel__unknown">Unbekannter Typ: {type}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
function FocusAreaDetail({ item, onUpdate }) {
|
function FocusAreaDetail({ item, onUpdate }) {
|
||||||
|
|
@ -57,7 +57,7 @@ function FocusAreaDetail({ item, onUpdate }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>Fokusbereich bearbeiten</h2>
|
<h2 className="detail-panel__title">Fokusbereich bearbeiten</h2>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Name *</label>
|
<label className="form-label">Name *</label>
|
||||||
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
||||||
|
|
@ -81,11 +81,11 @@ function FocusAreaDetail({ item, onUpdate }) {
|
||||||
<option value="inactive">Inaktiv</option>
|
<option value="inactive">Inaktiv</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
|
<div className="detail-panel__actions">
|
||||||
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name}>
|
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name}>
|
||||||
{saving ? 'Speichert...' : 'Speichern'}
|
{saving ? 'Speichert...' : 'Speichern'}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn" onClick={handleDelete}>Löschen</button>
|
<button type="button" className="btn btn-danger" onClick={handleDelete}>Löschen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -130,7 +130,7 @@ function StyleDirectionDetail({ item, onUpdate, focusAreas }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>Stilrichtung bearbeiten</h2>
|
<h2 className="detail-panel__title">Stilrichtung bearbeiten</h2>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Name *</label>
|
<label className="form-label">Name *</label>
|
||||||
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
||||||
|
|
@ -163,11 +163,11 @@ function StyleDirectionDetail({ item, onUpdate, focusAreas }) {
|
||||||
<option value="inactive">Inaktiv</option>
|
<option value="inactive">Inaktiv</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
|
<div className="detail-panel__actions">
|
||||||
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name || !form.focus_area_id}>
|
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name || !form.focus_area_id}>
|
||||||
{saving ? 'Speichert...' : 'Speichern'}
|
{saving ? 'Speichert...' : 'Speichern'}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn" onClick={handleDelete}>Löschen</button>
|
<button type="button" className="btn btn-danger" onClick={handleDelete}>Löschen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -212,7 +212,7 @@ function TrainingTypeDetail({ item, onUpdate, focusAreas }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>Trainingstyp bearbeiten</h2>
|
<h2 className="detail-panel__title">Trainingstyp bearbeiten</h2>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Name *</label>
|
<label className="form-label">Name *</label>
|
||||||
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
<input className="form-input" value={form.name} onChange={e => setForm({ ...form, name: e.target.value })} />
|
||||||
|
|
@ -245,11 +245,11 @@ function TrainingTypeDetail({ item, onUpdate, focusAreas }) {
|
||||||
<option value="inactive">Inaktiv</option>
|
<option value="inactive">Inaktiv</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
|
<div className="detail-panel__actions">
|
||||||
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name || !form.focus_area_id}>
|
<button className="btn btn-primary" onClick={handleSave} disabled={saving || !form.name || !form.focus_area_id}>
|
||||||
{saving ? 'Speichert...' : 'Speichern'}
|
{saving ? 'Speichert...' : 'Speichern'}
|
||||||
</button>
|
</button>
|
||||||
<button className="btn" onClick={handleDelete}>Löschen</button>
|
<button type="button" className="btn btn-danger" onClick={handleDelete}>Löschen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -284,8 +284,8 @@ function CreateStyleDirectionForm({ item, onUpdate }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>Neue Stilrichtung erstellen</h2>
|
<h2 className="detail-panel__title">Neue Stilrichtung erstellen</h2>
|
||||||
<div style={{ padding: '12px', background: 'var(--surface2)', borderRadius: '8px', marginBottom: '20px', color: 'var(--text2)' }}>
|
<div className="detail-panel__context">
|
||||||
Fokusbereich: <strong>{item.focus_area_name}</strong>
|
Fokusbereich: <strong>{item.focus_area_name}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
|
|
@ -304,7 +304,7 @@ function CreateStyleDirectionForm({ item, onUpdate }) {
|
||||||
<label className="form-label">Sortierung</label>
|
<label className="form-label">Sortierung</label>
|
||||||
<input className="form-input" type="number" value={form.sort_order} onChange={e => setForm({ ...form, sort_order: parseInt(e.target.value) || 0 })} />
|
<input className="form-input" type="number" value={form.sort_order} onChange={e => setForm({ ...form, sort_order: parseInt(e.target.value) || 0 })} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
|
<div className="detail-panel__actions">
|
||||||
<button className="btn btn-primary" onClick={handleCreate} disabled={saving || !form.name}>
|
<button className="btn btn-primary" onClick={handleCreate} disabled={saving || !form.name}>
|
||||||
{saving ? 'Erstellt...' : 'Erstellen'}
|
{saving ? 'Erstellt...' : 'Erstellen'}
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -342,8 +342,8 @@ function CreateTrainingTypeForm({ item, onUpdate }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ marginTop: 0 }}>Neuen Trainingstyp erstellen</h2>
|
<h2 className="detail-panel__title">Neuen Trainingstyp erstellen</h2>
|
||||||
<div style={{ padding: '12px', background: 'var(--surface2)', borderRadius: '8px', marginBottom: '20px', color: 'var(--text2)' }}>
|
<div className="detail-panel__context">
|
||||||
Fokusbereich: <strong>{item.focus_area_name}</strong>
|
Fokusbereich: <strong>{item.focus_area_name}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
|
|
@ -362,7 +362,7 @@ function CreateTrainingTypeForm({ item, onUpdate }) {
|
||||||
<label className="form-label">Sortierung</label>
|
<label className="form-label">Sortierung</label>
|
||||||
<input className="form-input" type="number" value={form.sort_order} onChange={e => setForm({ ...form, sort_order: parseInt(e.target.value) || 0 })} />
|
<input className="form-input" type="number" value={form.sort_order} onChange={e => setForm({ ...form, sort_order: parseInt(e.target.value) || 0 })} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '8px', marginTop: '20px' }}>
|
<div className="detail-panel__actions">
|
||||||
<button className="btn btn-primary" onClick={handleCreate} disabled={saving || !form.name}>
|
<button className="btn btn-primary" onClick={handleCreate} disabled={saving || !form.name}>
|
||||||
{saving ? 'Erstellt...' : 'Erstellen'}
|
{saving ? 'Erstellt...' : 'Erstellen'}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||||
|
|
||||||
function FocusAreaNode({ focusArea, expanded, onToggle, onSelect, selectedId, selectedType }) {
|
function FocusAreaNode({ focusArea, expanded, onToggle, onSelect, selectedId, selectedType }) {
|
||||||
const nodeId = `fa-${focusArea.id}`
|
const nodeId = `fa-${focusArea.id}`
|
||||||
|
|
@ -6,82 +7,94 @@ function FocusAreaNode({ focusArea, expanded, onToggle, onSelect, selectedId, se
|
||||||
const isSelected = selectedType === 'focus_area' && selectedId === focusArea.id
|
const isSelected = selectedType === 'focus_area' && selectedId === focusArea.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: '12px' }}>
|
<div className="focus-tree-root">
|
||||||
{/* Focus Area Header */}
|
<div className={'focus-tree-header' + (isSelected ? ' focus-tree-header--selected' : '')}>
|
||||||
<div
|
<button
|
||||||
onClick={() => onSelect(focusArea, 'focus_area')}
|
type="button"
|
||||||
style={{
|
className="focus-tree-toggle"
|
||||||
display: 'flex',
|
aria-expanded={isExpanded}
|
||||||
alignItems: 'center',
|
aria-label={isExpanded ? 'Bereich einklappen' : 'Bereich aufklappen'}
|
||||||
padding: '8px 12px',
|
onClick={(e) => {
|
||||||
borderRadius: '8px',
|
e.stopPropagation()
|
||||||
cursor: 'pointer',
|
onToggle(nodeId)
|
||||||
background: isSelected ? 'var(--accent)' : 'transparent',
|
}}
|
||||||
color: isSelected ? 'white' : 'var(--text1)',
|
|
||||||
fontWeight: 600
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
onClick={(e) => { e.stopPropagation(); onToggle(nodeId) }}
|
|
||||||
style={{ marginRight: '8px', cursor: 'pointer', fontSize: '18px' }}
|
|
||||||
>
|
>
|
||||||
{isExpanded ? '▼' : '▶'}
|
{isExpanded ? (
|
||||||
</span>
|
<ChevronDown size={18} strokeWidth={2} aria-hidden />
|
||||||
<span style={{ marginRight: '8px' }}>{focusArea.icon}</span>
|
) : (
|
||||||
<span>{focusArea.name}</span>
|
<ChevronRight size={18} strokeWidth={2} aria-hidden />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus-tree-header__label"
|
||||||
|
onClick={() => onSelect(focusArea, 'focus_area')}
|
||||||
|
>
|
||||||
|
{focusArea.icon ? (
|
||||||
|
<span className="focus-tree-emoji" aria-hidden>
|
||||||
|
{focusArea.icon}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
<span>{focusArea.name}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Children: Style Directions + Training Types */}
|
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div style={{ marginLeft: '28px', marginTop: '8px' }}>
|
<div className="focus-tree-children">
|
||||||
{/* Style Directions Section */}
|
<div className="focus-tree-group">
|
||||||
<div style={{ marginBottom: '12px' }}>
|
<div className="focus-tree-group__head">
|
||||||
<div style={{ fontSize: '12px', color: 'var(--text3)', marginBottom: '6px', textTransform: 'uppercase', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
||||||
<span>Stilrichtungen</span>
|
<span>Stilrichtungen</span>
|
||||||
<button
|
<button
|
||||||
className="btn"
|
type="button"
|
||||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
className="btn btn-secondary btn-tiny focus-tree-add-btn"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onSelect({ _createType: 'style_direction', focus_area_id: focusArea.id, focus_area_name: focusArea.name }, 'create_style_direction')
|
onSelect(
|
||||||
|
{ _createType: 'style_direction', focus_area_id: focusArea.id, focus_area_name: focusArea.name },
|
||||||
|
'create_style_direction'
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ Neu
|
+ Neu
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{focusArea.style_directions && focusArea.style_directions.map(sd => (
|
{focusArea.style_directions &&
|
||||||
<StyleDirectionNode
|
focusArea.style_directions.map((sd) => (
|
||||||
key={sd.id}
|
<StyleDirectionNode
|
||||||
styleDirection={sd}
|
key={sd.id}
|
||||||
onSelect={onSelect}
|
styleDirection={sd}
|
||||||
isSelected={selectedType === 'style_direction' && selectedId === sd.id}
|
onSelect={onSelect}
|
||||||
/>
|
isSelected={selectedType === 'style_direction' && selectedId === sd.id}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Training Types Section */}
|
<div className="focus-tree-group">
|
||||||
<div>
|
<div className="focus-tree-group__head">
|
||||||
<div style={{ fontSize: '12px', color: 'var(--text3)', marginBottom: '6px', textTransform: 'uppercase', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
||||||
<span>Trainingstypen</span>
|
<span>Trainingstypen</span>
|
||||||
<button
|
<button
|
||||||
className="btn"
|
type="button"
|
||||||
style={{ fontSize: '11px', padding: '4px 8px' }}
|
className="btn btn-secondary btn-tiny focus-tree-add-btn"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
onSelect({ _createType: 'training_type', focus_area_id: focusArea.id, focus_area_name: focusArea.name }, 'create_training_type')
|
onSelect(
|
||||||
|
{ _createType: 'training_type', focus_area_id: focusArea.id, focus_area_name: focusArea.name },
|
||||||
|
'create_training_type'
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
+ Neu
|
+ Neu
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{focusArea.training_types && focusArea.training_types.map(tt => (
|
{focusArea.training_types &&
|
||||||
<TrainingTypeNode
|
focusArea.training_types.map((tt) => (
|
||||||
key={tt.id}
|
<TrainingTypeNode
|
||||||
trainingType={tt}
|
key={tt.id}
|
||||||
onSelect={onSelect}
|
trainingType={tt}
|
||||||
isSelected={selectedType === 'training_type' && selectedId === tt.id}
|
onSelect={onSelect}
|
||||||
/>
|
isSelected={selectedType === 'training_type' && selectedId === tt.id}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -92,28 +105,26 @@ function FocusAreaNode({ focusArea, expanded, onToggle, onSelect, selectedId, se
|
||||||
function StyleDirectionNode({ styleDirection, onSelect, isSelected }) {
|
function StyleDirectionNode({ styleDirection, onSelect, isSelected }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={'focus-tree-item' + (isSelected ? ' focus-tree-item--selected' : '')}
|
||||||
onClick={() => onSelect(styleDirection, 'style_direction')}
|
onClick={() => onSelect(styleDirection, 'style_direction')}
|
||||||
style={{
|
onKeyDown={(e) => {
|
||||||
padding: '6px 12px',
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
marginBottom: '4px',
|
e.preventDefault()
|
||||||
borderRadius: '6px',
|
onSelect(styleDirection, 'style_direction')
|
||||||
cursor: 'pointer',
|
}
|
||||||
background: isSelected ? 'var(--accent)' : 'var(--surface2)',
|
|
||||||
color: isSelected ? 'white' : 'var(--text1)',
|
|
||||||
fontSize: '14px'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{styleDirection.name}
|
{styleDirection.name}
|
||||||
{styleDirection.abbreviation && (
|
{styleDirection.abbreviation ? (
|
||||||
<span style={{ marginLeft: '8px', opacity: 0.7, fontSize: '12px' }}>
|
<span className="focus-tree-item__abbr">({styleDirection.abbreviation})</span>
|
||||||
({styleDirection.abbreviation})
|
) : null}
|
||||||
</span>
|
{styleDirection.target_groups && styleDirection.target_groups.length > 0 ? (
|
||||||
)}
|
<div className="focus-tree-item__meta">
|
||||||
{styleDirection.target_groups && styleDirection.target_groups.length > 0 && (
|
Zielgruppen: {styleDirection.target_groups.map((tg) => tg.name).join(', ')}
|
||||||
<div style={{ fontSize: '11px', opacity: 0.8, marginTop: '4px' }}>
|
|
||||||
Zielgruppen: {styleDirection.target_groups.map(tg => tg.name).join(', ')}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -121,23 +132,21 @@ function StyleDirectionNode({ styleDirection, onSelect, isSelected }) {
|
||||||
function TrainingTypeNode({ trainingType, onSelect, isSelected }) {
|
function TrainingTypeNode({ trainingType, onSelect, isSelected }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={'focus-tree-item' + (isSelected ? ' focus-tree-item--selected' : '')}
|
||||||
onClick={() => onSelect(trainingType, 'training_type')}
|
onClick={() => onSelect(trainingType, 'training_type')}
|
||||||
style={{
|
onKeyDown={(e) => {
|
||||||
padding: '6px 12px',
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
marginBottom: '4px',
|
e.preventDefault()
|
||||||
borderRadius: '6px',
|
onSelect(trainingType, 'training_type')
|
||||||
cursor: 'pointer',
|
}
|
||||||
background: isSelected ? 'var(--accent)' : 'var(--surface2)',
|
|
||||||
color: isSelected ? 'white' : 'var(--text1)',
|
|
||||||
fontSize: '14px'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{trainingType.name}
|
{trainingType.name}
|
||||||
{trainingType.abbreviation && (
|
{trainingType.abbreviation ? (
|
||||||
<span style={{ marginLeft: '8px', opacity: 0.7, fontSize: '12px' }}>
|
<span className="focus-tree-item__abbr">({trainingType.abbreviation})</span>
|
||||||
({trainingType.abbreviation})
|
) : null}
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -545,65 +545,64 @@ function ExercisesListPage() {
|
||||||
|
|
||||||
if (!catalogsReady && pageTab === 'list') {
|
if (!catalogsReady && pageTab === 'list') {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
<div className="app-page">
|
||||||
<div className="spinner"></div>
|
<div className="empty-state" style={{ padding: '2.5rem 1rem' }}>
|
||||||
<p>Lade Kataloge…</p>
|
<div className="spinner" />
|
||||||
|
<p className="muted" style={{ marginTop: '12px' }}>
|
||||||
|
Lade Kataloge…
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-page">
|
<div className="app-page">
|
||||||
<div
|
<div className="exercises-page__header">
|
||||||
style={{
|
<h1 className="page-title exercises-page__title">Übungen</h1>
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: '12px',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: '8px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1 style={{ fontSize: '1.35rem' }}>Übungen</h1>
|
|
||||||
{pageTab === 'list' ? (
|
{pageTab === 'list' ? (
|
||||||
<Link to="/exercises/new" className="btn btn-primary">
|
<Link to="/exercises/new" className="btn btn-primary">
|
||||||
+ Neu
|
+ Neu
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="exercises-page-toolbar-tabs" role="tablist" aria-label="Übungen Bereiche">
|
||||||
role="tablist"
|
<div className="planning-segment-group planning-segment-group--equal exercises-page-mode-switch">
|
||||||
aria-label="Übungen Bereiche"
|
<button
|
||||||
style={{ display: 'flex', gap: '8px', marginBottom: '14px', flexWrap: 'wrap' }}
|
type="button"
|
||||||
>
|
role="tab"
|
||||||
<button
|
aria-selected={pageTab === 'list'}
|
||||||
type="button"
|
className={
|
||||||
role="tab"
|
'planning-segment-group__btn' +
|
||||||
aria-selected={pageTab === 'list'}
|
(pageTab === 'list' ? ' planning-segment-group__btn--active' : '')
|
||||||
className={pageTab === 'list' ? 'btn btn-primary' : 'btn btn-secondary'}
|
}
|
||||||
onClick={() => setPageTab('list')}
|
onClick={() => setPageTab('list')}
|
||||||
>
|
>
|
||||||
Liste
|
Liste
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
aria-selected={pageTab === 'progression'}
|
aria-selected={pageTab === 'progression'}
|
||||||
className={pageTab === 'progression' ? 'btn btn-primary' : 'btn btn-secondary'}
|
className={
|
||||||
onClick={() => setPageTab('progression')}
|
'planning-segment-group__btn' +
|
||||||
>
|
(pageTab === 'progression' ? ' planning-segment-group__btn--active' : '')
|
||||||
Progressionsgraphen
|
}
|
||||||
</button>
|
onClick={() => setPageTab('progression')}
|
||||||
|
>
|
||||||
|
Progressionsgraphen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{pageTab === 'progression' ? (
|
{pageTab === 'progression' ? (
|
||||||
<ExerciseProgressionGraphPanel />
|
<ExerciseProgressionGraphPanel />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="card exercise-search-bar" style={{ marginBottom: '12px' }}>
|
<div className="card exercise-search-bar">
|
||||||
<label className="form-label">Volltextsuche (Titel, Ziel, …)</label>
|
<label className="form-label">Volltextsuche (Titel, Ziel, …)</label>
|
||||||
<datalist id="exercise-search-titles">
|
<datalist id="exercise-search-titles">
|
||||||
{searchTitleSuggestions.map((t) => (
|
{searchTitleSuggestions.map((t) => (
|
||||||
|
|
@ -612,7 +611,7 @@ function ExercisesListPage() {
|
||||||
</datalist>
|
</datalist>
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-input"
|
className="form-input exercise-search-bar__primary"
|
||||||
placeholder="Suchbegriffe…"
|
placeholder="Suchbegriffe…"
|
||||||
value={searchInput}
|
value={searchInput}
|
||||||
onChange={(e) => setSearchInput(e.target.value)}
|
onChange={(e) => setSearchInput(e.target.value)}
|
||||||
|
|
@ -620,7 +619,6 @@ function ExercisesListPage() {
|
||||||
name="exercise-fulltext-search"
|
name="exercise-fulltext-search"
|
||||||
list="exercise-search-titles"
|
list="exercise-search-titles"
|
||||||
enterKeyHint="search"
|
enterKeyHint="search"
|
||||||
style={{ marginBottom: '10px' }}
|
|
||||||
/>
|
/>
|
||||||
<label className="form-label">Ergänzende Suche / KI-Vorbereitung (Beta)</label>
|
<label className="form-label">Ergänzende Suche / KI-Vorbereitung (Beta)</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -668,13 +666,13 @@ function ExercisesListPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: '10px', marginBottom: 0 }}>
|
<p className="exercise-search-hint">
|
||||||
Trefferliste aktualisiert sich kurz nach Eingabe. Titel der aktuellen Liste erscheinen als Vorschläge (Pfeil im
|
Trefferliste aktualisiert sich kurz nach Eingabe. Titel der aktuellen Liste erscheinen als Vorschläge (Pfeil im
|
||||||
Feld). Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
Feld). Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
||||||
{exercises.length > 0 ? (
|
{exercises.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
<button type="button" className="btn btn-secondary" style={{ marginLeft: '6px' }} onClick={toggleSelectAllPage}>
|
<button type="button" className="btn btn-secondary btn-small" onClick={toggleSelectAllPage}>
|
||||||
{allOnPageSelected ? 'Auswahl auf dieser Seite aufheben' : 'Alle auf dieser Seite auswählen'}
|
{allOnPageSelected ? 'Auswahl auf dieser Seite aufheben' : 'Alle auf dieser Seite auswählen'}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
|
|
@ -683,24 +681,15 @@ function ExercisesListPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedIds.size > 0 ? (
|
{selectedIds.size > 0 ? (
|
||||||
<div
|
<div className="card exercise-bulk-toolbar">
|
||||||
className="card"
|
|
||||||
style={{
|
|
||||||
marginBottom: '12px',
|
|
||||||
display: 'flex',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '10px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<strong>{selectedIds.size} ausgewählt</strong>
|
<strong>{selectedIds.size} ausgewählt</strong>
|
||||||
<button type="button" className="btn btn-secondary" onClick={clearSelection}>
|
<button type="button" className="btn btn-secondary btn-small" onClick={clearSelection}>
|
||||||
Auswahl aufheben
|
Auswahl aufheben
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="btn btn-primary" onClick={openBulkModal}>
|
<button type="button" className="btn btn-primary btn-small" onClick={openBulkModal}>
|
||||||
Massenänderung…
|
Massenänderung…
|
||||||
</button>
|
</button>
|
||||||
<span style={{ fontSize: '12px', color: 'var(--text3)' }}>
|
<span className="exercise-bulk-toolbar__meta">
|
||||||
Bis zu {BULK_MAX_IDS} pro Anfrage. Für „Verein“ ohne Auswahl: aktiver Vereinskontext (
|
Bis zu {BULK_MAX_IDS} pro Anfrage. Für „Verein“ ohne Auswahl: aktiver Vereinskontext (
|
||||||
<code>X-Active-Club-Id</code>
|
<code>X-Active-Club-Id</code>
|
||||||
).
|
).
|
||||||
|
|
@ -736,7 +725,7 @@ function ExercisesListPage() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
||||||
<p style={{ fontSize: '13px', color: 'var(--text2)', marginTop: 0, marginBottom: '14px' }}>
|
<p className="muted" style={{ marginTop: 0, marginBottom: '14px' }}>
|
||||||
Zwischen den Bereichen gilt <strong>UND</strong>. Innerhalb eines Feldes werden mehrere Einträge mit{' '}
|
Zwischen den Bereichen gilt <strong>UND</strong>. Innerhalb eines Feldes werden mehrere Einträge mit{' '}
|
||||||
<strong>ODER</strong> verknüpft.
|
<strong>ODER</strong> verknüpft.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -900,12 +889,12 @@ function ExercisesListPage() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
<div className="admin-modal-sheet__body exercise-filter-modal__scroll">
|
||||||
<p style={{ fontSize: '13px', color: 'var(--text2)', marginTop: 0 }}>
|
<p className="muted" style={{ marginTop: 0 }}>
|
||||||
Es werden <strong>{selectedIds.size}</strong> Übung(en) aus der aktuellen Auswahl bearbeitet. Pro Durchlauf
|
Es werden <strong>{selectedIds.size}</strong> Übung(en) aus der aktuellen Auswahl bearbeitet. Pro Durchlauf
|
||||||
höchstens {BULK_MAX_IDS}. Ohne Berechtigung bleiben Einzelübungen unverändert (siehe Hinweis nach dem
|
höchstens {BULK_MAX_IDS}. Ohne Berechtigung bleiben Einzelübungen unverändert (siehe Hinweis nach dem
|
||||||
Speichern).
|
Speichern).
|
||||||
</p>
|
</p>
|
||||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: 0, marginBottom: '14px' }}>
|
<p className="form-sub" style={{ marginTop: 0, marginBottom: '14px' }}>
|
||||||
Unter „Zuordnung ersetzen“: die gewählte Liste ersetzt die bisherige Zuordnung bei allen betroffenen
|
Unter „Zuordnung ersetzen“: die gewählte Liste ersetzt die bisherige Zuordnung bei allen betroffenen
|
||||||
Übungen vollständig (leere Auswahl = alle Zuordnungen dieser Kategorie entfernen). Die erste Auswahl gilt
|
Übungen vollständig (leere Auswahl = alle Zuordnungen dieser Kategorie entfernen). Die erste Auswahl gilt
|
||||||
als Primärzuordnung.
|
als Primärzuordnung.
|
||||||
|
|
@ -1066,7 +1055,7 @@ function ExercisesListPage() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="exercise-filter-modal__footer" style={{ justifyContent: 'flex-end' }}>
|
<div className="exercise-filter-modal__footer">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn"
|
className="btn"
|
||||||
|
|
@ -1084,52 +1073,43 @@ function ExercisesListPage() {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{listFetching && exercises.length === 0 ? (
|
{listFetching && exercises.length === 0 ? (
|
||||||
<div className="card" style={{ textAlign: 'center', padding: '2rem' }}>
|
<div className="card empty-state" style={{ padding: '2rem 1rem' }}>
|
||||||
<div className="spinner"></div>
|
<div className="spinner" />
|
||||||
<p style={{ color: 'var(--text2)', marginTop: '12px' }}>Lade Übungen…</p>
|
<p className="muted" style={{ marginTop: '12px' }}>
|
||||||
|
Lade Übungen…
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : exercises.length === 0 ? (
|
) : exercises.length === 0 ? (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
<p className="exercises-empty-text">Keine Übungen gefunden.</p>
|
||||||
Keine Übungen gefunden.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{listFetching ? (
|
{listFetching ? (
|
||||||
<p style={{ fontSize: '13px', color: 'var(--text3)', marginBottom: '8px' }}>Aktualisiere Treffer…</p>
|
<p className="exercises-meta-line exercises-meta-line--muted">Aktualisiere Treffer…</p>
|
||||||
) : null}
|
) : null}
|
||||||
<p style={{ fontSize: '13px', color: 'var(--text2)', marginBottom: '10px' }}>
|
<p className="exercises-meta-line">
|
||||||
{exercises.length} angezeigt
|
{exercises.length} angezeigt
|
||||||
{hasMore ? ' · es gibt weitere Einträge' : ''}
|
{hasMore ? ' · es gibt weitere Einträge' : ''}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div className="exercises-list-grid">
|
||||||
style={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
|
||||||
gap: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{exercises.map((exercise) => (
|
{exercises.map((exercise) => (
|
||||||
<div key={exercise.id} className="card exercise-card">
|
<div key={exercise.id} className="card exercise-card">
|
||||||
<div style={{ display: 'flex', gap: '10px', alignItems: 'flex-start' }}>
|
<div className="exercise-card-layout">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedIds.has(Number(exercise.id))}
|
checked={selectedIds.has(Number(exercise.id))}
|
||||||
onChange={() => toggleSelect(exercise.id)}
|
onChange={() => toggleSelect(exercise.id)}
|
||||||
aria-label={`„${(exercise.title || 'Übung').replace(/"/g, '')}“ auswählen`}
|
aria-label={`„${(exercise.title || 'Übung').replace(/"/g, '')}“ auswählen`}
|
||||||
style={{ marginTop: '4px', flexShrink: 0 }}
|
className="exercise-card-layout__check"
|
||||||
/>
|
/>
|
||||||
<div className="exercise-card__body" style={{ flex: 1, minWidth: 0 }}>
|
<div className="exercise-card__body exercise-card-body-flex">
|
||||||
<h3 style={{ marginBottom: '8px', fontSize: '1.05rem', lineHeight: 1.3 }}>
|
<h3 className="exercise-card-title">
|
||||||
<Link
|
<Link to={`/exercises/${exercise.id}`}>
|
||||||
to={`/exercises/${exercise.id}`}
|
|
||||||
style={{ color: 'inherit', textDecoration: 'none' }}
|
|
||||||
>
|
|
||||||
{exercise.title}
|
{exercise.title}
|
||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</h3>
|
||||||
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap', marginBottom: '8px' }}>
|
<div className="exercise-card-tags">
|
||||||
{exercise.focus_area && (
|
{exercise.focus_area && (
|
||||||
<span className="exercise-tag exercise-tag--accent">{exercise.focus_area}</span>
|
<span className="exercise-tag exercise-tag--accent">{exercise.focus_area}</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1137,7 +1117,7 @@ function ExercisesListPage() {
|
||||||
<span className="exercise-tag">{exercise.status}</span>
|
<span className="exercise-tag">{exercise.status}</span>
|
||||||
</div>
|
</div>
|
||||||
{exercise.summary && (
|
{exercise.summary && (
|
||||||
<p style={{ color: 'var(--text2)', fontSize: '13px', lineHeight: 1.4 }}>
|
<p className="exercise-card-summary">
|
||||||
{exercise.summary.length > 160
|
{exercise.summary.length > 160
|
||||||
? `${exercise.summary.slice(0, 160)}…`
|
? `${exercise.summary.slice(0, 160)}…`
|
||||||
: exercise.summary}
|
: exercise.summary}
|
||||||
|
|
@ -1154,12 +1134,7 @@ function ExercisesListPage() {
|
||||||
</Link>
|
</Link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn"
|
className="btn btn-danger btn-small"
|
||||||
style={{
|
|
||||||
background: 'var(--danger)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
|
||||||
}}
|
|
||||||
onClick={() => handleDelete(exercise)}
|
onClick={() => handleDelete(exercise)}
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
|
|
@ -1169,7 +1144,7 @@ function ExercisesListPage() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
<div style={{ textAlign: 'center', marginTop: '16px' }}>
|
<div className="exercises-load-more">
|
||||||
<button type="button" className="btn btn-secondary" disabled={loadingMore} onClick={loadMore}>
|
<button type="button" className="btn btn-secondary" disabled={loadingMore} onClick={loadMore}>
|
||||||
{loadingMore ? 'Laden…' : 'Mehr laden'}
|
{loadingMore ? 'Laden…' : 'Mehr laden'}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ function SkillsPage() {
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
<div className="skills-page__loading">
|
||||||
<div className="spinner"></div>
|
<div className="spinner"></div>
|
||||||
<p>Laden...</p>
|
<p>Laden...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -143,40 +143,38 @@ function SkillsPage() {
|
||||||
const methodsByCategory = groupByCategory(methods)
|
const methodsByCategory = groupByCategory(methods)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-page">
|
<div className="app-page skills-page">
|
||||||
<h1 style={{ marginBottom: '1.5rem' }}>Fähigkeiten & Methoden</h1>
|
<h1 className="page-title">Fähigkeiten & Methoden</h1>
|
||||||
|
|
||||||
{/* Tabs */}
|
<div className="skills-page__tabs-scroll">
|
||||||
<div style={{
|
<div
|
||||||
display: 'flex',
|
className="planning-segment-group planning-segment-group--equal skills-page-mode-switch"
|
||||||
gap: '0.5rem',
|
role="tablist"
|
||||||
marginBottom: '1.5rem',
|
aria-label="Bereich wählen"
|
||||||
borderBottom: '2px solid var(--border)'
|
>
|
||||||
}}>
|
|
||||||
{['skills', 'methods'].map(tab => (
|
{['skills', 'methods'].map(tab => (
|
||||||
<button
|
<button
|
||||||
key={tab}
|
key={tab}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
aria-selected={activeTab === tab}
|
||||||
|
className={
|
||||||
|
'planning-segment-group__btn' +
|
||||||
|
(activeTab === tab ? ' planning-segment-group__btn--active' : '')
|
||||||
|
}
|
||||||
onClick={() => setActiveTab(tab)}
|
onClick={() => setActiveTab(tab)}
|
||||||
style={{
|
|
||||||
padding: '0.75rem 1.5rem',
|
|
||||||
background: activeTab === tab ? 'var(--accent)' : 'transparent',
|
|
||||||
color: activeTab === tab ? 'white' : 'var(--text1)',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '8px 8px 0 0',
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontWeight: activeTab === tab ? 'bold' : 'normal'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{tab === 'skills' ? 'Fähigkeiten' : 'Trainingsmethoden'}
|
{tab === 'skills' ? 'Fähigkeiten' : 'Trainingsmethoden'}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Skills Tab */}
|
{/* Skills Tab */}
|
||||||
{activeTab === 'skills' && (
|
{activeTab === 'skills' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1rem' }}>
|
<div className="skills-page__intro-row">
|
||||||
<p style={{ color: 'var(--text2)' }}>
|
<p>
|
||||||
Fähigkeiten sind Kompetenzen, die in Übungen trainiert werden.
|
Fähigkeiten sind Kompetenzen, die in Übungen trainiert werden.
|
||||||
</p>
|
</p>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
|
|
@ -188,60 +186,46 @@ function SkillsPage() {
|
||||||
|
|
||||||
{Object.keys(skillsByCategory).length === 0 ? (
|
{Object.keys(skillsByCategory).length === 0 ? (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
<p className="skills-page__empty">
|
||||||
Keine Fähigkeiten gefunden
|
Keine Fähigkeiten gefunden
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
Object.keys(skillsByCategory).sort().map(category => (
|
Object.keys(skillsByCategory).sort().map(category => (
|
||||||
<div key={category} style={{ marginBottom: '2rem' }}>
|
<div key={category} className="skills-page__category">
|
||||||
<h2 style={{ marginBottom: '1rem', textTransform: 'capitalize' }}>
|
<h2 className="skills-page__category-title">
|
||||||
{category}
|
{category}
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{
|
<div className="skills-page__card-grid">
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
|
||||||
gap: '1rem'
|
|
||||||
}}>
|
|
||||||
{skillsByCategory[category].map(skill => (
|
{skillsByCategory[category].map(skill => (
|
||||||
<div key={skill.id} className="card">
|
<div key={skill.id} className="card skills-page-card">
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', marginBottom: '0.5rem' }}>
|
<div className="skills-page-card__head">
|
||||||
<h3 style={{ fontSize: '1rem' }}>{skill.name}</h3>
|
<h3 className="skills-page-card__title">{skill.name}</h3>
|
||||||
{skill.importance && (
|
{skill.importance && (
|
||||||
<span style={{
|
<span className="skills-page-card__badge">
|
||||||
fontSize: '0.875rem',
|
|
||||||
padding: '0.25rem 0.5rem',
|
|
||||||
borderRadius: '4px',
|
|
||||||
background: 'var(--accent)',
|
|
||||||
color: 'white'
|
|
||||||
}}>
|
|
||||||
⭐ {skill.importance}/5
|
⭐ {skill.importance}/5
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{skill.description && (
|
{skill.description && (
|
||||||
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '1rem' }}>
|
<p className="skills-page-card__desc">
|
||||||
{skill.description}
|
{skill.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', marginTop: 'auto' }}>
|
<div className="skills-page-card__actions">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
type="button"
|
||||||
style={{ flex: 1 }}
|
className="btn btn-secondary skills-page-card__grow"
|
||||||
onClick={() => handleEdit(skill, 'skill')}
|
onClick={() => handleEdit(skill, 'skill')}
|
||||||
>
|
>
|
||||||
Bearbeiten
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn"
|
type="button"
|
||||||
style={{
|
className="btn btn-danger"
|
||||||
background: 'var(--danger)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
onClick={() => handleDelete(skill, 'skill')}
|
onClick={() => handleDelete(skill, 'skill')}
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
|
|
@ -260,8 +244,8 @@ function SkillsPage() {
|
||||||
{/* Methods Tab */}
|
{/* Methods Tab */}
|
||||||
{activeTab === 'methods' && (
|
{activeTab === 'methods' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1rem' }}>
|
<div className="skills-page__intro-row">
|
||||||
<p style={{ color: 'var(--text2)' }}>
|
<p>
|
||||||
Trainingsmethoden sind didaktische Ansätze für die Trainingsgestaltung.
|
Trainingsmethoden sind didaktische Ansätze für die Trainingsgestaltung.
|
||||||
</p>
|
</p>
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
|
|
@ -273,52 +257,36 @@ function SkillsPage() {
|
||||||
|
|
||||||
{Object.keys(methodsByCategory).length === 0 ? (
|
{Object.keys(methodsByCategory).length === 0 ? (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
<p className="skills-page__empty">
|
||||||
Keine Trainingsmethoden gefunden
|
Keine Trainingsmethoden gefunden
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
Object.keys(methodsByCategory).sort().map(category => (
|
Object.keys(methodsByCategory).sort().map(category => (
|
||||||
<div key={category} style={{ marginBottom: '2rem' }}>
|
<div key={category} className="skills-page__category">
|
||||||
<h2 style={{ marginBottom: '1rem', textTransform: 'capitalize' }}>
|
<h2 className="skills-page__category-title">
|
||||||
{category}
|
{category}
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{
|
<div className="skills-page__card-grid skills-page__card-grid--methods">
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))',
|
|
||||||
gap: '1rem'
|
|
||||||
}}>
|
|
||||||
{methodsByCategory[category].map(method => (
|
{methodsByCategory[category].map(method => (
|
||||||
<div key={method.id} className="card">
|
<div key={method.id} className="card skills-page-card">
|
||||||
<div style={{ marginBottom: '0.5rem' }}>
|
<div className="skills-page-card__meta-block">
|
||||||
<h3 style={{ fontSize: '1rem', marginBottom: '0.25rem' }}>
|
<h3 className="skills-page-card__title skills-page-card__title--method">
|
||||||
{method.name}
|
{method.name}
|
||||||
{method.abbreviation && (
|
{method.abbreviation && (
|
||||||
<span style={{ color: 'var(--text2)', fontSize: '0.875rem', marginLeft: '0.5rem' }}>
|
<span className="skills-page-card__abbr">
|
||||||
({method.abbreviation})
|
({method.abbreviation})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
<div className="skills-page-card__meta-row">
|
||||||
{method.typical_duration && (
|
{method.typical_duration && (
|
||||||
<span style={{
|
<span className="skills-page-card__chip">
|
||||||
fontSize: '0.75rem',
|
|
||||||
padding: '0.25rem 0.5rem',
|
|
||||||
borderRadius: '4px',
|
|
||||||
background: 'var(--surface2)',
|
|
||||||
color: 'var(--text2)'
|
|
||||||
}}>
|
|
||||||
⏱️ {method.typical_duration} min
|
⏱️ {method.typical_duration} min
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{method.typical_group_size && (
|
{method.typical_group_size && (
|
||||||
<span style={{
|
<span className="skills-page-card__chip">
|
||||||
fontSize: '0.75rem',
|
|
||||||
padding: '0.25rem 0.5rem',
|
|
||||||
borderRadius: '4px',
|
|
||||||
background: 'var(--surface2)',
|
|
||||||
color: 'var(--text2)'
|
|
||||||
}}>
|
|
||||||
👥 {method.typical_group_size}
|
👥 {method.typical_group_size}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -326,27 +294,23 @@ function SkillsPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{method.description && (
|
{method.description && (
|
||||||
<p style={{ color: 'var(--text2)', fontSize: '0.875rem', marginBottom: '1rem' }}>
|
<p className="skills-page-card__desc">
|
||||||
{method.description}
|
{method.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', marginTop: 'auto' }}>
|
<div className="skills-page-card__actions">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
type="button"
|
||||||
style={{ flex: 1 }}
|
className="btn btn-secondary skills-page-card__grow"
|
||||||
onClick={() => handleEdit(method, 'method')}
|
onClick={() => handleEdit(method, 'method')}
|
||||||
>
|
>
|
||||||
Bearbeiten
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn"
|
type="button"
|
||||||
style={{
|
className="btn btn-danger"
|
||||||
background: 'var(--danger)',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
onClick={() => handleDelete(method, 'method')}
|
onClick={() => handleDelete(method, 'method')}
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
|
|
@ -364,36 +328,37 @@ function SkillsPage() {
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{showModal && isAdmin && (
|
{showModal && isAdmin && (
|
||||||
<div style={{
|
<div
|
||||||
position: 'fixed',
|
className="admin-modal-backdrop"
|
||||||
top: 0,
|
role="presentation"
|
||||||
left: 0,
|
onClick={(e) => {
|
||||||
right: 0,
|
if (e.target === e.currentTarget) setShowModal(false)
|
||||||
bottom: 0,
|
}}
|
||||||
background: 'rgba(0,0,0,0.5)',
|
>
|
||||||
display: 'flex',
|
<div
|
||||||
alignItems: 'center',
|
className="admin-modal-sheet skills-page-modal"
|
||||||
justifyContent: 'center',
|
role="dialog"
|
||||||
zIndex: 1000,
|
aria-modal="true"
|
||||||
padding: '1rem'
|
aria-labelledby="skills-page-modal-title"
|
||||||
}}>
|
onClick={(e) => e.stopPropagation()}
|
||||||
<div style={{
|
>
|
||||||
background: 'var(--surface)',
|
<div className="admin-modal-sheet__header">
|
||||||
borderRadius: '12px',
|
<h2 id="skills-page-modal-title" className="admin-modal-sheet__title">
|
||||||
padding: '2rem',
|
{editing
|
||||||
maxWidth: '600px',
|
? (modalType === 'skill' ? 'Fähigkeit bearbeiten' : 'Methode bearbeiten')
|
||||||
width: '100%',
|
: (modalType === 'skill' ? 'Neue Fähigkeit' : 'Neue Methode')
|
||||||
maxHeight: '90vh',
|
}
|
||||||
overflowY: 'auto'
|
</h2>
|
||||||
}}>
|
<button
|
||||||
<h2 style={{ marginBottom: '1.5rem' }}>
|
type="button"
|
||||||
{editing
|
className="btn btn-secondary admin-modal-sheet__close"
|
||||||
? (modalType === 'skill' ? 'Fähigkeit bearbeiten' : 'Methode bearbeiten')
|
onClick={() => setShowModal(false)}
|
||||||
: (modalType === 'skill' ? 'Neue Fähigkeit' : 'Neue Methode')
|
>
|
||||||
}
|
Schließen
|
||||||
</h2>
|
</button>
|
||||||
|
</div>
|
||||||
<form onSubmit={handleSubmit}>
|
<div className="admin-modal-sheet__body">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Name *</label>
|
<label className="form-label">Name *</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -455,7 +420,7 @@ function SkillsPage() {
|
||||||
|
|
||||||
{modalType === 'method' && (
|
{modalType === 'method' && (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
|
<div className="exercise-filters-modal-grid exercise-filters-modal-grid--two">
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label className="form-label">Typische Dauer (min)</label>
|
<label className="form-label">Typische Dauer (min)</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -492,8 +457,8 @@ function SkillsPage() {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '1.5rem' }}>
|
<div className="skills-page-modal__footer">
|
||||||
<button type="submit" className="btn btn-primary" style={{ flex: 1 }}>
|
<button type="submit" className="btn btn-primary skills-page-modal__submit">
|
||||||
{editing ? 'Speichern' : 'Erstellen'}
|
{editing ? 'Speichern' : 'Erstellen'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|
@ -505,6 +470,7 @@ function SkillsPage() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user