refactor: update loading state management and enhance search functionality in ExercisesListPage
- Replaced loading state with listFetching for clearer intent in managing exercise list fetching. - Introduced search title suggestions for improved user experience during exercise searches. - Updated UI elements to reflect changes in loading states and added datalist for search inputs, enhancing usability. - Adjusted text for loading indicators to provide a more localized experience.
This commit is contained in:
parent
756263bad4
commit
d5fbc2cd5c
|
|
@ -34,7 +34,7 @@ function ExercisesListPage() {
|
|||
skills: [],
|
||||
})
|
||||
const [catalogsReady, setCatalogsReady] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [listFetching, setListFetching] = useState(false)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [offset, setOffset] = useState(0)
|
||||
const [hasMore, setHasMore] = useState(false)
|
||||
|
|
@ -222,6 +222,12 @@ function ExercisesListPage() {
|
|||
statusOptions,
|
||||
])
|
||||
|
||||
/** Für Browser-/datalist-Vorschläge (aktuelle Treffer-Titel, begrenzt) */
|
||||
const searchTitleSuggestions = useMemo(() => {
|
||||
const titles = exercises.map((e) => (e.title || '').trim()).filter(Boolean)
|
||||
return [...new Set(titles)].slice(0, 80)
|
||||
}, [exercises])
|
||||
|
||||
const queryBase = useMemo(() => {
|
||||
const q = {}
|
||||
const n = (v) => (v === '' || v == null ? undefined : Number(v))
|
||||
|
|
@ -284,7 +290,7 @@ function ExercisesListPage() {
|
|||
if (!catalogsReady) return
|
||||
let cancelled = false
|
||||
const run = async () => {
|
||||
setLoading(true)
|
||||
setListFetching(true)
|
||||
setOffset(0)
|
||||
try {
|
||||
const batch = await api.listExercises({ ...queryBase, limit: PAGE_SIZE, offset: 0 })
|
||||
|
|
@ -298,7 +304,7 @@ function ExercisesListPage() {
|
|||
alert('Fehler beim Laden: ' + err.message)
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false)
|
||||
if (!cancelled) setListFetching(false)
|
||||
}
|
||||
}
|
||||
run()
|
||||
|
|
@ -334,11 +340,11 @@ function ExercisesListPage() {
|
|||
|
||||
const resetAllFilters = useCallback(() => setFilters({ ...INITIAL_FILTERS }), [])
|
||||
|
||||
if (!catalogsReady || loading) {
|
||||
if (!catalogsReady) {
|
||||
return (
|
||||
<div style={{ padding: '2rem', textAlign: 'center' }}>
|
||||
<div className="spinner"></div>
|
||||
<p>Laden...</p>
|
||||
<p>Lade Kataloge…</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -363,13 +369,21 @@ function ExercisesListPage() {
|
|||
|
||||
<div className="card exercise-search-bar" style={{ marginBottom: '12px' }}>
|
||||
<label className="form-label">Volltextsuche (Titel, Ziel, …)</label>
|
||||
<datalist id="exercise-search-titles">
|
||||
{searchTitleSuggestions.map((t) => (
|
||||
<option key={t} value={t} />
|
||||
))}
|
||||
</datalist>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder="Suchbegriffe…"
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
autoComplete="on"
|
||||
name="exercise-fulltext-search"
|
||||
list="exercise-search-titles"
|
||||
enterKeyHint="search"
|
||||
style={{ marginBottom: '10px' }}
|
||||
/>
|
||||
<label className="form-label">Ergänzende Suche / KI-Vorbereitung (Beta)</label>
|
||||
|
|
@ -379,7 +393,10 @@ function ExercisesListPage() {
|
|||
placeholder="zweiter Begriff — zusätzliche Volltextsuche (ODER)"
|
||||
value={aiSearchInput}
|
||||
onChange={(e) => setAiSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
autoComplete="on"
|
||||
name="exercise-ai-search"
|
||||
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)}>
|
||||
|
|
@ -416,7 +433,8 @@ function ExercisesListPage() {
|
|||
</div>
|
||||
) : null}
|
||||
<p style={{ fontSize: '12px', color: 'var(--text3)', marginTop: '10px', marginBottom: 0 }}>
|
||||
Vereins-/Trainerfilter folgen später. Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
||||
Trefferliste aktualisiert sich kurz nach Eingabe. Titel der aktuellen Liste erscheinen als Vorschläge (Pfeil im
|
||||
Feld). Fachliche Filter über „Filter“ – zwischen Feldern UND, Auswahl mehrerer Werte je Feld mit ODER.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -584,7 +602,12 @@ function ExercisesListPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{exercises.length === 0 ? (
|
||||
{listFetching && exercises.length === 0 ? (
|
||||
<div className="card" style={{ textAlign: 'center', padding: '2rem' }}>
|
||||
<div className="spinner"></div>
|
||||
<p style={{ color: 'var(--text2)', marginTop: '12px' }}>Lade Übungen…</p>
|
||||
</div>
|
||||
) : exercises.length === 0 ? (
|
||||
<div className="card">
|
||||
<p style={{ color: 'var(--text2)', textAlign: 'center' }}>
|
||||
Keine Übungen gefunden.
|
||||
|
|
@ -592,6 +615,9 @@ function ExercisesListPage() {
|
|||
</div>
|
||||
) : (
|
||||
<>
|
||||
{listFetching ? (
|
||||
<p style={{ fontSize: '13px', color: 'var(--text3)', marginBottom: '8px' }}>Aktualisiere Treffer…</p>
|
||||
) : null}
|
||||
<p style={{ fontSize: '13px', color: 'var(--text2)', marginBottom: '10px' }}>
|
||||
{exercises.length} angezeigt
|
||||
{hasMore ? ' · es gibt weitere Einträge' : ''}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user