feat: enhance dashboard and exercises list UI with new styles and filtering options
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 7s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Added responsive styles for dashboard KPI cards and exercise search actions, improving layout on smaller screens. - Refactored the Dashboard component to streamline error handling and loading states for better user experience. - Updated ExercisesListPage to include new filtering options for user-created exercises, enhancing data relevance. - Improved overall CSS organization and introduced new classes for better styling consistency across components.
This commit is contained in:
parent
3bf0af001a
commit
40a3b4b8e6
|
|
@ -2706,6 +2706,21 @@ a.analysis-split__nav-item {
|
|||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.exercise-search-bar__actions--split {
|
||||
width: 100%;
|
||||
}
|
||||
.exercise-search-bar__actions-main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.exercise-mine-toggle--active {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-light);
|
||||
color: var(--accent-dark);
|
||||
font-weight: 600;
|
||||
}
|
||||
.exercise-filter-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -3607,13 +3622,11 @@ a.analysis-split__nav-item {
|
|||
margin: 0 0 10px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--danger);
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.dashboard-phase0-kpis__loading {
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
grid-column: 1 / -1;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.dashboard-kpi-card {
|
||||
|
|
@ -3685,6 +3698,55 @@ a.analysis-split__nav-item {
|
|||
margin-top: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.dashboard-phase0-kpis {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scroll-snap-type: x proximity;
|
||||
scrollbar-width: none;
|
||||
padding: 2px 0 4px;
|
||||
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));
|
||||
}
|
||||
.dashboard-phase0-kpis::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.dashboard-kpi-card {
|
||||
flex: 0 0 auto;
|
||||
scroll-snap-align: start;
|
||||
width: min(132px, 38vw);
|
||||
min-height: 0;
|
||||
padding: 10px 10px 8px;
|
||||
gap: 2px;
|
||||
}
|
||||
.dashboard-kpi-card__icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.dashboard-kpi-card__icon svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.dashboard-kpi-card__value {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
.dashboard-kpi-card__label {
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.dashboard-kpi-card__hint {
|
||||
font-size: 0.6rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-training-preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
|
||||
|
|
|
|||
|
|
@ -191,49 +191,48 @@ function Dashboard() {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dashboard-phase0-kpis">
|
||||
{phase0Err ? (
|
||||
<p className="dashboard-phase0-kpis__err" role="alert">
|
||||
{phase0Err}
|
||||
</p>
|
||||
) : null}
|
||||
{!phase0Err && phase0Stats ? (
|
||||
<>
|
||||
<Link className="dashboard-kpi-card" to={draftsHref}>
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<FilePenLine size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.draftCount, phase0Stats.draftCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Übungs-Entwürfe</span>
|
||||
<span className="dashboard-kpi-card__hint">finalisieren</span>
|
||||
</Link>
|
||||
<Link className="dashboard-kpi-card" to={mineHref}>
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<Library size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.mineCount, phase0Stats.mineCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Meine Übungen</span>
|
||||
<span className="dashboard-kpi-card__hint">alle Status</span>
|
||||
</Link>
|
||||
<div className="dashboard-kpi-card dashboard-kpi-card--static">
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<ClipboardList size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.ytdCompletedCount, phase0Stats.ytdCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Gehalten {phase0Stats.year}</span>
|
||||
<span className="dashboard-kpi-card__hint">abrechnungsnah</span>
|
||||
</div>
|
||||
</>
|
||||
) : !phase0Err ? (
|
||||
<div className="dashboard-phase0-kpis__loading muted">Zahlen werden geladen…</div>
|
||||
) : null}
|
||||
</div>
|
||||
{phase0Err ? (
|
||||
<p className="dashboard-phase0-kpis__err" role="alert">
|
||||
{phase0Err}
|
||||
</p>
|
||||
) : null}
|
||||
{!phase0Err && !phase0Stats ? (
|
||||
<div className="dashboard-phase0-kpis__loading muted">Zahlen werden geladen…</div>
|
||||
) : null}
|
||||
{!phase0Err && phase0Stats ? (
|
||||
<div className="dashboard-phase0-kpis">
|
||||
<Link className="dashboard-kpi-card" to={draftsHref}>
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<FilePenLine size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.draftCount, phase0Stats.draftCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Übungs-Entwürfe</span>
|
||||
<span className="dashboard-kpi-card__hint">finalisieren</span>
|
||||
</Link>
|
||||
<Link className="dashboard-kpi-card" to={mineHref}>
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<Library size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.mineCount, phase0Stats.mineCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Meine Übungen</span>
|
||||
<span className="dashboard-kpi-card__hint">alle Status</span>
|
||||
</Link>
|
||||
<div className="dashboard-kpi-card dashboard-kpi-card--static">
|
||||
<span className="dashboard-kpi-card__icon" aria-hidden>
|
||||
<ClipboardList size={22} strokeWidth={2} />
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__value">
|
||||
{formatCappedCount(phase0Stats.ytdCompletedCount, phase0Stats.ytdCapped)}
|
||||
</span>
|
||||
<span className="dashboard-kpi-card__label">Gehalten {phase0Stats.year}</span>
|
||||
<span className="dashboard-kpi-card__hint">abrechnungsnah</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
|
||||
<section className="dashboard-section" aria-labelledby="dash-trainings-title">
|
||||
|
|
|
|||
|
|
@ -123,16 +123,29 @@ function levelOptionShort(levelStr) {
|
|||
return o ? String(o.level) : String(levelStr)
|
||||
}
|
||||
|
||||
function exerciseListFiltersWithDashboardUrl(mergedFilters) {
|
||||
function applyDashboardExerciseListUrl(mergedFromPrefs) {
|
||||
try {
|
||||
const sp = new URLSearchParams(window.location.search)
|
||||
if (sp.get('status') !== 'draft') return mergedFilters
|
||||
return {
|
||||
...mergedFilters,
|
||||
status_rules: [{ key: 'url-dashboard-draft', id: 'draft', mode: 'require' }],
|
||||
const mine = sp.get('mine') === '1' || sp.get('created_by_me') === '1'
|
||||
const statusDraft = sp.get('status') === 'draft'
|
||||
|
||||
if (mine) {
|
||||
const next = { ...INITIAL_EXERCISE_LIST_FILTERS }
|
||||
if (statusDraft) {
|
||||
next.status_rules = [{ key: 'url-dashboard-draft', id: 'draft', mode: 'require' }]
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
if (statusDraft) {
|
||||
return {
|
||||
...mergedFromPrefs,
|
||||
status_rules: [{ key: 'url-dashboard-draft', id: 'draft', mode: 'require' }],
|
||||
}
|
||||
}
|
||||
return mergedFromPrefs
|
||||
} catch {
|
||||
return mergedFilters
|
||||
return mergedFromPrefs
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +205,7 @@ function ExercisesListPage() {
|
|||
if (!user?.id) return
|
||||
if (prefsAppliedRef.current) return
|
||||
const merged = mergeExerciseListPrefsFromApi(user.exercise_list_prefs)
|
||||
setFilters(exerciseListFiltersWithDashboardUrl(merged))
|
||||
setFilters(applyDashboardExerciseListUrl(merged))
|
||||
try {
|
||||
const sp = new URLSearchParams(window.location.search)
|
||||
if (sp.get('mine') === '1' || sp.get('created_by_me') === '1') setMineOnly(true)
|
||||
|
|
@ -270,6 +283,14 @@ function ExercisesListPage() {
|
|||
const filterChips = useMemo(() => {
|
||||
const chips = []
|
||||
|
||||
if (mineOnly) {
|
||||
chips.push({
|
||||
key: 'mine-only',
|
||||
label: 'Nur von mir erstellt',
|
||||
onRemove: () => setMineOnly(false),
|
||||
})
|
||||
}
|
||||
|
||||
pushCatalogRuleFilterChips(chips, 'focus_rules', filters.focus_rules, focusOptions, 'Fokus', setFilters)
|
||||
|
||||
if (filters.focus_only_without) {
|
||||
|
|
@ -410,6 +431,7 @@ function ExercisesListPage() {
|
|||
|
||||
return chips
|
||||
}, [
|
||||
mineOnly,
|
||||
filters,
|
||||
focusOptions,
|
||||
styleOptions,
|
||||
|
|
@ -625,7 +647,10 @@ function ExercisesListPage() {
|
|||
}
|
||||
}
|
||||
|
||||
const resetAllFilters = useCallback(() => setFilters({ ...INITIAL_EXERCISE_LIST_FILTERS }), [])
|
||||
const resetAllFilters = useCallback(() => {
|
||||
setMineOnly(false)
|
||||
setFilters({ ...INITIAL_EXERCISE_LIST_FILTERS })
|
||||
}, [])
|
||||
|
||||
const handleSaveExerciseFilterPrefs = useCallback(async () => {
|
||||
const uid = user?.id
|
||||
|
|
@ -833,20 +858,30 @@ function ExercisesListPage() {
|
|||
list="exercise-search-titles"
|
||||
enterKeyHint="search"
|
||||
/>
|
||||
<div className="exercise-search-bar__actions">
|
||||
<button type="button" className="btn btn-secondary exercise-filter-trigger" onClick={() => setFilterModalOpen(true)}>
|
||||
Filter
|
||||
{filterChips.length > 0 ? (
|
||||
<span className="exercise-filter-badge" aria-hidden>
|
||||
{filterChips.length}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
{filterChips.length > 0 ? (
|
||||
<button type="button" className="btn" onClick={resetAllFilters}>
|
||||
Alle entfernen
|
||||
<div className="exercise-search-bar__actions exercise-search-bar__actions--split">
|
||||
<div className="exercise-search-bar__actions-main">
|
||||
<button
|
||||
type="button"
|
||||
className={'btn btn-secondary exercise-mine-toggle' + (mineOnly ? ' exercise-mine-toggle--active' : '')}
|
||||
onClick={() => setMineOnly((v) => !v)}
|
||||
title="Nur Übungen, die mit deinem Profil als Ersteller gespeichert sind"
|
||||
>
|
||||
Meine Übungen
|
||||
</button>
|
||||
) : null}
|
||||
<button type="button" className="btn btn-secondary exercise-filter-trigger" onClick={() => setFilterModalOpen(true)}>
|
||||
Filter
|
||||
{filterChips.length > 0 ? (
|
||||
<span className="exercise-filter-badge" aria-hidden>
|
||||
{filterChips.length}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
{filterChips.length > 0 ? (
|
||||
<button type="button" className="btn" onClick={resetAllFilters}>
|
||||
Alle entfernen
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{filterChips.length > 0 ? (
|
||||
<div className="exercise-filter-chips-row" role="list" aria-label="Aktive Filter">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user