From 40a3b4b8e60c57a818411dc5febcceb485667244 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 7 May 2026 08:47:01 +0200 Subject: [PATCH] feat: enhance dashboard and exercises list UI with new styles and filtering options - 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. --- frontend/src/app.css | 68 ++++++++++++++++++- frontend/src/pages/Dashboard.jsx | 85 ++++++++++++------------ frontend/src/pages/ExercisesListPage.jsx | 77 +++++++++++++++------ 3 files changed, 163 insertions(+), 67 deletions(-) diff --git a/frontend/src/app.css b/frontend/src/app.css index 207e750..2969738 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -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)); diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 4e75cc2..031231a 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -191,49 +191,48 @@ function Dashboard() {

-
- {phase0Err ? ( -

- {phase0Err} -

- ) : null} - {!phase0Err && phase0Stats ? ( - <> - - - - - - {formatCappedCount(phase0Stats.draftCount, phase0Stats.draftCapped)} - - Übungs-Entwürfe - finalisieren - - - - - - - {formatCappedCount(phase0Stats.mineCount, phase0Stats.mineCapped)} - - Meine Übungen - alle Status - -
- - - - - {formatCappedCount(phase0Stats.ytdCompletedCount, phase0Stats.ytdCapped)} - - Gehalten {phase0Stats.year} - abrechnungsnah -
- - ) : !phase0Err ? ( -
Zahlen werden geladen…
- ) : null} -
+ {phase0Err ? ( +

+ {phase0Err} +

+ ) : null} + {!phase0Err && !phase0Stats ? ( +
Zahlen werden geladen…
+ ) : null} + {!phase0Err && phase0Stats ? ( +
+ + + + + + {formatCappedCount(phase0Stats.draftCount, phase0Stats.draftCapped)} + + Übungs-Entwürfe + finalisieren + + + + + + + {formatCappedCount(phase0Stats.mineCount, phase0Stats.mineCapped)} + + Meine Übungen + alle Status + +
+ + + + + {formatCappedCount(phase0Stats.ytdCompletedCount, phase0Stats.ytdCapped)} + + Gehalten {phase0Stats.year} + abrechnungsnah +
+
+ ) : null}
diff --git a/frontend/src/pages/ExercisesListPage.jsx b/frontend/src/pages/ExercisesListPage.jsx index 2695e8d..2be9270 100644 --- a/frontend/src/pages/ExercisesListPage.jsx +++ b/frontend/src/pages/ExercisesListPage.jsx @@ -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" /> -
- - {filterChips.length > 0 ? ( - - ) : null} + + {filterChips.length > 0 ? ( + + ) : null} +
{filterChips.length > 0 ? (