UX - Filter #12
|
|
@ -214,12 +214,26 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
||||||
|
|
||||||
/* Cards */
|
/* Cards */
|
||||||
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
|
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
|
||||||
|
/* Vertikaler Rhythmus nur im normalen Blockfluss — in Grids/Flex mit gap stört margin-top zwischen Geschwistern */
|
||||||
.card + .card { margin-top: 12px; }
|
.card + .card { margin-top: 12px; }
|
||||||
/* In CSS-Grids: Abstände nur über gap, nicht über Adjacent-Sibling-Margin */
|
ul > li.card + li.card,
|
||||||
.exercises-list-grid > .card + .card,
|
.exercises-list-grid > .card + .card,
|
||||||
.ref-value-tiles-grid > .card + .card {
|
.ref-value-tiles-grid > .card + .card,
|
||||||
|
.skills-page__card-grid > .card + .card,
|
||||||
|
.dashboard-training-grid > .card + .card,
|
||||||
|
.framework-slots-board > .card + .card,
|
||||||
|
[class*="slots-board"] > .card + .card,
|
||||||
|
.card-grid > .card + .card,
|
||||||
|
.clubs-groups-card-grid > .card + .card {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
/* Optional: Raster für Karten (Abstände nur über gap); Spalten per Modifier oder inline grid-template-columns */
|
||||||
|
.card-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: stretch;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
|
||||||
|
}
|
||||||
.card-title { font-size: 13px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; }
|
.card-title { font-size: 13px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; }
|
||||||
|
|
||||||
/* Stats grid */
|
/* Stats grid */
|
||||||
|
|
@ -1509,6 +1523,7 @@ button.capture-shell__nav-item {
|
||||||
* • viele flache Tabs (z. B. Stammdaten) → .admin-page-subtabs (gleiche Chip-Idee, Edge-Scroll mobil)
|
* • viele flache Tabs (z. B. Stammdaten) → .admin-page-subtabs (gleiche Chip-Idee, Edge-Scroll mobil)
|
||||||
* Wechsel zwischen Admin-Seiten → .admin-top-nav
|
* Wechsel zwischen Admin-Seiten → .admin-top-nav
|
||||||
* „Sub-Sub“ (dritte Ebene, z. B. Editor-Spalten): bewusst in jeweiligen Feature-Layouts (Seitenleiste / Panel).
|
* „Sub-Sub“ (dritte Ebene, z. B. Editor-Spalten): bewusst in jeweiligen Feature-Layouts (Seitenleiste / Panel).
|
||||||
|
* Karten-Raster: .card-grid oder Klassen mit *list-grid* / *slots-board* / .dashboard-training-grid — dort kein .card+.card-Abstand (nur gap).
|
||||||
* ---------- */
|
* ---------- */
|
||||||
|
|
||||||
/* Admin-Kataloge: Seite „Stammdaten“ — viele Unter-Tabs, Chip-Scroll */
|
/* Admin-Kataloge: Seite „Stammdaten“ — viele Unter-Tabs, Chip-Scroll */
|
||||||
|
|
@ -3779,19 +3794,53 @@ button.capture-shell__nav-item {
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.exercise-card__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-height: 44px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.exercise-card__meta-compact {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--text3);
|
||||||
|
font-size: 0;
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.exercise-card__meta-glyph {
|
||||||
|
display: inline-flex;
|
||||||
|
color: var(--text3);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.exercise-card__meta-sep {
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.45;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0 1px;
|
||||||
|
}
|
||||||
.exercise-card__actions {
|
.exercise-card__actions {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: auto;
|
margin-top: 0;
|
||||||
padding-top: 10px;
|
padding-top: 0;
|
||||||
border-top: 1px solid var(--border);
|
border-top: none;
|
||||||
}
|
}
|
||||||
.exercise-card__actions--icons {
|
.exercise-card__actions--icons {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
.exercise-card__icon-btn {
|
.exercise-card__icon-btn {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
@ -3868,17 +3917,18 @@ button.capture-shell__nav-item {
|
||||||
color: var(--text1);
|
color: var(--text1);
|
||||||
border-color: var(--border2);
|
border-color: var(--border2);
|
||||||
}
|
}
|
||||||
.exercise-tag--scope {
|
|
||||||
font-weight: 700;
|
/* Liste Rahmenprogramme: Abstand nur über gap (kein .card+.card zwischen li) */
|
||||||
background: var(--surface);
|
.framework-programs-list {
|
||||||
color: var(--text2);
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
}
|
}
|
||||||
.exercise-tag--meta {
|
.framework-programs-list > li.card {
|
||||||
font-weight: 600;
|
margin-bottom: 0;
|
||||||
font-size: 10px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.03em;
|
|
||||||
color: var(--text3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.exercise-detail-shell {
|
.exercise-detail-shell {
|
||||||
|
|
|
||||||
|
|
@ -504,11 +504,13 @@ function ClubsPage() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{
|
<div
|
||||||
display: 'grid',
|
className="card-grid clubs-groups-card-grid"
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
|
style={{
|
||||||
gap: '1rem'
|
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
|
||||||
}}>
|
gap: '1rem'
|
||||||
|
}}
|
||||||
|
>
|
||||||
{groups.map(group => (
|
{groups.map(group => (
|
||||||
<div key={group.id} className="card">
|
<div key={group.id} className="card">
|
||||||
<h3 style={{ marginBottom: '0.5rem' }}>{group.name}</h3>
|
<h3 style={{ marginBottom: '0.5rem' }}>{group.name}</h3>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { Eye, Pencil, Trash2 } from 'lucide-react'
|
import {
|
||||||
|
Eye,
|
||||||
|
Pencil,
|
||||||
|
Trash2,
|
||||||
|
Globe,
|
||||||
|
Users,
|
||||||
|
Lock,
|
||||||
|
CheckCircle2,
|
||||||
|
Archive,
|
||||||
|
CircleDot,
|
||||||
|
FilePenLine,
|
||||||
|
} from 'lucide-react'
|
||||||
import api from '../utils/api'
|
import api from '../utils/api'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
||||||
|
|
@ -56,6 +67,38 @@ function exerciseCardClassName(exercise, userId) {
|
||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ExerciseCardScopeStatus({ exercise }) {
|
||||||
|
const v = exercise.visibility || 'private'
|
||||||
|
const s = exercise.status || 'draft'
|
||||||
|
const visLabel = visibilityLabel(v)
|
||||||
|
const stLabel = statusLabel(s)
|
||||||
|
const tip = `${visLabel} · ${stLabel}`
|
||||||
|
let VisIcon = Lock
|
||||||
|
if (v === 'official') VisIcon = Globe
|
||||||
|
else if (v === 'club') VisIcon = Users
|
||||||
|
let StatIcon = FilePenLine
|
||||||
|
if (s === 'approved') StatIcon = CheckCircle2
|
||||||
|
else if (s === 'archived') StatIcon = Archive
|
||||||
|
else if (s === 'in_review') StatIcon = CircleDot
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="exercise-card__meta-compact"
|
||||||
|
title={tip}
|
||||||
|
aria-label={`Sichtbarkeit: ${visLabel}. Status: ${stLabel}.`}
|
||||||
|
>
|
||||||
|
<span className="exercise-card__meta-glyph">
|
||||||
|
<VisIcon size={15} strokeWidth={2} aria-hidden />
|
||||||
|
</span>
|
||||||
|
<span className="exercise-card__meta-sep" aria-hidden>
|
||||||
|
·
|
||||||
|
</span>
|
||||||
|
<span className="exercise-card__meta-glyph">
|
||||||
|
<StatIcon size={15} strokeWidth={2} aria-hidden />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function levelOptionShort(levelStr) {
|
function levelOptionShort(levelStr) {
|
||||||
const o = LEVEL_FILTER_OPTS.find((x) => String(x.level) === String(levelStr))
|
const o = LEVEL_FILTER_OPTS.find((x) => String(x.level) === String(levelStr))
|
||||||
return o ? String(o.level) : String(levelStr)
|
return o ? String(o.level) : String(levelStr)
|
||||||
|
|
@ -1157,8 +1200,6 @@ function ExercisesListPage() {
|
||||||
{typeNames.map((name) => (
|
{typeNames.map((name) => (
|
||||||
<span key={`tt:${name}`} className="exercise-tag exercise-tag--training">{name}</span>
|
<span key={`tt:${name}`} className="exercise-tag exercise-tag--training">{name}</span>
|
||||||
))}
|
))}
|
||||||
<span className="exercise-tag exercise-tag--scope">{visibilityLabel(exercise.visibility)}</span>
|
|
||||||
<span className="exercise-tag exercise-tag--meta">{statusLabel(exercise.status)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
{summaryHtml ? (
|
{summaryHtml ? (
|
||||||
<div
|
<div
|
||||||
|
|
@ -1168,7 +1209,9 @@ function ExercisesListPage() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="exercise-card__actions exercise-card__actions--icons">
|
<div className="exercise-card__footer">
|
||||||
|
<ExerciseCardScopeStatus exercise={exercise} />
|
||||||
|
<div className="exercise-card__actions exercise-card__actions--icons">
|
||||||
<Link
|
<Link
|
||||||
to={`/exercises/${exercise.id}`}
|
to={`/exercises/${exercise.id}`}
|
||||||
className="exercise-card__icon-btn"
|
className="exercise-card__icon-btn"
|
||||||
|
|
@ -1194,6 +1237,7 @@ function ExercisesListPage() {
|
||||||
>
|
>
|
||||||
<Trash2 size={18} strokeWidth={2} aria-hidden />
|
<Trash2 size={18} strokeWidth={2} aria-hidden />
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -159,9 +159,9 @@ export default function TrainingFrameworkProgramsListPage() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul style={{ listStyle: 'none' }}>
|
<ul className="framework-programs-list">
|
||||||
{rows.map((r) => (
|
{rows.map((r) => (
|
||||||
<li key={r.id} className="card" style={{ marginBottom: '12px' }}>
|
<li key={r.id} className="card">
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user