Add ExerciseAiQuickCreateTeaser Component and Update ExercisePickerModal
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m17s

- Introduced the ExerciseAiQuickCreateTeaser component for a compact entry point in the exercise creation process.
- Updated ExercisePickerModal to integrate the new teaser, allowing users to expand and create exercises directly from the search results.
- Enhanced the quick create functionality with dynamic headlines and hints based on user input and context.
- Refactored conditional rendering logic to improve user experience when no exercises are found.
This commit is contained in:
Lars 2026-05-23 06:16:37 +02:00
parent 5c882985e0
commit a8633235f2
2 changed files with 90 additions and 21 deletions

View File

@ -3,6 +3,39 @@ import React from 'react'
/**
* Inline-Angebot: aus Suchstring neue Übung per KI anlegen (Fokusbereich + optional Titel/Skizze).
*/
/** Kompakter Einstieg unter Trefferliste — expandiert zum vollen Formular. */
export function ExerciseAiQuickCreateTeaser({ onExpand, disabled = false }) {
return (
<div
className="card"
style={{
padding: '12px 16px',
marginTop: 16,
borderColor: 'var(--border)',
background: 'var(--surface2)',
}}
>
<div
style={{
display: 'flex',
flexWrap: 'wrap',
gap: '10px 12px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span style={{ fontSize: '0.92rem', lineHeight: 1.45 }}>
<strong>Nichts Richtiges dabei?</strong>{' '}
<span style={{ color: 'var(--text2)' }}>Neue Übung mit KI anlegen und direkt übernehmen.</span>
</span>
<button type="button" className="btn btn-secondary" disabled={disabled} onClick={() => onExpand?.()}>
Neue Übung anlegen
</button>
</div>
</div>
)
}
export default function ExerciseAiQuickCreateOffer({
searchLabel,
title,
@ -19,6 +52,7 @@ export default function ExerciseAiQuickCreateOffer({
showSketchField = true,
sketchOptional = true,
hint,
headline,
}) {
const canRun =
!busy &&
@ -37,7 +71,7 @@ export default function ExerciseAiQuickCreateOffer({
}}
>
<strong style={{ display: 'block', marginBottom: '6px', fontSize: '0.95rem' }}>
Keine passende Übung gefunden
{headline || 'Keine passende Übung gefunden'}
</strong>
<p style={{ margin: '0 0 12px', fontSize: '13px', color: 'var(--text2)', lineHeight: 1.45 }}>
{hint ||

View File

@ -18,7 +18,7 @@ import SkillTreeMultiSelect from './SkillTreeMultiSelect'
import ExerciseFocusRulePicker from './ExerciseFocusRulePicker'
import CatalogRulePicker from './CatalogRulePicker'
import ExerciseAiSuggestPreviewModal from './ExerciseAiSuggestPreviewModal'
import ExerciseAiQuickCreateOffer from './ExerciseAiQuickCreateOffer'
import ExerciseAiQuickCreateOffer, { ExerciseAiQuickCreateTeaser } from './ExerciseAiQuickCreateOffer'
import { useExerciseAiQuickCreateFields } from '../hooks/useExerciseAiQuickCreateFields'
import {
buildQuickCreateAiPreview,
@ -79,6 +79,7 @@ export default function ExercisePickerModal({
const [quickSaving, setQuickSaving] = useState(false)
const [quickAiError, setQuickAiError] = useState('')
const [quickCreateDraft, setQuickCreateDraft] = useState(null)
const [quickCreateExpanded, setQuickCreateExpanded] = useState(false)
const [planningContextSummary, setPlanningContextSummary] = useState(null)
const [planningTargetProfileSummary, setPlanningTargetProfileSummary] = useState(null)
const [planningLlmRankApplied, setPlanningLlmRankApplied] = useState(false)
@ -154,6 +155,7 @@ export default function ExercisePickerModal({
: (searchInput || aiSearchInput).trim()
setPlanningSubmittedQuery(q)
setPlanningHasSearched(true)
setQuickCreateExpanded(false)
setList([])
setPlanningSearchTick((t) => t + 1)
}, [searchInput, aiSearchInput])
@ -174,6 +176,10 @@ export default function ExercisePickerModal({
)
}
useEffect(() => {
if (!usePlanningSearch) setQuickCreateExpanded(false)
}, [effectivePickerQuery, usePlanningSearch])
useEffect(() => {
const t = setTimeout(() => setDebouncedSearch(searchInput.trim()), 350)
return () => clearTimeout(t)
@ -184,13 +190,45 @@ export default function ExercisePickerModal({
return () => clearTimeout(t)
}, [aiSearchInput])
const showQuickCreateOffer =
const canOfferQuickCreate =
enableQuickCreateDraft &&
catalogsReady &&
!loading &&
list.length === 0 &&
planningHasSearched &&
(usePlanningSearch ? true : effectivePickerQuery.length >= 3)
(usePlanningSearch ? planningHasSearched : effectivePickerQuery.length >= 3)
const showQuickCreateFull = canOfferQuickCreate && (list.length === 0 || quickCreateExpanded)
const showQuickCreateTeaser = canOfferQuickCreate && list.length > 0 && !quickCreateExpanded
const quickCreateHeadline = usePlanningSearch
? 'Nichts Richtiges dabei?'
: list.length > 0
? 'Neue Übung anlegen'
: undefined
const quickCreateHint = usePlanningSearch
? effectivePickerQuery
? `Aus Planungsanfrage „${effectivePickerQuery}“ oder eigener Idee — KI schlägt Texte vor, danach bearbeiten und übernehmen.`
: 'Aus Planungskontext oder eigener Idee — KI schlägt Texte vor, danach bearbeiten und übernehmen.'
: undefined
const renderQuickCreateOffer = () => (
<ExerciseAiQuickCreateOffer
searchLabel={effectivePickerQuery || undefined}
title={quickTitle}
onTitleChange={setQuickTitle}
sketch={quickSketch}
onSketchChange={setQuickSketch}
focusAreaId={quickFocusAreaId}
onFocusAreaChange={setQuickFocusAreaId}
focusAreas={catalogs.focusAreas}
catalogsReady={catalogsReady}
busy={quickSaving}
error={quickAiError}
onRunAi={runQuickCreateAiSuggest}
headline={quickCreateHeadline}
hint={quickCreateHint}
/>
)
useEffect(() => {
if (!open) return
@ -239,6 +277,7 @@ export default function ExercisePickerModal({
setQuickSaving(false)
setQuickAiError('')
setQuickCreateDraft(null)
setQuickCreateExpanded(false)
setPlanningContextSummary(null)
setPlanningTargetProfileSummary(null)
setPlanningLlmRankApplied(false)
@ -1060,21 +1099,8 @@ export default function ExercisePickerModal({
<div className="spinner" />
</div>
) : list.length === 0 ? (
showQuickCreateOffer ? (
<ExerciseAiQuickCreateOffer
searchLabel={effectivePickerQuery}
title={quickTitle}
onTitleChange={setQuickTitle}
sketch={quickSketch}
onSketchChange={setQuickSketch}
focusAreaId={quickFocusAreaId}
onFocusAreaChange={setQuickFocusAreaId}
focusAreas={catalogs.focusAreas}
catalogsReady={catalogsReady}
busy={quickSaving}
error={quickAiError}
onRunAi={runQuickCreateAiSuggest}
/>
showQuickCreateFull ? (
renderQuickCreateOffer()
) : (
<p style={{ color: 'var(--text2)', textAlign: 'center', lineHeight: 1.5 }}>
{usePlanningSearch
@ -1223,6 +1249,15 @@ export default function ExercisePickerModal({
</button>
</div>
)}
{showQuickCreateTeaser ? (
<ExerciseAiQuickCreateTeaser
disabled={quickSaving}
onExpand={() => setQuickCreateExpanded(true)}
/>
) : null}
{showQuickCreateFull && quickCreateExpanded ? (
<div style={{ marginTop: 4 }}>{renderQuickCreateOffer()}</div>
) : null}
{multiSelect && typeof onSelectExercises === 'function' ? (
<div
className="exercise-picker-multi-footer"