Enhance exercise listing with variant and media counts
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Has been cancelled
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Has been cancelled
- Updated the `list_exercises` function to include counts for exercise variants and media, improving data retrieval for exercise details. - Added new CSS styles for the exercise card footer to display variant and media statistics in a visually appealing manner. - Implemented `ExerciseCardContentStats` component to conditionally render variant and media counts, enhancing the user interface of exercise cards.
This commit is contained in:
parent
cb868373f4
commit
d19a1061d8
|
|
@ -2095,7 +2095,17 @@ def list_exercises(
|
||||||
FROM exercise_training_types ett
|
FROM exercise_training_types ett
|
||||||
JOIN training_types tt ON tt.id = ett.training_type_id
|
JOIN training_types tt ON tt.id = ett.training_type_id
|
||||||
WHERE ett.exercise_id = e.id
|
WHERE ett.exercise_id = e.id
|
||||||
) AS training_type_names
|
) AS training_type_names,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)::int
|
||||||
|
FROM exercise_variants ev
|
||||||
|
WHERE ev.exercise_id = e.id
|
||||||
|
) AS variant_count,
|
||||||
|
(
|
||||||
|
SELECT COUNT(*)::int
|
||||||
|
FROM exercise_media em
|
||||||
|
WHERE em.exercise_id = e.id
|
||||||
|
) AS media_count
|
||||||
{variants_sql}
|
{variants_sql}
|
||||||
FROM exercises e
|
FROM exercises e
|
||||||
LEFT JOIN profiles p ON e.created_by = p.id
|
LEFT JOIN profiles p ON e.created_by = p.id
|
||||||
|
|
@ -2118,6 +2128,8 @@ def list_exercises(
|
||||||
d["focus_area_names"] = _coerce_json_str_list(d.get("focus_area_names"))
|
d["focus_area_names"] = _coerce_json_str_list(d.get("focus_area_names"))
|
||||||
d["style_direction_names"] = _coerce_json_str_list(d.get("style_direction_names"))
|
d["style_direction_names"] = _coerce_json_str_list(d.get("style_direction_names"))
|
||||||
d["training_type_names"] = _coerce_json_str_list(d.get("training_type_names"))
|
d["training_type_names"] = _coerce_json_str_list(d.get("training_type_names"))
|
||||||
|
d["variant_count"] = int(d.get("variant_count") or 0)
|
||||||
|
d["media_count"] = int(d.get("media_count") or 0)
|
||||||
if include_variants:
|
if include_variants:
|
||||||
v = d.get("variants")
|
v = d.get("variants")
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
|
|
|
||||||
|
|
@ -4819,6 +4819,34 @@ html.modal-scroll-locked .app-main {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.exercise-card__footer-meta {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
min-width: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.exercise-card__content-stats {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text3);
|
||||||
|
}
|
||||||
|
.exercise-card__stat {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3px;
|
||||||
|
color: var(--text3);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.exercise-card__stat-num {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
.exercise-card__meta-compact {
|
.exercise-card__meta-compact {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import {
|
||||||
Archive,
|
Archive,
|
||||||
CircleDot,
|
CircleDot,
|
||||||
FilePenLine,
|
FilePenLine,
|
||||||
|
Image,
|
||||||
|
Layers,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import ExerciseRichTextBlock from '../ExerciseRichTextBlock'
|
import ExerciseRichTextBlock from '../ExerciseRichTextBlock'
|
||||||
import { coerceApiNameList } from '../../utils/sanitizeHtml'
|
import { coerceApiNameList } from '../../utils/sanitizeHtml'
|
||||||
|
|
@ -48,6 +50,52 @@ function exerciseCardClassName(exercise, userId) {
|
||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function exerciseVariantCount(exercise) {
|
||||||
|
if (typeof exercise?.variant_count === 'number' && Number.isFinite(exercise.variant_count)) {
|
||||||
|
return Math.max(0, exercise.variant_count)
|
||||||
|
}
|
||||||
|
return Array.isArray(exercise?.variants) ? exercise.variants.length : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function exerciseMediaCount(exercise) {
|
||||||
|
if (typeof exercise?.media_count === 'number' && Number.isFinite(exercise.media_count)) {
|
||||||
|
return Math.max(0, exercise.media_count)
|
||||||
|
}
|
||||||
|
return Array.isArray(exercise?.media) ? exercise.media.length : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExerciseCardContentStats({ exercise }) {
|
||||||
|
const mediaCount = exerciseMediaCount(exercise)
|
||||||
|
const variantCount = exerciseVariantCount(exercise)
|
||||||
|
if (mediaCount <= 0 && variantCount <= 0) return null
|
||||||
|
|
||||||
|
const mediaLabel =
|
||||||
|
mediaCount === 1 ? '1 Medium hinterlegt' : `${mediaCount} Medien hinterlegt`
|
||||||
|
const variantLabel =
|
||||||
|
variantCount === 1 ? '1 Variante' : `${variantCount} Varianten`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="exercise-card__content-stats">
|
||||||
|
{mediaCount > 0 ? (
|
||||||
|
<span className="exercise-card__stat" title={mediaLabel} aria-label={mediaLabel}>
|
||||||
|
<Image size={15} strokeWidth={2} aria-hidden />
|
||||||
|
<span className="exercise-card__stat-num" aria-hidden>
|
||||||
|
{mediaCount}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{variantCount > 0 ? (
|
||||||
|
<span className="exercise-card__stat" title={variantLabel} aria-label={variantLabel}>
|
||||||
|
<Layers size={15} strokeWidth={2} aria-hidden />
|
||||||
|
<span className="exercise-card__stat-num" aria-hidden>
|
||||||
|
{variantCount}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function ExerciseCardScopeStatus({ exercise }) {
|
function ExerciseCardScopeStatus({ exercise }) {
|
||||||
const v = exercise.visibility || 'private'
|
const v = exercise.visibility || 'private'
|
||||||
const s = exercise.status || 'draft'
|
const s = exercise.status || 'draft'
|
||||||
|
|
@ -138,7 +186,10 @@ export default function ExerciseListCard({ exercise, user, selectedIds, toggleSe
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="exercise-card__footer">
|
<div className="exercise-card__footer">
|
||||||
<ExerciseCardScopeStatus exercise={exercise} />
|
<div className="exercise-card__footer-meta">
|
||||||
|
<ExerciseCardScopeStatus exercise={exercise} />
|
||||||
|
<ExerciseCardContentStats exercise={exercise} />
|
||||||
|
</div>
|
||||||
<div className="exercise-card__actions exercise-card__actions--icons">
|
<div className="exercise-card__actions exercise-card__actions--icons">
|
||||||
<Link
|
<Link
|
||||||
to={`/exercises/${exercise.id}`}
|
to={`/exercises/${exercise.id}`}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user