fix(p06): RightsDeclarationDialog in Exercise-Upload-Modals integriert
Some checks failed
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 11s
Test Suite / playwright-tests (push) Failing after 1m19s

ExerciseInlineFileMediaModal (Upload-Tab) und ExerciseInlineEmbedModal
zeigen jetzt den vollstaendigen P-06-Einwilligungsdialog bevor der
API-Call ausgefuehrt wird. Vorher wurde der Backend-Fehler (400)
als nicht benutzbarer browser alert angezeigt.

- ExerciseInlineFileMediaModal: handleUploadAndInsert oeffnet Dialog,
  doUploadWithDecl haengt die 9 P-06-Felder an FormData an
- ExerciseInlineEmbedModal: submit oeffnet Dialog, doSubmitWithDecl
  haengt P-06-Felder an FormData an
- Backdrop-Click deaktiviert wenn Dialog offen

version: 0.8.76

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-05-11 08:25:00 +02:00
parent 34235ef46d
commit 000e78e976
4 changed files with 54 additions and 6 deletions

View File

@ -1,6 +1,6 @@
# Shinkan Jinkendo Version Information # Shinkan Jinkendo Version Information
APP_VERSION = "0.8.75" APP_VERSION = "0.8.76"
BUILD_DATE = "2026-05-11" BUILD_DATE = "2026-05-11"
DB_SCHEMA_VERSION = "20260511048" DB_SCHEMA_VERSION = "20260511048"
@ -31,6 +31,13 @@ MODULE_VERSIONS = {
} }
CHANGELOG = [ CHANGELOG = [
{
"version": "0.8.76",
"date": "2026-05-11",
"changes": [
"Fix P-06: RightsDeclarationDialog in ExerciseInlineFileMediaModal (Upload-Tab) und ExerciseInlineEmbedModal integriert; vor jedem Exercise-Media-Upload oeffnet sich der vollstaendige Einwilligungsdialog statt eines nicht-benutzbaren alert()",
],
},
{ {
"version": "0.8.75", "version": "0.8.75",
"date": "2026-05-11", "date": "2026-05-11",

View File

@ -9,6 +9,7 @@ import {
sanitizeInlineMediaSize, sanitizeInlineMediaSize,
} from '../constants/inlineExerciseMedia' } from '../constants/inlineExerciseMedia'
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption' import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
import RightsDeclarationDialog from './RightsDeclarationDialog'
/** /**
* @param {{ * @param {{
@ -30,6 +31,7 @@ export default function ExerciseInlineEmbedModal({
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE) const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE)
const [busy, setBusy] = useState(false) const [busy, setBusy] = useState(false)
const [rightsDialogOpen, setRightsDialogOpen] = useState(false)
useEffect(() => { useEffect(() => {
if (!open) return if (!open) return
@ -38,12 +40,18 @@ export default function ExerciseInlineEmbedModal({
setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE) setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE)
}, [open]) }, [open])
const submit = async () => { const submit = () => {
const u = url.trim() const u = url.trim()
if (!u) { if (!u) {
alert('Bitte eine Embed-URL eingeben (https://…).') alert('Bitte eine Embed-URL eingeben (https://…).')
return return
} }
setRightsDialogOpen(true)
}
const doSubmitWithDecl = async (decl) => {
setRightsDialogOpen(false)
const u = url.trim()
const size = sanitizeInlineMediaSize(displaySize) const size = sanitizeInlineMediaSize(displaySize)
const fd = new FormData() const fd = new FormData()
fd.append('embed_url', u) fd.append('embed_url', u)
@ -52,6 +60,9 @@ export default function ExerciseInlineEmbedModal({
fd.append('description', '') fd.append('description', '')
fd.append('context', 'ablauf') fd.append('context', 'ablauf')
fd.append('is_primary', 'false') fd.append('is_primary', 'false')
for (const [k, v] of Object.entries(decl)) {
fd.append(k, String(v))
}
setBusy(true) setBusy(true)
try { try {
const row = await api.uploadExerciseMedia(exerciseId, fd) const row = await api.uploadExerciseMedia(exerciseId, fd)
@ -75,7 +86,15 @@ export default function ExerciseInlineEmbedModal({
if (!open) return null if (!open) return null
return ( return (
<div className="admin-modal-backdrop" role="presentation" onClick={(e) => e.target === e.currentTarget && !busy && onClose()}> <>
<RightsDeclarationDialog
open={rightsDialogOpen}
onCancel={() => setRightsDialogOpen(false)}
onConfirm={doSubmitWithDecl}
targetVisibility="private"
mode="upload"
/>
<div className="admin-modal-backdrop" role="presentation" onClick={(e) => e.target === e.currentTarget && !busy && !rightsDialogOpen && onClose()}>
<div <div
className="admin-modal-sheet" className="admin-modal-sheet"
role="dialog" role="dialog"
@ -128,5 +147,6 @@ export default function ExerciseInlineEmbedModal({
</div> </div>
</div> </div>
</div> </div>
</>
) )
} }

View File

@ -10,6 +10,7 @@ import {
sanitizeInlineMediaSize, sanitizeInlineMediaSize,
} from '../constants/inlineExerciseMedia' } from '../constants/inlineExerciseMedia'
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption' import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
import RightsDeclarationDialog from './RightsDeclarationDialog'
function RtePickerAssetThumb({ asset }) { function RtePickerAssetThumb({ asset }) {
const id = asset.id const id = asset.id
@ -89,6 +90,7 @@ export default function ExerciseInlineFileMediaModal({
const [uploadTitle, setUploadTitle] = useState('') const [uploadTitle, setUploadTitle] = useState('')
const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE) const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE)
const [uploadInputKey, setUploadInputKey] = useState(0) const [uploadInputKey, setUploadInputKey] = useState(0)
const [rightsDialogOpen, setRightsDialogOpen] = useState(false)
const assetToExerciseMedia = useMemo(() => { const assetToExerciseMedia = useMemo(() => {
const m = new Map() const m = new Map()
@ -182,11 +184,16 @@ export default function ExerciseInlineFileMediaModal({
} }
} }
const handleUploadAndInsert = async () => { const handleUploadAndInsert = () => {
if (!uploadFile) { if (!uploadFile) {
alert('Bitte eine Datei wählen.') alert('Bitte eine Datei wählen.')
return return
} }
setRightsDialogOpen(true)
}
const doUploadWithDecl = async (decl) => {
setRightsDialogOpen(false)
const size = sanitizeInlineMediaSize(displaySize) const size = sanitizeInlineMediaSize(displaySize)
const inferred = inferExerciseMediaType(uploadFile) const inferred = inferExerciseMediaType(uploadFile)
const fd = new FormData() const fd = new FormData()
@ -196,6 +203,9 @@ export default function ExerciseInlineFileMediaModal({
fd.append('description', '') fd.append('description', '')
fd.append('context', 'ablauf') fd.append('context', 'ablauf')
fd.append('is_primary', 'false') fd.append('is_primary', 'false')
for (const [k, v] of Object.entries(decl)) {
fd.append(k, String(v))
}
setBusy(true) setBusy(true)
setErr(null) setErr(null)
try { try {
@ -232,7 +242,15 @@ export default function ExerciseInlineFileMediaModal({
if (!open) return null if (!open) return null
return ( return (
<div className="admin-modal-backdrop" role="presentation" onClick={(e) => e.target === e.currentTarget && !busy && onClose()}> <>
<RightsDeclarationDialog
open={rightsDialogOpen}
onCancel={() => setRightsDialogOpen(false)}
onConfirm={doUploadWithDecl}
targetVisibility="private"
mode="upload"
/>
<div className="admin-modal-backdrop" role="presentation" onClick={(e) => e.target === e.currentTarget && !busy && !rightsDialogOpen && onClose()}>
<div <div
className="admin-modal-sheet rte-inline-media-modal" className="admin-modal-sheet rte-inline-media-modal"
role="dialog" role="dialog"
@ -392,5 +410,6 @@ export default function ExerciseInlineFileMediaModal({
</div> </div>
</div> </div>
</div> </div>
</>
) )
} }

View File

@ -1,6 +1,6 @@
// Shinkan Jinkendo Frontend Version // Shinkan Jinkendo Frontend Version
export const APP_VERSION = "0.8.75" export const APP_VERSION = "0.8.76"
export const BUILD_DATE = "2026-05-11" export const BUILD_DATE = "2026-05-11"
export const PAGE_VERSIONS = { export const PAGE_VERSIONS = {
@ -21,4 +21,6 @@ export const PAGE_VERSIONS = {
AdminCatalogsPage: "2.2.0", AdminCatalogsPage: "2.2.0",
TrainerContextsPage: "1.0.0", TrainerContextsPage: "1.0.0",
MediaLibraryPage: "1.2.0", // P-06: RightsDeclarationDialog + Altbestand-Indikator MediaLibraryPage: "1.2.0", // P-06: RightsDeclarationDialog + Altbestand-Indikator
ExerciseInlineFileMediaModal: "1.1.0", // P-06: RightsDeclarationDialog vor Upload
ExerciseInlineEmbedModal: "1.1.0", // P-06: RightsDeclarationDialog vor Embed-Upload
} }