Refactor ExercisePickerModal for Enhanced Search Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Updated `effectivePickerQuery` logic to improve search handling based on planning context, allowing for a single input field in planning mode. - Simplified query construction by utilizing `effectivePickerQuery` throughout the component, enhancing clarity and user experience. - Adjusted UI elements and labels to better reflect the context of the search, providing clearer guidance for users. - Modified `TrainingUnitEditPage` to ensure proper unit ID resolution, improving integration with the exercise picker.
This commit is contained in:
parent
905bce198f
commit
d019c20338
|
|
@ -75,10 +75,13 @@ export default function ExercisePickerModal({
|
|||
|
||||
const usePlanningSearch = Boolean(planningContext?.unitId && Number(planningContext.unitId) > 0)
|
||||
|
||||
const effectivePickerQuery = useMemo(
|
||||
() => [debouncedSearch, debouncedAi].filter(Boolean).join(' ').trim(),
|
||||
[debouncedSearch, debouncedAi]
|
||||
)
|
||||
/** Gemeinsamer Suchtext — in Planung nur ein Feld; in Bibliothek beide Felder kombiniert. */
|
||||
const effectivePickerQuery = useMemo(() => {
|
||||
if (usePlanningSearch) {
|
||||
return (debouncedSearch || debouncedAi).trim()
|
||||
}
|
||||
return [debouncedSearch, debouncedAi].filter(Boolean).join(' ').trim()
|
||||
}, [usePlanningSearch, debouncedSearch, debouncedAi])
|
||||
|
||||
const {
|
||||
title: quickTitle,
|
||||
|
|
@ -249,9 +252,7 @@ export default function ExercisePickerModal({
|
|||
if (filters.skill_max_level) q.skill_max_level = n(filters.skill_max_level)
|
||||
if (filters.exclude_without_focus) q.exclude_without_focus = true
|
||||
if (filters.include_archived) q.include_archived = true
|
||||
if (debouncedSearch) q.search = debouncedSearch
|
||||
if (debouncedAi) q.ai_search = debouncedAi
|
||||
if (!debouncedSearch && debouncedAi) q.search = debouncedAi
|
||||
if (effectivePickerQuery) q.search = effectivePickerQuery
|
||||
if (
|
||||
Array.isArray(exerciseKindAny) &&
|
||||
exerciseKindAny.length > 0
|
||||
|
|
@ -259,7 +260,7 @@ export default function ExercisePickerModal({
|
|||
q.exercise_kind_any = exerciseKindAny
|
||||
}
|
||||
return q
|
||||
}, [filters, debouncedSearch, debouncedAi, exerciseKindAny])
|
||||
}, [filters, effectivePickerQuery, exerciseKindAny])
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
if (!open || !catalogsReady) return
|
||||
|
|
@ -345,8 +346,6 @@ export default function ExercisePickerModal({
|
|||
usePlanningSearch,
|
||||
planningContext,
|
||||
effectivePickerQuery,
|
||||
debouncedSearch,
|
||||
debouncedAi,
|
||||
exerciseKindAny,
|
||||
])
|
||||
|
||||
|
|
@ -496,7 +495,13 @@ export default function ExercisePickerModal({
|
|||
>
|
||||
<div className="admin-modal-sheet__header">
|
||||
<h3 className="admin-modal-sheet__title">
|
||||
{multiSelect ? 'Übungen auswählen' : 'Übung auswählen'}
|
||||
{usePlanningSearch
|
||||
? multiSelect
|
||||
? 'Planungs-KI: Übungen vorschlagen'
|
||||
: 'Planungs-KI: Übung vorschlagen'
|
||||
: multiSelect
|
||||
? 'Übungen auswählen'
|
||||
: 'Übung auswählen'}
|
||||
</h3>
|
||||
<button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={onClose}>
|
||||
Schließen
|
||||
|
|
@ -575,51 +580,72 @@ export default function ExercisePickerModal({
|
|||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{!usePlanningSearch ? (
|
||||
<p
|
||||
style={{
|
||||
margin: '0 0 10px',
|
||||
padding: '8px 10px',
|
||||
borderRadius: '8px',
|
||||
background: 'var(--surface2)',
|
||||
border: '1px solid var(--border)',
|
||||
fontSize: '12px',
|
||||
color: 'var(--text2)',
|
||||
}}
|
||||
>
|
||||
<strong style={{ color: 'var(--text1)' }}>Bibliothekssuche (Volltext)</strong> — Planungs-KI mit
|
||||
Kontext (Einheit, Plan, Anker) gibt es in der{' '}
|
||||
<strong>Trainingseinheit bearbeiten</strong>, nach dem Speichern der Einheit.
|
||||
</p>
|
||||
) : null}
|
||||
<div style={{ display: 'grid', gap: '0.65rem' }}>
|
||||
<div>
|
||||
<label className="form-label">
|
||||
{usePlanningSearch ? 'Planungs-Anfrage' : 'Volltextsuche'}
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder={
|
||||
usePlanningSearch
|
||||
? 'z. B. Schlage mir die nächste Übung vor, Vertiefung, Reaktion mit Partner …'
|
||||
: 'Stichwort, Titelfragment…'
|
||||
}
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="form-label">
|
||||
{usePlanningSearch ? 'Planungs-Anfrage (Zusatz, optional)' : 'Semantisch / '}
|
||||
{!usePlanningSearch ? (
|
||||
<span title="aktuell gleiche Datenbanksuche wie Volltext; später KI-Verfeinerung möglich">
|
||||
KI-Feld
|
||||
</span>
|
||||
) : null}
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder={
|
||||
usePlanningSearch
|
||||
? 'Alternative Formulierung — wird mit oben kombiniert'
|
||||
: 'zweites Suchkonzept oder Umschreibung…'
|
||||
}
|
||||
value={aiSearchInput}
|
||||
onChange={(e) => setAiSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{usePlanningSearch ? (
|
||||
{usePlanningSearch ? (
|
||||
<div>
|
||||
<label className="form-label">Planungs-Anfrage (KI)</label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder="z. B. Schlage mir die nächste Übung vor, baut auf dem Plan auf und trainiert Schnellkraft …"
|
||||
value={searchInput || aiSearchInput}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value
|
||||
setSearchInput(v)
|
||||
setAiSearchInput(v)
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p style={{ margin: '4px 0 0', fontSize: '11px', color: 'var(--text3)' }}>
|
||||
Beide Felder bilden eine gemeinsame Planungs-Anfrage.
|
||||
Leer lassen = nächste Übung aus Planungskontext. Mit Text = KI-Intent + Profil + Ranking.
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<label className="form-label">Volltextsuche</label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder="Stichwort, Titelfragment…"
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="form-label">
|
||||
Ergänzung /{' '}
|
||||
<span title="Wird mit Volltextsuche kombiniert">zweites Suchfeld</span>
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
className="form-input"
|
||||
placeholder="zweites Suchkonzept oder Umschreibung…"
|
||||
value={aiSearchInput}
|
||||
onChange={(e) => setAiSearchInput(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', alignItems: 'center' }}>
|
||||
<button type="button" className="btn btn-secondary" onClick={() => setFilterOpen(!filterOpen)}>
|
||||
{filterOpen ? 'Filter ausblenden' : 'Erweiterte Filter'}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,9 @@ export default function TrainingUnitEditPage() {
|
|||
const [saveModuleOpen, setSaveModuleOpen] = useState(false)
|
||||
|
||||
const exercisePickerPlanningContext = useMemo(() => {
|
||||
if (!editingUnit?.id) return null
|
||||
const resolvedUnitId =
|
||||
editingUnit?.id ?? (Number.isFinite(unitId) && unitId > 0 ? unitId : null)
|
||||
if (!resolvedUnitId) return null
|
||||
const target = exercisePickerTarget
|
||||
const secs = formData.sections || []
|
||||
const sIdx = target?.sIdx ?? 0
|
||||
|
|
@ -164,13 +166,13 @@ export default function TrainingUnitEditPage() {
|
|||
}
|
||||
}
|
||||
return {
|
||||
unitId: Number(editingUnit.id),
|
||||
unitId: Number(resolvedUnitId),
|
||||
sectionOrderIndex: sIdx,
|
||||
anchorExerciseId: Number.isFinite(anchorExerciseId) && anchorExerciseId > 0 ? anchorExerciseId : null,
|
||||
progressionGraphId: null,
|
||||
plannedExerciseIds,
|
||||
}
|
||||
}, [editingUnit?.id, exercisePickerTarget, formData.sections])
|
||||
}, [editingUnit?.id, unitId, exercisePickerTarget, formData.sections])
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
goNavReturn(navigate, location, {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user