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 usePlanningSearch = Boolean(planningContext?.unitId && Number(planningContext.unitId) > 0)
|
||||||
|
|
||||||
const effectivePickerQuery = useMemo(
|
/** Gemeinsamer Suchtext — in Planung nur ein Feld; in Bibliothek beide Felder kombiniert. */
|
||||||
() => [debouncedSearch, debouncedAi].filter(Boolean).join(' ').trim(),
|
const effectivePickerQuery = useMemo(() => {
|
||||||
[debouncedSearch, debouncedAi]
|
if (usePlanningSearch) {
|
||||||
)
|
return (debouncedSearch || debouncedAi).trim()
|
||||||
|
}
|
||||||
|
return [debouncedSearch, debouncedAi].filter(Boolean).join(' ').trim()
|
||||||
|
}, [usePlanningSearch, debouncedSearch, debouncedAi])
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title: quickTitle,
|
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.skill_max_level) q.skill_max_level = n(filters.skill_max_level)
|
||||||
if (filters.exclude_without_focus) q.exclude_without_focus = true
|
if (filters.exclude_without_focus) q.exclude_without_focus = true
|
||||||
if (filters.include_archived) q.include_archived = true
|
if (filters.include_archived) q.include_archived = true
|
||||||
if (debouncedSearch) q.search = debouncedSearch
|
if (effectivePickerQuery) q.search = effectivePickerQuery
|
||||||
if (debouncedAi) q.ai_search = debouncedAi
|
|
||||||
if (!debouncedSearch && debouncedAi) q.search = debouncedAi
|
|
||||||
if (
|
if (
|
||||||
Array.isArray(exerciseKindAny) &&
|
Array.isArray(exerciseKindAny) &&
|
||||||
exerciseKindAny.length > 0
|
exerciseKindAny.length > 0
|
||||||
|
|
@ -259,7 +260,7 @@ export default function ExercisePickerModal({
|
||||||
q.exercise_kind_any = exerciseKindAny
|
q.exercise_kind_any = exerciseKindAny
|
||||||
}
|
}
|
||||||
return q
|
return q
|
||||||
}, [filters, debouncedSearch, debouncedAi, exerciseKindAny])
|
}, [filters, effectivePickerQuery, exerciseKindAny])
|
||||||
|
|
||||||
const reload = useCallback(async () => {
|
const reload = useCallback(async () => {
|
||||||
if (!open || !catalogsReady) return
|
if (!open || !catalogsReady) return
|
||||||
|
|
@ -345,8 +346,6 @@ export default function ExercisePickerModal({
|
||||||
usePlanningSearch,
|
usePlanningSearch,
|
||||||
planningContext,
|
planningContext,
|
||||||
effectivePickerQuery,
|
effectivePickerQuery,
|
||||||
debouncedSearch,
|
|
||||||
debouncedAi,
|
|
||||||
exerciseKindAny,
|
exerciseKindAny,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -496,7 +495,13 @@ export default function ExercisePickerModal({
|
||||||
>
|
>
|
||||||
<div className="admin-modal-sheet__header">
|
<div className="admin-modal-sheet__header">
|
||||||
<h3 className="admin-modal-sheet__title">
|
<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>
|
</h3>
|
||||||
<button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={onClose}>
|
<button type="button" className="btn btn-secondary admin-modal-sheet__close" onClick={onClose}>
|
||||||
Schließen
|
Schließen
|
||||||
|
|
@ -575,51 +580,72 @@ export default function ExercisePickerModal({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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 style={{ display: 'grid', gap: '0.65rem' }}>
|
||||||
<div>
|
{usePlanningSearch ? (
|
||||||
<label className="form-label">
|
<div>
|
||||||
{usePlanningSearch ? 'Planungs-Anfrage' : 'Volltextsuche'}
|
<label className="form-label">Planungs-Anfrage (KI)</label>
|
||||||
</label>
|
<input
|
||||||
<input
|
type="search"
|
||||||
type="search"
|
className="form-input"
|
||||||
className="form-input"
|
placeholder="z. B. Schlage mir die nächste Übung vor, baut auf dem Plan auf und trainiert Schnellkraft …"
|
||||||
placeholder={
|
value={searchInput || aiSearchInput}
|
||||||
usePlanningSearch
|
onChange={(e) => {
|
||||||
? 'z. B. Schlage mir die nächste Übung vor, Vertiefung, Reaktion mit Partner …'
|
const v = e.target.value
|
||||||
: 'Stichwort, Titelfragment…'
|
setSearchInput(v)
|
||||||
}
|
setAiSearchInput(v)
|
||||||
value={searchInput}
|
}}
|
||||||
onChange={(e) => setSearchInput(e.target.value)}
|
autoComplete="off"
|
||||||
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 ? (
|
|
||||||
<p style={{ margin: '4px 0 0', fontSize: '11px', color: 'var(--text3)' }}>
|
<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>
|
</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' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', alignItems: 'center' }}>
|
||||||
<button type="button" className="btn btn-secondary" onClick={() => setFilterOpen(!filterOpen)}>
|
<button type="button" className="btn btn-secondary" onClick={() => setFilterOpen(!filterOpen)}>
|
||||||
{filterOpen ? 'Filter ausblenden' : 'Erweiterte Filter'}
|
{filterOpen ? 'Filter ausblenden' : 'Erweiterte Filter'}
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,9 @@ export default function TrainingUnitEditPage() {
|
||||||
const [saveModuleOpen, setSaveModuleOpen] = useState(false)
|
const [saveModuleOpen, setSaveModuleOpen] = useState(false)
|
||||||
|
|
||||||
const exercisePickerPlanningContext = useMemo(() => {
|
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 target = exercisePickerTarget
|
||||||
const secs = formData.sections || []
|
const secs = formData.sections || []
|
||||||
const sIdx = target?.sIdx ?? 0
|
const sIdx = target?.sIdx ?? 0
|
||||||
|
|
@ -164,13 +166,13 @@ export default function TrainingUnitEditPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
unitId: Number(editingUnit.id),
|
unitId: Number(resolvedUnitId),
|
||||||
sectionOrderIndex: sIdx,
|
sectionOrderIndex: sIdx,
|
||||||
anchorExerciseId: Number.isFinite(anchorExerciseId) && anchorExerciseId > 0 ? anchorExerciseId : null,
|
anchorExerciseId: Number.isFinite(anchorExerciseId) && anchorExerciseId > 0 ? anchorExerciseId : null,
|
||||||
progressionGraphId: null,
|
progressionGraphId: null,
|
||||||
plannedExerciseIds,
|
plannedExerciseIds,
|
||||||
}
|
}
|
||||||
}, [editingUnit?.id, exercisePickerTarget, formData.sections])
|
}, [editingUnit?.id, unitId, exercisePickerTarget, formData.sections])
|
||||||
|
|
||||||
const goBack = useCallback(() => {
|
const goBack = useCallback(() => {
|
||||||
goNavReturn(navigate, location, {
|
goNavReturn(navigate, location, {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user