All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated app version to 0.8.110 and database schema version to 20260512057, reflecting recent enhancements. - Revised project status documentation to include new versioning and next steps for development. - Enhanced the functional specification for training modules and combination exercises, detailing upcoming features and improvements. - Improved technical specifications to align with the latest code changes, ensuring consistency across documentation. - Introduced new UI elements for toast notifications and unsaved changes prompts to enhance user experience. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
1.9 KiB
JavaScript
64 lines
1.9 KiB
JavaScript
import React from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
|
|
/**
|
|
* Bei blocker.state === "blocked": Speichern, Abbrechen (auf Seite bleiben), Verwerfen (Navigation fortsetzen).
|
|
*/
|
|
export default function UnsavedChangesPrompt({
|
|
blocker,
|
|
isBusy,
|
|
onSave,
|
|
onDiscardWithoutSave,
|
|
title = 'Ungespeicherte Änderungen',
|
|
detail = 'Es gibt Änderungen, die noch nicht gespeichert sind. Was möchten Sie tun?',
|
|
}) {
|
|
if (!blocker || blocker.state !== 'blocked') return null
|
|
|
|
return createPortal(
|
|
<div
|
|
className="unsaved-prompt-backdrop"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="unsaved-prompt-title"
|
|
onMouseDown={(e) => {
|
|
if (e.target === e.currentTarget && !isBusy) blocker.reset()
|
|
}}
|
|
>
|
|
<div className="unsaved-prompt-sheet card" onMouseDown={(e) => e.stopPropagation()}>
|
|
<h2 id="unsaved-prompt-title" className="unsaved-prompt-title">
|
|
{title}
|
|
</h2>
|
|
<p style={{ margin: '0 0 1rem', fontSize: '0.9375rem', color: 'var(--text2)', lineHeight: 1.55 }}>
|
|
{detail}
|
|
</p>
|
|
<div className="unsaved-prompt-actions">
|
|
<button
|
|
type="button"
|
|
className="btn btn-primary"
|
|
disabled={isBusy}
|
|
onClick={onSave}
|
|
>
|
|
Speichern
|
|
</button>
|
|
<button type="button" className="btn btn-secondary" disabled={isBusy} onClick={() => blocker.reset()}>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn"
|
|
disabled={isBusy}
|
|
style={{ borderColor: 'var(--danger)', color: 'var(--danger)' }}
|
|
onClick={() => {
|
|
onDiscardWithoutSave()
|
|
blocker.proceed()
|
|
}}
|
|
>
|
|
Nicht speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
)
|
|
}
|