From 6586d3b68b1cb6f1304ccd7eff5d1fba0fd80618 Mon Sep 17 00:00:00 2001 From: Lars Date: Mon, 11 May 2026 09:39:15 +0200 Subject: [PATCH] feat(version): update app version to 0.8.80 and add changelog entries for recent fixes and enhancements fix(RightsDeclarationDialog): change cancel button to icon for improved UI feat(MediaLibraryPage): implement rights dialog for visibility promotions and enhance error handling fix(version): update MediaLibraryPage version to 1.4.0 reflecting rights dialog changes --- backend/version.py | 16 +++- .../components/RightsDeclarationDialog.jsx | 4 +- frontend/src/pages/MediaLibraryPage.jsx | 84 ++++++++++++++----- frontend/src/version.js | 4 +- tests/dev-smoke-test.spec.js | 14 ++-- 5 files changed, 91 insertions(+), 31 deletions(-) diff --git a/backend/version.py b/backend/version.py index 84c5ce4..9f45726 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.78" +APP_VERSION = "0.8.80" BUILD_DATE = "2026-05-11" DB_SCHEMA_VERSION = "20260511048" @@ -31,6 +31,20 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.80", + "date": "2026-05-11", + "changes": [ + "Fix P-06: Bei Sichtbarkeits-Promotion (private→club, club→official) oeffnet das Frontend bei RIGHTS_SCOPE_INSUFFICIENT / LEGACY_REDECLARATION_REQUIRED automatisch den Einwilligungsdialog (promotion/redeclaration-Modus) statt eines alert()-Fehlers; PATCH wird nach Bestaetigung mit P-06-Feldern wiederholt.", + ], + }, + { + "version": "0.8.79", + "date": "2026-05-11", + "changes": [ + "Fix: profiles.username -> profiles.name in Journal-Endpoint (500er behoben); RightsDeclarationDialog: doppelter Abbrechen-Button entfernt (Header-X statt Text); Playwright P-01: Platzhalter-Pruefung optional (echtes Impressum deployed); Playwright P-06c: Selector-Konflikt durch UI-Fix beseitigt.", + ], + }, { "version": "0.8.78", "date": "2026-05-11", diff --git a/frontend/src/components/RightsDeclarationDialog.jsx b/frontend/src/components/RightsDeclarationDialog.jsx index 1496cbd..7da44f3 100644 --- a/frontend/src/components/RightsDeclarationDialog.jsx +++ b/frontend/src/components/RightsDeclarationDialog.jsx @@ -103,8 +103,8 @@ export default function RightsDeclarationDialog({

{titleMap[mode] || titleMap.upload}

- diff --git a/frontend/src/pages/MediaLibraryPage.jsx b/frontend/src/pages/MediaLibraryPage.jsx index 1feabf8..0415d44 100644 --- a/frontend/src/pages/MediaLibraryPage.jsx +++ b/frontend/src/pages/MediaLibraryPage.jsx @@ -283,9 +283,13 @@ export default function MediaLibraryPage() { const [uploadClubId, setUploadClubId] = useState('') const [uploadBusy, setUploadBusy] = useState(false) const [uploadSummary, setUploadSummary] = useState('') - // P-06: Rechte-Dialog + // P-06: Rechte-Dialog (Upload) const [rightsDialogOpen, setRightsDialogOpen] = useState(false) const [pendingUploadFiles, setPendingUploadFiles] = useState(null) + // P-06: Rechte-Dialog (Sichtbarkeits-Promotion) + const [rightsUpgradeDialogOpen, setRightsUpgradeDialogOpen] = useState(false) + const [rightsUpgradeMode, setRightsUpgradeMode] = useState('promotion') + const [pendingPatchBody, setPendingPatchBody] = useState(null) const [journalModal, setJournalModal] = useState(null) const [journalLoading, setJournalLoading] = useState(false) const mediaListFetchSeqRef = useRef(0) @@ -418,32 +422,62 @@ export default function MediaLibraryPage() { } } + function parseApiErrorCode(msg) { + try { + const d = JSON.parse(msg) + if (d && d.code) return d + } catch { /* not JSON */ } + return null + } + const saveModal = async () => { if (!modal || !modalDraft) return const p = modal.permissions || {} + const body = {} + if (p.edit_metadata) { + body.original_filename = modalDraft.display_name + body.copyright_notice = modalDraft.copyright_notice + body.tags = parseTagsInput(modalDraft.tags_input) + } + if (p.change_visibility) { + body.visibility = modalDraft.visibility + if (modalDraft.visibility === 'club' || (modalDraft.visibility === 'private' && isPlatformAdmin)) { + const cid = Number(modalDraft.club_id) + if (!cid) { + alert('Bitte einen Verein wählen.') + return + } + body.club_id = cid + } + } + if (!Object.keys(body).length) return setBusy(true) try { - const body = {} - if (p.edit_metadata) { - body.original_filename = modalDraft.display_name - body.copyright_notice = modalDraft.copyright_notice - body.tags = parseTagsInput(modalDraft.tags_input) - } - if (p.change_visibility) { - body.visibility = modalDraft.visibility - if (modalDraft.visibility === 'club' || (modalDraft.visibility === 'private' && isPlatformAdmin)) { - const cid = Number(modalDraft.club_id) - if (!cid) { - alert('Bitte einen Verein wählen.') - setBusy(false) - return - } - body.club_id = cid - } - } - if (Object.keys(body).length) { - await api.patchMediaAsset(modal.id, body) + await api.patchMediaAsset(modal.id, body) + closeModal() + await loadItems() + } catch (e) { + const parsed = parseApiErrorCode(e.message) + if (parsed && (parsed.code === 'RIGHTS_SCOPE_INSUFFICIENT' || parsed.code === 'LEGACY_REDECLARATION_REQUIRED')) { + setPendingPatchBody(body) + setRightsUpgradeMode(parsed.code === 'LEGACY_REDECLARATION_REQUIRED' ? 'redeclaration' : 'promotion') + setRightsUpgradeDialogOpen(true) + return } + alert(e.message || String(e)) + } finally { + setBusy(false) + } + } + + const doSaveWithRightsDecl = async (decl) => { + setRightsUpgradeDialogOpen(false) + if (!modal || !pendingPatchBody) return + const body = { ...pendingPatchBody, ...decl } + setPendingPatchBody(null) + setBusy(true) + try { + await api.patchMediaAsset(modal.id, body) closeModal() await loadItems() } catch (e) { @@ -589,6 +623,14 @@ export default function MediaLibraryPage() { targetVisibility={uploadVis} mode="upload" /> + { setRightsUpgradeDialogOpen(false); setPendingPatchBody(null) }} + onConfirm={doSaveWithRightsDecl} + targetVisibility={pendingPatchBody?.visibility || modal?.visibility || 'club'} + isPromotion={true} + mode={rightsUpgradeMode} + />
diff --git a/frontend/src/version.js b/frontend/src/version.js index c8b4055..8e7687b 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,6 +1,6 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.8.78" +export const APP_VERSION = "0.8.80" export const BUILD_DATE = "2026-05-11" export const PAGE_VERSIONS = { @@ -20,7 +20,7 @@ export const PAGE_VERSIONS = { TrainingCoachPage: "1.0.0", AdminCatalogsPage: "2.2.0", TrainerContextsPage: "1.0.0", - MediaLibraryPage: "1.3.0", // P-06: Superadmin Medienjournal-Modal + MediaLibraryPage: "1.4.0", // P-06: Rechte-Dialog bei Sichtbarkeits-Promotion ExerciseInlineFileMediaModal: "1.1.0", // P-06: RightsDeclarationDialog vor Upload ExerciseInlineEmbedModal: "1.1.0", // P-06: RightsDeclarationDialog vor Embed-Upload } diff --git a/tests/dev-smoke-test.spec.js b/tests/dev-smoke-test.spec.js index fcec11c..4681ce2 100644 --- a/tests/dev-smoke-test.spec.js +++ b/tests/dev-smoke-test.spec.js @@ -204,19 +204,23 @@ for (const route of LEGAL_ROUTES) { // Seite ist erreichbar (kein Redirect zur Login-Seite) expect(page.url()).toContain(route.path); - // Platzhalterhinweis sichtbar - const hinweis = await page.getByText('MUSTER / PLATZHALTER').first(); - await expect(hinweis).toBeVisible(); - // Seitentitel korrekt await expect(page.getByRole('heading', { level: 1 })).toContainText(route.label); + // Platzhalterhinweis sichtbar – nur wenn noch kein echtes Dokument veröffentlicht wurde + const hasPlaceholder = await page.getByText('MUSTER / PLATZHALTER').first().isVisible().catch(() => false); + if (hasPlaceholder) { + console.log(`✓ P-01: ${route.label} – Platzhalter sichtbar`); + } else { + console.log(`✓ P-01: ${route.label} – echtes Dokument veröffentlicht (kein Platzhalter)`); + } + // Reload funktioniert await page.reload(); await page.waitForLoadState('networkidle'); expect(page.url()).toContain(route.path); - console.log(`✓ P-01: ${route.label} – ohne Auth erreichbar, Platzhalter sichtbar, Reload OK`); + console.log(`✓ P-01: ${route.label} – ohne Auth erreichbar, Reload OK`); }); }