feat: enhance card layouts and UI components across multiple pages
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 10s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 29s
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 10s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 29s
- Updated CSS styles to improve card spacing and layout consistency in grid formats. - Introduced a new card-grid class for better handling of card arrangements in ClubsPage and TrainingFrameworkProgramsListPage. - Added ExerciseCardScopeStatus component to display visibility and status icons in ExercisesListPage, enhancing user feedback. - Refactored exercise card actions and footer for improved layout and accessibility. - Enhanced overall responsiveness and visual clarity across various components.
This commit is contained in:
parent
b9ef0395c1
commit
1e1fd80fb7
|
|
@ -214,12 +214,26 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
|||
|
||||
/* Cards */
|
||||
.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; }
|
||||
/* In CSS-Grids: Abstände nur über gap, nicht über Adjacent-Sibling-Margin */
|
||||
ul > li.card + li.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;
|
||||
}
|
||||
/* 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; }
|
||||
|
||||
/* 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)
|
||||
* Wechsel zwischen Admin-Seiten → .admin-top-nav
|
||||
* „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 */
|
||||
|
|
@ -3779,19 +3794,53 @@ button.capture-shell__nav-item {
|
|||
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 {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: auto;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--border);
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
.exercise-card__actions--icons {
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex-wrap: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
.exercise-card__icon-btn {
|
||||
display: inline-flex;
|
||||
|
|
@ -3868,17 +3917,18 @@ button.capture-shell__nav-item {
|
|||
color: var(--text1);
|
||||
border-color: var(--border2);
|
||||
}
|
||||
.exercise-tag--scope {
|
||||
font-weight: 700;
|
||||
background: var(--surface);
|
||||
color: var(--text2);
|
||||
|
||||
/* Liste Rahmenprogramme: Abstand nur über gap (kein .card+.card zwischen li) */
|
||||
.framework-programs-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.exercise-tag--meta {
|
||||
font-weight: 600;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
color: var(--text3);
|
||||
.framework-programs-list > li.card {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.exercise-detail-shell {
|
||||
|
|
|
|||
|
|
@ -504,11 +504,13 @@ function ClubsPage() {
|
|||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
<div
|
||||
className="card-grid clubs-groups-card-grid"
|
||||
style={{
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',
|
||||
gap: '1rem'
|
||||
}}
|
||||
>
|
||||
{groups.map(group => (
|
||||
<div key={group.id} className="card">
|
||||
<h3 style={{ marginBottom: '0.5rem' }}>{group.name}</h3>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
import React, { useState, useEffect, useMemo, useCallback } from 'react'
|
||||
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 { useAuth } from '../context/AuthContext'
|
||||
import { SKILL_LEVEL_OPTIONS } from '../constants/skillLevels'
|
||||
|
|
@ -56,6 +67,38 @@ function exerciseCardClassName(exercise, userId) {
|
|||
.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) {
|
||||
const o = LEVEL_FILTER_OPTS.find((x) => String(x.level) === String(levelStr))
|
||||
return o ? String(o.level) : String(levelStr)
|
||||
|
|
@ -1157,8 +1200,6 @@ function ExercisesListPage() {
|
|||
{typeNames.map((name) => (
|
||||
<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>
|
||||
{summaryHtml ? (
|
||||
<div
|
||||
|
|
@ -1168,7 +1209,9 @@ function ExercisesListPage() {
|
|||
) : null}
|
||||
</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
|
||||
to={`/exercises/${exercise.id}`}
|
||||
className="exercise-card__icon-btn"
|
||||
|
|
@ -1194,6 +1237,7 @@ function ExercisesListPage() {
|
|||
>
|
||||
<Trash2 size={18} strokeWidth={2} aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -159,9 +159,9 @@ export default function TrainingFrameworkProgramsListPage() {
|
|||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<ul style={{ listStyle: 'none' }}>
|
||||
<ul className="framework-programs-list">
|
||||
{rows.map((r) => (
|
||||
<li key={r.id} className="card" style={{ marginBottom: '12px' }}>
|
||||
<li key={r.id} className="card">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user