Enhance ExerciseFormPageRoot with save and close functionality
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m13s

- Added a new `handleSaveAndClose` function to allow users to save and navigate back to the exercise list.
- Updated `performSaveAttempt` to accept a `closeAfter` parameter for conditional navigation.
- Refactored form submission handling to include separate actions for saving and saving with closure.
- Integrated `PageFormEditorChrome` for improved layout and user experience, including a back navigation option.
This commit is contained in:
Lars 2026-05-20 06:38:53 +02:00
parent d19a1061d8
commit e50c18f92e

View File

@ -26,6 +26,7 @@ import { COMBINATION_ARCHETYPE_OPTIONS, ARCHETYPE_DEFAULT_REP_SERIES_COUNT, defa
import { readSlotProfilesV1, normalizeAdvanceMode, parseComboRepSeriesCountUi } from '../../utils/combinationMethodProfileUi' import { readSlotProfilesV1, normalizeAdvanceMode, parseComboRepSeriesCountUi } from '../../utils/combinationMethodProfileUi'
import { GripVertical } from 'lucide-react' import { GripVertical } from 'lucide-react'
import UnsavedChangesPrompt from '../UnsavedChangesPrompt' import UnsavedChangesPrompt from '../UnsavedChangesPrompt'
import PageFormEditorChrome from '../PageFormEditorChrome'
import { useBeforeUnloadWhen, useUnsavedChangesBlocker } from '../../hooks/useUnsavedChangesBlocker' import { useBeforeUnloadWhen, useUnsavedChangesBlocker } from '../../hooks/useUnsavedChangesBlocker'
const INTENSITY_OPTIONS = [ const INTENSITY_OPTIONS = [
@ -836,7 +837,7 @@ function ExerciseFormPageRoot() {
} }
const performSaveAttempt = useCallback( const performSaveAttempt = useCallback(
async ({ fromUnsavedDialog = false } = {}) => { async ({ fromUnsavedDialog = false, closeAfter = false } = {}) => {
if (!formData.title || formData.title.trim().length < 3) { if (!formData.title || formData.title.trim().length < 3) {
toast.error('Titel mindestens 3 Zeichen') toast.error('Titel mindestens 3 Zeichen')
return false return false
@ -940,12 +941,15 @@ function ExerciseFormPageRoot() {
setVariants((ex.variants || []).map(apiVariantToRow)) setVariants((ex.variants || []).map(apiVariantToRow))
setFormDirty(false) setFormDirty(false)
toast.success('Gespeichert.') toast.success('Gespeichert.')
if (closeAfter) navigate('/exercises')
return true return true
} }
const created = await api.createExercise(payload) const created = await api.createExercise(payload)
setFormDirty(false) setFormDirty(false)
toast.success('Übung angelegt.') toast.success('Übung angelegt.')
if (!fromUnsavedDialog) { if (closeAfter) {
navigate('/exercises')
} else if (!fromUnsavedDialog) {
navigate(`/exercises/${created.id}/edit`, { replace: true }) navigate(`/exercises/${created.id}/edit`, { replace: true })
} }
return true return true
@ -959,10 +963,39 @@ function ExerciseFormPageRoot() {
[exerciseId, formData, isEdit, navigate, toast], [exerciseId, formData, isEdit, navigate, toast],
) )
const handleSubmit = async (e) => { const handleSubmit = useCallback(
e.preventDefault() async (e) => {
await performSaveAttempt({ fromUnsavedDialog: false }) e?.preventDefault?.()
} await performSaveAttempt({ fromUnsavedDialog: false, closeAfter: false })
},
[performSaveAttempt],
)
const handleSaveAndClose = useCallback(
async (e) => {
e?.preventDefault?.()
await performSaveAttempt({ fromUnsavedDialog: false, closeAfter: true })
},
[performSaveAttempt],
)
const goBackToList = useCallback(() => {
navigate('/exercises')
}, [navigate])
const actionConfig = useMemo(
() => ({
formId: 'exercise-form',
saving,
isNew: !isEdit,
onSave: handleSubmit,
onSaveAndClose: handleSaveAndClose,
onCancel: goBackToList,
showSave: true,
showSaveAndClose: true,
}),
[saving, isEdit, handleSubmit, handleSaveAndClose, goBackToList],
)
const handleUnsavedDialogSave = async () => { const handleUnsavedDialogSave = async () => {
const ok = await performSaveAttempt({ fromUnsavedDialog: true }) const ok = await performSaveAttempt({ fromUnsavedDialog: true })
@ -1162,27 +1195,28 @@ function ExerciseFormPageRoot() {
} }
return ( return (
<div style={{ padding: '12px' }} className="app-page"> <PageFormEditorChrome
<div style={{ marginBottom: '12px' }}> testId="exercise-form-page"
<button type="button" className="btn btn-secondary" onClick={() => navigate('/exercises')}> title={isEdit ? 'Übung bearbeiten' : 'Neue Übung'}
Übersicht backTo="/exercises"
</button> backLabel="Übersicht"
{isEdit && ( actionConfig={actionConfig}
<button >
type="button" {isEdit ? (
<p style={{ margin: '0 0 12px' }}>
<Link
to={`/exercises/${exerciseId}`}
state={{ fromExerciseEdit: true }}
className="btn btn-secondary" className="btn btn-secondary"
style={{ marginLeft: '8px' }} style={{ fontSize: '0.875rem' }}
onClick={() => navigate(`/exercises/${exerciseId}`, { state: { fromExerciseEdit: true } })}
> >
Ansehen Ansehen
</button> </Link>
)} </p>
</div> ) : null}
<div className="card"> <div className="card">
<h1 style={{ marginTop: 0, fontSize: '1.25rem' }}>{isEdit ? 'Übung bearbeiten' : 'Neue Übung'}</h1> <form id="exercise-form" onSubmit={handleSubmit}>
<form onSubmit={handleSubmit}>
<div className="form-row"> <div className="form-row">
<label className="form-label">Titel *</label> <label className="form-label">Titel *</label>
<input <input
@ -1943,12 +1977,6 @@ function ExerciseFormPageRoot() {
</p> </p>
</div> </div>
) : null} ) : null}
<div style={{ marginTop: '16px' }}>
<button type="submit" className="btn btn-primary" disabled={saving}>
{saving ? 'Speichern…' : isEdit ? 'Speichern' : 'Anlegen & weiter'}
</button>
</div>
</form> </form>
</div> </div>
@ -2439,8 +2467,9 @@ function ExerciseFormPageRoot() {
isBusy={saving} isBusy={saving}
onSave={handleUnsavedDialogSave} onSave={handleUnsavedDialogSave}
onDiscardWithoutSave={() => setFormDirty(false)} onDiscardWithoutSave={() => setFormDirty(false)}
detail="Du hast ungespeicherte Änderungen vorgenommen. Möchtest du die Seite wirklich verlassen?"
/> />
</div> </PageFormEditorChrome>
) )
} }