feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m2s
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m2s
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations (person_consent_context, parental_consent_context, music_rights_context, third_party_rights_context) fuer Freitext zum Einwilligungskontext. Backend: - media_rights.py: write_rights_declaration speichert 4 Kontextfelder - media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload, RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody - exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media, wird in INSERT gespeichert Frontend (alle 3 Formulare): - RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) + Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige, Musik, Fremdinhalte) - ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab - ExerciseInlineEmbedModal: gleiche Felder inline - api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets version: 0.8.77 module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
42aec79ad1
commit
4bc24b4caf
|
|
@ -290,6 +290,12 @@ def assert_rights_for_exercise_link(cur: Any, asset_id: int, exercise_visibility
|
||||||
# Declaration-Log schreiben + Schnellfelder aktualisieren
|
# Declaration-Log schreiben + Schnellfelder aktualisieren
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _clean_context(val: Any) -> Optional[str]:
|
||||||
|
"""Leere Strings → None, sonst auf 2000 Zeichen kuerzen."""
|
||||||
|
s = (val or "").strip()
|
||||||
|
return s[:2000] if s else None
|
||||||
|
|
||||||
|
|
||||||
def write_rights_declaration(
|
def write_rights_declaration(
|
||||||
cur: Any,
|
cur: Any,
|
||||||
asset_id: int,
|
asset_id: int,
|
||||||
|
|
@ -307,11 +313,11 @@ def write_rights_declaration(
|
||||||
media_asset_id, declared_by_profile_id, action_type, target_visibility,
|
media_asset_id, declared_by_profile_id, action_type, target_visibility,
|
||||||
declaration_version,
|
declaration_version,
|
||||||
rights_holder_confirmed,
|
rights_holder_confirmed,
|
||||||
contains_identifiable_persons, person_consent_confirmed,
|
contains_identifiable_persons, person_consent_confirmed, person_consent_context,
|
||||||
contains_minors, parental_consent_confirmed,
|
contains_minors, parental_consent_confirmed, parental_consent_context,
|
||||||
contains_music, music_rights_confirmed,
|
contains_music, music_rights_confirmed, music_rights_context,
|
||||||
contains_third_party_content, third_party_rights_confirmed
|
contains_third_party_content, third_party_rights_confirmed, third_party_rights_context
|
||||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
RETURNING id""",
|
RETURNING id""",
|
||||||
(
|
(
|
||||||
asset_id,
|
asset_id,
|
||||||
|
|
@ -322,12 +328,16 @@ def write_rights_declaration(
|
||||||
bool(decl.get("rights_holder_confirmed")),
|
bool(decl.get("rights_holder_confirmed")),
|
||||||
decl.get("contains_identifiable_persons"),
|
decl.get("contains_identifiable_persons"),
|
||||||
decl.get("person_consent_confirmed"),
|
decl.get("person_consent_confirmed"),
|
||||||
|
_clean_context(decl.get("person_consent_context")),
|
||||||
decl.get("contains_minors"),
|
decl.get("contains_minors"),
|
||||||
decl.get("parental_consent_confirmed"),
|
decl.get("parental_consent_confirmed"),
|
||||||
|
_clean_context(decl.get("parental_consent_context")),
|
||||||
decl.get("contains_music"),
|
decl.get("contains_music"),
|
||||||
decl.get("music_rights_confirmed"),
|
decl.get("music_rights_confirmed"),
|
||||||
|
_clean_context(decl.get("music_rights_context")),
|
||||||
decl.get("contains_third_party_content"),
|
decl.get("contains_third_party_content"),
|
||||||
decl.get("third_party_rights_confirmed"),
|
decl.get("third_party_rights_confirmed"),
|
||||||
|
_clean_context(decl.get("third_party_rights_context")),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
row = cur.fetchone()
|
row = cur.fetchone()
|
||||||
|
|
|
||||||
18
backend/migrations/049_media_rights_consent_context.sql
Normal file
18
backend/migrations/049_media_rights_consent_context.sql
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
-- Migration 049: P-06 Erweiterung – Einwilligungskontext-Felder und Copyright im Upload-Dialog
|
||||||
|
-- Optionale Freitextfelder fuer den Kontext der Einwilligung (z.B. "Schriftliche Einwilligung
|
||||||
|
-- vom 2026-05-01 liegt vor") sowie copyright_notice direkt beim Upload erfassbar.
|
||||||
|
|
||||||
|
ALTER TABLE media_asset_rights_declarations
|
||||||
|
ADD COLUMN IF NOT EXISTS person_consent_context TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS parental_consent_context TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS music_rights_context TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS third_party_rights_context TEXT;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN media_asset_rights_declarations.person_consent_context IS
|
||||||
|
'Optionaler Freitext: In welchem Zusammenhang liegt die Einwilligung der abgebildeten Personen vor?';
|
||||||
|
COMMENT ON COLUMN media_asset_rights_declarations.parental_consent_context IS
|
||||||
|
'Optionaler Freitext: In welchem Zusammenhang liegt die Einwilligung der Sorgeberechtigten vor?';
|
||||||
|
COMMENT ON COLUMN media_asset_rights_declarations.music_rights_context IS
|
||||||
|
'Optionaler Freitext: Welche Lizenz / GEMA-Regelung liegt fuer die Musik vor?';
|
||||||
|
COMMENT ON COLUMN media_asset_rights_declarations.third_party_rights_context IS
|
||||||
|
'Optionaler Freitext: Auf welcher Grundlage duerfen die enthaltenen Fremdinhalte verwendet werden?';
|
||||||
|
|
@ -2527,16 +2527,21 @@ async def upload_exercise_media(
|
||||||
description: str = Form(""),
|
description: str = Form(""),
|
||||||
context: str = Form("ablauf"),
|
context: str = Form("ablauf"),
|
||||||
is_primary: bool = Form(False),
|
is_primary: bool = Form(False),
|
||||||
|
copyright_notice: Optional[str] = Form(None),
|
||||||
# P-06: Rechte-Erklaerung (Pflicht bei Datei-Upload mit neuem media_asset)
|
# P-06: Rechte-Erklaerung (Pflicht bei Datei-Upload mit neuem media_asset)
|
||||||
rights_holder_confirmed: Optional[bool] = Form(None),
|
rights_holder_confirmed: Optional[bool] = Form(None),
|
||||||
contains_identifiable_persons: Optional[bool] = Form(None),
|
contains_identifiable_persons: Optional[bool] = Form(None),
|
||||||
person_consent_confirmed: Optional[bool] = Form(None),
|
person_consent_confirmed: Optional[bool] = Form(None),
|
||||||
|
person_consent_context: Optional[str] = Form(None),
|
||||||
contains_minors: Optional[bool] = Form(None),
|
contains_minors: Optional[bool] = Form(None),
|
||||||
parental_consent_confirmed: Optional[bool] = Form(None),
|
parental_consent_confirmed: Optional[bool] = Form(None),
|
||||||
|
parental_consent_context: Optional[str] = Form(None),
|
||||||
contains_music: Optional[bool] = Form(None),
|
contains_music: Optional[bool] = Form(None),
|
||||||
music_rights_confirmed: Optional[bool] = Form(None),
|
music_rights_confirmed: Optional[bool] = Form(None),
|
||||||
|
music_rights_context: Optional[str] = Form(None),
|
||||||
contains_third_party_content: Optional[bool] = Form(None),
|
contains_third_party_content: Optional[bool] = Form(None),
|
||||||
third_party_rights_confirmed: Optional[bool] = Form(None),
|
third_party_rights_confirmed: Optional[bool] = Form(None),
|
||||||
|
third_party_rights_context: Optional[str] = Form(None),
|
||||||
):
|
):
|
||||||
profile_id = tenant.profile_id
|
profile_id = tenant.profile_id
|
||||||
if media_type not in ("image", "video", "document", "sketch"):
|
if media_type not in ("image", "video", "document", "sketch"):
|
||||||
|
|
@ -2775,11 +2780,12 @@ async def upload_exercise_media(
|
||||||
if not dest_path.is_file():
|
if not dest_path.is_file():
|
||||||
dest_path.write_bytes(raw)
|
dest_path.write_bytes(raw)
|
||||||
|
|
||||||
|
clean_cr = (copyright_notice or "").strip() or None
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""INSERT INTO media_assets (
|
"""INSERT INTO media_assets (
|
||||||
mime_type, byte_size, sha256, original_filename, visibility, club_id,
|
mime_type, byte_size, sha256, original_filename, visibility, club_id,
|
||||||
uploaded_by_profile_id, copyright_notice, storage_backend, storage_key, lifecycle_state
|
uploaded_by_profile_id, copyright_notice, storage_backend, storage_key, lifecycle_state
|
||||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, NULL, 'local', %s, 'active')
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'local', %s, 'active')
|
||||||
RETURNING id""",
|
RETURNING id""",
|
||||||
(
|
(
|
||||||
mime,
|
mime,
|
||||||
|
|
@ -2789,6 +2795,7 @@ async def upload_exercise_media(
|
||||||
ex_vis,
|
ex_vis,
|
||||||
dedupe_club,
|
dedupe_club,
|
||||||
profile_id,
|
profile_id,
|
||||||
|
clean_cr,
|
||||||
storage_key,
|
storage_key,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -2799,12 +2806,16 @@ async def upload_exercise_media(
|
||||||
"rights_holder_confirmed": rights_holder_confirmed,
|
"rights_holder_confirmed": rights_holder_confirmed,
|
||||||
"contains_identifiable_persons": contains_identifiable_persons,
|
"contains_identifiable_persons": contains_identifiable_persons,
|
||||||
"person_consent_confirmed": person_consent_confirmed,
|
"person_consent_confirmed": person_consent_confirmed,
|
||||||
|
"person_consent_context": person_consent_context,
|
||||||
"contains_minors": contains_minors,
|
"contains_minors": contains_minors,
|
||||||
"parental_consent_confirmed": parental_consent_confirmed,
|
"parental_consent_confirmed": parental_consent_confirmed,
|
||||||
|
"parental_consent_context": parental_consent_context,
|
||||||
"contains_music": contains_music,
|
"contains_music": contains_music,
|
||||||
"music_rights_confirmed": music_rights_confirmed,
|
"music_rights_confirmed": music_rights_confirmed,
|
||||||
|
"music_rights_context": music_rights_context,
|
||||||
"contains_third_party_content": contains_third_party_content,
|
"contains_third_party_content": contains_third_party_content,
|
||||||
"third_party_rights_confirmed": third_party_rights_confirmed,
|
"third_party_rights_confirmed": third_party_rights_confirmed,
|
||||||
|
"third_party_rights_context": third_party_rights_context,
|
||||||
}
|
}
|
||||||
validate_rights_declaration(p06_decl, ex_vis)
|
validate_rights_declaration(p06_decl, ex_vis)
|
||||||
write_rights_declaration(cur, aid, profile_id, "upload", ex_vis, p06_decl)
|
write_rights_declaration(cur, aid, profile_id, "upload", ex_vis, p06_decl)
|
||||||
|
|
|
||||||
|
|
@ -82,12 +82,16 @@ class MediaAssetPatch(BaseModel):
|
||||||
rights_holder_confirmed: Optional[bool] = None
|
rights_holder_confirmed: Optional[bool] = None
|
||||||
contains_identifiable_persons: Optional[bool] = None
|
contains_identifiable_persons: Optional[bool] = None
|
||||||
person_consent_confirmed: Optional[bool] = None
|
person_consent_confirmed: Optional[bool] = None
|
||||||
|
person_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_minors: Optional[bool] = None
|
contains_minors: Optional[bool] = None
|
||||||
parental_consent_confirmed: Optional[bool] = None
|
parental_consent_confirmed: Optional[bool] = None
|
||||||
|
parental_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_music: Optional[bool] = None
|
contains_music: Optional[bool] = None
|
||||||
music_rights_confirmed: Optional[bool] = None
|
music_rights_confirmed: Optional[bool] = None
|
||||||
|
music_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_third_party_content: Optional[bool] = None
|
contains_third_party_content: Optional[bool] = None
|
||||||
third_party_rights_confirmed: Optional[bool] = None
|
third_party_rights_confirmed: Optional[bool] = None
|
||||||
|
third_party_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
|
|
||||||
|
|
||||||
class RightsDeclarationBody(BaseModel):
|
class RightsDeclarationBody(BaseModel):
|
||||||
|
|
@ -96,12 +100,16 @@ class RightsDeclarationBody(BaseModel):
|
||||||
rights_holder_confirmed: bool
|
rights_holder_confirmed: bool
|
||||||
contains_identifiable_persons: bool
|
contains_identifiable_persons: bool
|
||||||
person_consent_confirmed: Optional[bool] = None
|
person_consent_confirmed: Optional[bool] = None
|
||||||
|
person_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_minors: bool
|
contains_minors: bool
|
||||||
parental_consent_confirmed: Optional[bool] = None
|
parental_consent_confirmed: Optional[bool] = None
|
||||||
|
parental_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_music: bool
|
contains_music: bool
|
||||||
music_rights_confirmed: Optional[bool] = None
|
music_rights_confirmed: Optional[bool] = None
|
||||||
|
music_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
contains_third_party_content: bool
|
contains_third_party_content: bool
|
||||||
third_party_rights_confirmed: Optional[bool] = None
|
third_party_rights_confirmed: Optional[bool] = None
|
||||||
|
third_party_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
|
|
||||||
|
|
||||||
class MediaBulkLifecycleBody(BaseModel):
|
class MediaBulkLifecycleBody(BaseModel):
|
||||||
|
|
@ -132,6 +140,11 @@ class MediaBulkPatchBody(BaseModel):
|
||||||
original_filename: Optional[str] = Field(None, max_length=300)
|
original_filename: Optional[str] = Field(None, max_length=300)
|
||||||
visibility: Optional[str] = Field(None, pattern="^(private|club|official)$")
|
visibility: Optional[str] = Field(None, pattern="^(private|club|official)$")
|
||||||
club_id: Optional[int] = None
|
club_id: Optional[int] = None
|
||||||
|
# P-06 Kontext (bei Promotion)
|
||||||
|
person_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
|
parental_consent_context: Optional[str] = Field(None, max_length=2000)
|
||||||
|
music_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
|
third_party_rights_context: Optional[str] = Field(None, max_length=2000)
|
||||||
tags: Optional[list[str]] = None
|
tags: Optional[list[str]] = None
|
||||||
# P-06: Rechterklaerung (gilt fuer alle Assets des Batches bei Promotion)
|
# P-06: Rechterklaerung (gilt fuer alle Assets des Batches bei Promotion)
|
||||||
rights_holder_confirmed: Optional[bool] = None
|
rights_holder_confirmed: Optional[bool] = None
|
||||||
|
|
@ -671,6 +684,7 @@ def _ingest_library_media_file(
|
||||||
visibility: str,
|
visibility: str,
|
||||||
club_id_form: Optional[int],
|
club_id_form: Optional[int],
|
||||||
decl: Optional[dict] = None,
|
decl: Optional[dict] = None,
|
||||||
|
copyright_notice: Optional[str] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Neues Archiv-Medium oder aktiver Dedupe-Treffer (sha256 + Sichtbarkeit + Verein). Kein exercise_media.
|
"""Neues Archiv-Medium oder aktiver Dedupe-Treffer (sha256 + Sichtbarkeit + Verein). Kein exercise_media.
|
||||||
|
|
||||||
|
|
@ -809,11 +823,12 @@ def _ingest_library_media_file(
|
||||||
if not dest_path.is_file():
|
if not dest_path.is_file():
|
||||||
dest_path.write_bytes(raw)
|
dest_path.write_bytes(raw)
|
||||||
|
|
||||||
|
clean_cr = (copyright_notice or "").strip() or None
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""INSERT INTO media_assets (
|
"""INSERT INTO media_assets (
|
||||||
mime_type, byte_size, sha256, original_filename, visibility, club_id,
|
mime_type, byte_size, sha256, original_filename, visibility, club_id,
|
||||||
uploaded_by_profile_id, copyright_notice, storage_backend, storage_key, lifecycle_state
|
uploaded_by_profile_id, copyright_notice, storage_backend, storage_key, lifecycle_state
|
||||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, NULL, 'local', %s, 'active')
|
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'local', %s, 'active')
|
||||||
RETURNING id""",
|
RETURNING id""",
|
||||||
(
|
(
|
||||||
mime,
|
mime,
|
||||||
|
|
@ -823,6 +838,7 @@ def _ingest_library_media_file(
|
||||||
vis,
|
vis,
|
||||||
next_cid,
|
next_cid,
|
||||||
profile_id,
|
profile_id,
|
||||||
|
clean_cr,
|
||||||
storage_key,
|
storage_key,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -841,16 +857,21 @@ async def bulk_upload_media_assets(
|
||||||
files: list[UploadFile] = File(..., description="Mehrere Dateien (jpeg, png, gif, mp4, pdf)"),
|
files: list[UploadFile] = File(..., description="Mehrere Dateien (jpeg, png, gif, mp4, pdf)"),
|
||||||
visibility: str = Form("private"),
|
visibility: str = Form("private"),
|
||||||
club_id: Optional[int] = Form(None),
|
club_id: Optional[int] = Form(None),
|
||||||
|
copyright_notice: Optional[str] = Form(None),
|
||||||
# P-06 Rechterklaerung (gilt fuer alle Dateien des Batches)
|
# P-06 Rechterklaerung (gilt fuer alle Dateien des Batches)
|
||||||
rights_holder_confirmed: bool = Form(...),
|
rights_holder_confirmed: bool = Form(...),
|
||||||
contains_identifiable_persons: bool = Form(...),
|
contains_identifiable_persons: bool = Form(...),
|
||||||
person_consent_confirmed: Optional[bool] = Form(None),
|
person_consent_confirmed: Optional[bool] = Form(None),
|
||||||
|
person_consent_context: Optional[str] = Form(None),
|
||||||
contains_minors: bool = Form(...),
|
contains_minors: bool = Form(...),
|
||||||
parental_consent_confirmed: Optional[bool] = Form(None),
|
parental_consent_confirmed: Optional[bool] = Form(None),
|
||||||
|
parental_consent_context: Optional[str] = Form(None),
|
||||||
contains_music: bool = Form(...),
|
contains_music: bool = Form(...),
|
||||||
music_rights_confirmed: Optional[bool] = Form(None),
|
music_rights_confirmed: Optional[bool] = Form(None),
|
||||||
|
music_rights_context: Optional[str] = Form(None),
|
||||||
contains_third_party_content: bool = Form(...),
|
contains_third_party_content: bool = Form(...),
|
||||||
third_party_rights_confirmed: Optional[bool] = Form(None),
|
third_party_rights_confirmed: Optional[bool] = Form(None),
|
||||||
|
third_party_rights_context: Optional[str] = Form(None),
|
||||||
):
|
):
|
||||||
"""Mehrere Dateien ins Archiv; Dedupe wie Übungs-Upload. Pro Datei eigene DB-Transaktion.
|
"""Mehrere Dateien ins Archiv; Dedupe wie Übungs-Upload. Pro Datei eigene DB-Transaktion.
|
||||||
|
|
||||||
|
|
@ -869,12 +890,16 @@ async def bulk_upload_media_assets(
|
||||||
"rights_holder_confirmed": rights_holder_confirmed,
|
"rights_holder_confirmed": rights_holder_confirmed,
|
||||||
"contains_identifiable_persons": contains_identifiable_persons,
|
"contains_identifiable_persons": contains_identifiable_persons,
|
||||||
"person_consent_confirmed": person_consent_confirmed,
|
"person_consent_confirmed": person_consent_confirmed,
|
||||||
|
"person_consent_context": person_consent_context,
|
||||||
"contains_minors": contains_minors,
|
"contains_minors": contains_minors,
|
||||||
"parental_consent_confirmed": parental_consent_confirmed,
|
"parental_consent_confirmed": parental_consent_confirmed,
|
||||||
|
"parental_consent_context": parental_consent_context,
|
||||||
"contains_music": contains_music,
|
"contains_music": contains_music,
|
||||||
"music_rights_confirmed": music_rights_confirmed,
|
"music_rights_confirmed": music_rights_confirmed,
|
||||||
|
"music_rights_context": music_rights_context,
|
||||||
"contains_third_party_content": contains_third_party_content,
|
"contains_third_party_content": contains_third_party_content,
|
||||||
"third_party_rights_confirmed": third_party_rights_confirmed,
|
"third_party_rights_confirmed": third_party_rights_confirmed,
|
||||||
|
"third_party_rights_context": third_party_rights_context,
|
||||||
}
|
}
|
||||||
target_vis = (visibility or "private").strip().lower()
|
target_vis = (visibility or "private").strip().lower()
|
||||||
validate_rights_declaration(decl, target_vis)
|
validate_rights_declaration(decl, target_vis)
|
||||||
|
|
@ -901,6 +926,7 @@ async def bulk_upload_media_assets(
|
||||||
visibility,
|
visibility,
|
||||||
club_id,
|
club_id,
|
||||||
decl=decl,
|
decl=decl,
|
||||||
|
copyright_notice=copyright_notice,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
results.append({"filename": fn, "ok": True, **r})
|
results.append({"filename": fn, "ok": True, **r})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Shinkan Jinkendo Version Information
|
# Shinkan Jinkendo Version Information
|
||||||
|
|
||||||
APP_VERSION = "0.8.76"
|
APP_VERSION = "0.8.77"
|
||||||
BUILD_DATE = "2026-05-11"
|
BUILD_DATE = "2026-05-11"
|
||||||
DB_SCHEMA_VERSION = "20260511048"
|
DB_SCHEMA_VERSION = "20260511048"
|
||||||
|
|
||||||
|
|
@ -14,12 +14,12 @@ MODULE_VERSIONS = {
|
||||||
"club_join_requests": "1.0.1", # Depends(get_tenant_context)
|
"club_join_requests": "1.0.1", # Depends(get_tenant_context)
|
||||||
"admin_users": "1.0.0", # GET /api/admin/users
|
"admin_users": "1.0.0", # GET /api/admin/users
|
||||||
"platform_media_storage": "1.0.0", # GET/PUT /api/admin/platform-media-storage (Superadmin-Pfad unter MEDIA_ROOT)
|
"platform_media_storage": "1.0.0", # GET/PUT /api/admin/platform-media-storage (Superadmin-Pfad unter MEDIA_ROOT)
|
||||||
"media_rights": "1.0.0", # P-06: zentrales Policy-Modul (validate, coverage, write declaration)
|
"media_rights": "1.1.0", # P-06+: write_rights_declaration + 4 Kontext-Freitextfelder
|
||||||
"media_assets": "1.13.0", # P-06: Rechte-Erklaerung bei Upload/Promotion; Re-Deklarations-Endpoint; Admin-Legacy-Summary
|
"media_assets": "1.14.0", # P-06+: copyright_notice im Upload-Dialog; 4 Kontext-Felder in Bulk-Upload + PATCH + Re-Deklaration
|
||||||
"groups": "0.1.0",
|
"groups": "0.1.0",
|
||||||
"skills": "0.1.0",
|
"skills": "0.1.0",
|
||||||
"methods": "0.1.0",
|
"methods": "0.1.0",
|
||||||
"exercises": "2.20.0", # P-06: upload_exercise_media + from-asset Rechtspruefung
|
"exercises": "2.21.0", # P-06+: copyright_notice + 4 Kontext-Felder in upload_exercise_media
|
||||||
"training_units": "0.2.0",
|
"training_units": "0.2.0",
|
||||||
"training_programs": "0.1.0",
|
"training_programs": "0.1.0",
|
||||||
"planning": "0.8.1", # Vorlagen Leseprüfung library_content_visible_to_profile
|
"planning": "0.8.1", # Vorlagen Leseprüfung library_content_visible_to_profile
|
||||||
|
|
@ -31,6 +31,13 @@ MODULE_VERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
{
|
||||||
|
"version": "0.8.77",
|
||||||
|
"date": "2026-05-11",
|
||||||
|
"changes": [
|
||||||
|
"P-06 Erweiterung: Migration 049 — 4 optionale Freitext-Kontextfelder in media_asset_rights_declarations (person_consent_context, parental_consent_context, music_rights_context, third_party_rights_context); copyright_notice direkt im Upload-Dialog erfassbar; alle drei Dialoge (RightsDeclarationDialog, ExerciseInlineFileMediaModal, ExerciseInlineEmbedModal) und alle Backend-Endpoints aktualisiert.",
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "0.8.76",
|
"version": "0.8.76",
|
||||||
"date": "2026-05-11",
|
"date": "2026-05-11",
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,20 @@ import {
|
||||||
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
|
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
|
||||||
|
|
||||||
const DECL_INIT = {
|
const DECL_INIT = {
|
||||||
|
copyright_notice: '',
|
||||||
rights_holder_confirmed: false,
|
rights_holder_confirmed: false,
|
||||||
contains_identifiable_persons: null,
|
contains_identifiable_persons: null,
|
||||||
person_consent_confirmed: false,
|
person_consent_confirmed: false,
|
||||||
|
person_consent_context: '',
|
||||||
contains_minors: null,
|
contains_minors: null,
|
||||||
parental_consent_confirmed: false,
|
parental_consent_confirmed: false,
|
||||||
|
parental_consent_context: '',
|
||||||
contains_music: null,
|
contains_music: null,
|
||||||
music_rights_confirmed: false,
|
music_rights_confirmed: false,
|
||||||
|
music_rights_context: '',
|
||||||
contains_third_party_content: null,
|
contains_third_party_content: null,
|
||||||
third_party_rights_confirmed: false,
|
third_party_rights_confirmed: false,
|
||||||
|
third_party_rights_context: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateDecl(decl) {
|
function validateDecl(decl) {
|
||||||
|
|
@ -178,15 +183,17 @@ export default function ExerciseInlineEmbedModal({
|
||||||
Rechte-Erklärung <span style={{ fontWeight: 400, color: 'var(--text3)' }}>(VORLÄUFIG – p06-v1-conservative)</span>
|
Rechte-Erklärung <span style={{ fontWeight: 400, color: 'var(--text3)' }}>(VORLÄUFIG – p06-v1-conservative)</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label htmlFor="emb-cr" style={{ display: 'block', fontSize: '0.82rem', fontWeight: 600, marginBottom: '3px' }}>Copyright-Angabe (optional)</label>
|
||||||
|
<input id="emb-cr" type="text" className="form-input" placeholder="z.B. © 2026 Lars Stommer, CC BY-NC"
|
||||||
|
value={decl.copyright_notice} onChange={(e) => setDeclField('copyright_notice', e.target.value)}
|
||||||
|
disabled={busy} style={{ fontSize: '0.85rem' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '10px' }}>
|
||||||
<input
|
<input type="checkbox" id="emb-rhc" checked={decl.rights_holder_confirmed}
|
||||||
type="checkbox"
|
|
||||||
id="emb-rhc"
|
|
||||||
checked={decl.rights_holder_confirmed}
|
|
||||||
onChange={(e) => setDeclField('rights_holder_confirmed', e.target.checked)}
|
onChange={(e) => setDeclField('rights_holder_confirmed', e.target.checked)}
|
||||||
disabled={busy}
|
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
style={{ marginTop: '3px', flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<label htmlFor="emb-rhc" style={{ fontSize: '0.85rem' }}>
|
<label htmlFor="emb-rhc" style={{ fontSize: '0.85rem' }}>
|
||||||
Ich bestätige, dass ich die erforderlichen Rechte an diesem Medium besitze oder zur Veröffentlichung berechtigt bin. *
|
Ich bestätige, dass ich die erforderlichen Rechte an diesem Medium besitze oder zur Veröffentlichung berechtigt bin. *
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -194,96 +201,72 @@ export default function ExerciseInlineEmbedModal({
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Erkennbare Personen abgebildet? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Erkennbare Personen abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cip" checked={decl.contains_identifiable_persons === true} onChange={() => setDeclField('contains_identifiable_persons', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="emb-cip" checked={decl.contains_identifiable_persons === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cip" checked={decl.contains_identifiable_persons === false} onChange={() => setDeclField('contains_identifiable_persons', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_identifiable_persons', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="emb-cip" checked={decl.contains_identifiable_persons === false}
|
|
||||||
onChange={() => setDeclField('contains_identifiable_persons', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_identifiable_persons === true && (
|
{decl.contains_identifiable_persons === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="emb-pcc" checked={decl.person_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('person_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="emb-pcc" checked={decl.person_consent_confirmed} onChange={(e) => setDeclField('person_consent_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="emb-pcc" style={{ fontSize: '0.85rem' }}>Einwilligungen aller erkennbaren Personen liegen vor. *</label>
|
||||||
<label htmlFor="emb-pcc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Einwilligungen aller erkennbaren Personen liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Einwilligungskontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Schriftliche Einwilligung vom 2026-05-01" value={decl.person_consent_context} onChange={(e) => setDeclField('person_consent_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Minderjährige abgebildet? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Minderjährige abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cm" checked={decl.contains_minors === true} onChange={() => setDeclField('contains_minors', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="emb-cm" checked={decl.contains_minors === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cm" checked={decl.contains_minors === false} onChange={() => setDeclField('contains_minors', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_minors', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="emb-cm" checked={decl.contains_minors === false}
|
|
||||||
onChange={() => setDeclField('contains_minors', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_minors === true && (
|
{decl.contains_minors === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="emb-pcc2" checked={decl.parental_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('parental_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="emb-pcc2" checked={decl.parental_consent_confirmed} onChange={(e) => setDeclField('parental_consent_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="emb-pcc2" style={{ fontSize: '0.85rem' }}>Einwilligungen der Sorgeberechtigten liegen vor. *</label>
|
||||||
<label htmlFor="emb-pcc2" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Einwilligungen der Sorgeberechtigten liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Einwilligungskontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Elterliche Einwilligung per E-Mail vom 2026-04-15" value={decl.parental_consent_context} onChange={(e) => setDeclField('parental_consent_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Musik enthalten? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Musik enthalten? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cmu" checked={decl.contains_music === true} onChange={() => setDeclField('contains_music', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="emb-cmu" checked={decl.contains_music === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-cmu" checked={decl.contains_music === false} onChange={() => setDeclField('contains_music', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_music', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="emb-cmu" checked={decl.contains_music === false}
|
|
||||||
onChange={() => setDeclField('contains_music', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_music === true && (
|
{decl.contains_music === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="emb-mrc" checked={decl.music_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('music_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="emb-mrc" checked={decl.music_rights_confirmed} onChange={(e) => setDeclField('music_rights_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="emb-mrc" style={{ fontSize: '0.85rem' }}>Musikrechte (GEMA / Lizenz) liegen vor. *</label>
|
||||||
<label htmlFor="emb-mrc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Musikrechte (GEMA / Lizenz) liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Lizenz / GEMA-Kontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. CC BY 4.0 oder GEMA-Freimeldung Nr. …" value={decl.music_rights_context} onChange={(e) => setDeclField('music_rights_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '4px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '4px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-ctpc" checked={decl.contains_third_party_content === true} onChange={() => setDeclField('contains_third_party_content', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="emb-ctpc" checked={decl.contains_third_party_content === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="emb-ctpc" checked={decl.contains_third_party_content === false} onChange={() => setDeclField('contains_third_party_content', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_third_party_content', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="emb-ctpc" checked={decl.contains_third_party_content === false}
|
|
||||||
onChange={() => setDeclField('contains_third_party_content', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_third_party_content === true && (
|
{decl.contains_third_party_content === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="emb-tprc" checked={decl.third_party_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('third_party_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="emb-tprc" checked={decl.third_party_rights_confirmed} onChange={(e) => setDeclField('third_party_rights_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="emb-tprc" style={{ fontSize: '0.85rem' }}>Rechte an allen enthaltenen Fremdmaterialien liegen vor. *</label>
|
||||||
<label htmlFor="emb-tprc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Rechte an allen enthaltenen Fremdmaterialien liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Rechtsgrundlage (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Verbandslogo mit Genehmigung vom 2026-03-10" value={decl.third_party_rights_context} onChange={(e) => setDeclField('third_party_rights_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
||||||
|
|
@ -61,15 +61,20 @@ function inferExerciseMediaType(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DECL_INIT = {
|
const DECL_INIT = {
|
||||||
|
copyright_notice: '',
|
||||||
rights_holder_confirmed: false,
|
rights_holder_confirmed: false,
|
||||||
contains_identifiable_persons: null,
|
contains_identifiable_persons: null,
|
||||||
person_consent_confirmed: false,
|
person_consent_confirmed: false,
|
||||||
|
person_consent_context: '',
|
||||||
contains_minors: null,
|
contains_minors: null,
|
||||||
parental_consent_confirmed: false,
|
parental_consent_confirmed: false,
|
||||||
|
parental_consent_context: '',
|
||||||
contains_music: null,
|
contains_music: null,
|
||||||
music_rights_confirmed: false,
|
music_rights_confirmed: false,
|
||||||
|
music_rights_context: '',
|
||||||
contains_third_party_content: null,
|
contains_third_party_content: null,
|
||||||
third_party_rights_confirmed: false,
|
third_party_rights_confirmed: false,
|
||||||
|
third_party_rights_context: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateDecl(decl) {
|
function validateDecl(decl) {
|
||||||
|
|
@ -414,15 +419,19 @@ export default function ExerciseInlineFileMediaModal({
|
||||||
Rechte-Erklärung <span style={{ fontWeight: 400, color: 'var(--text3)' }}>(VORLÄUFIG – p06-v1-conservative)</span>
|
Rechte-Erklärung <span style={{ fontWeight: 400, color: 'var(--text3)' }}>(VORLÄUFIG – p06-v1-conservative)</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<label htmlFor="up-cr" style={{ display: 'block', fontSize: '0.82rem', fontWeight: 600, marginBottom: '3px' }}>Copyright-Angabe (optional)</label>
|
||||||
|
<input id="up-cr" type="text" className="form-input"
|
||||||
|
placeholder="z.B. © 2026 Lars Stommer, CC BY-NC"
|
||||||
|
value={decl.copyright_notice}
|
||||||
|
onChange={(e) => setDeclField('copyright_notice', e.target.value)}
|
||||||
|
disabled={busy} style={{ fontSize: '0.85rem' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '10px' }}>
|
||||||
<input
|
<input type="checkbox" id="up-rhc" checked={decl.rights_holder_confirmed}
|
||||||
type="checkbox"
|
|
||||||
id="up-rhc"
|
|
||||||
checked={decl.rights_holder_confirmed}
|
|
||||||
onChange={(e) => setDeclField('rights_holder_confirmed', e.target.checked)}
|
onChange={(e) => setDeclField('rights_holder_confirmed', e.target.checked)}
|
||||||
disabled={busy}
|
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
style={{ marginTop: '3px', flexShrink: 0 }}
|
|
||||||
/>
|
|
||||||
<label htmlFor="up-rhc" style={{ fontSize: '0.85rem' }}>
|
<label htmlFor="up-rhc" style={{ fontSize: '0.85rem' }}>
|
||||||
Ich bestätige, dass ich die erforderlichen Rechte an diesem Medium besitze oder zur Veröffentlichung berechtigt bin. *
|
Ich bestätige, dass ich die erforderlichen Rechte an diesem Medium besitze oder zur Veröffentlichung berechtigt bin. *
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -430,96 +439,72 @@ export default function ExerciseInlineFileMediaModal({
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Erkennbare Personen abgebildet? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Erkennbare Personen abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cip" checked={decl.contains_identifiable_persons === true} onChange={() => setDeclField('contains_identifiable_persons', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="up-cip" checked={decl.contains_identifiable_persons === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cip" checked={decl.contains_identifiable_persons === false} onChange={() => setDeclField('contains_identifiable_persons', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_identifiable_persons', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="up-cip" checked={decl.contains_identifiable_persons === false}
|
|
||||||
onChange={() => setDeclField('contains_identifiable_persons', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_identifiable_persons === true && (
|
{decl.contains_identifiable_persons === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="up-pcc" checked={decl.person_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('person_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="up-pcc" checked={decl.person_consent_confirmed} onChange={(e) => setDeclField('person_consent_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="up-pcc" style={{ fontSize: '0.85rem' }}>Einwilligungen aller erkennbaren Personen liegen vor. *</label>
|
||||||
<label htmlFor="up-pcc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Einwilligungen aller erkennbaren Personen liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Einwilligungskontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Schriftliche Einwilligung vom 2026-05-01" value={decl.person_consent_context} onChange={(e) => setDeclField('person_consent_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Minderjährige abgebildet? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Minderjährige abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cm" checked={decl.contains_minors === true} onChange={() => setDeclField('contains_minors', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="up-cm" checked={decl.contains_minors === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cm" checked={decl.contains_minors === false} onChange={() => setDeclField('contains_minors', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_minors', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="up-cm" checked={decl.contains_minors === false}
|
|
||||||
onChange={() => setDeclField('contains_minors', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_minors === true && (
|
{decl.contains_minors === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="up-pcc2" checked={decl.parental_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('parental_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="up-pcc2" checked={decl.parental_consent_confirmed} onChange={(e) => setDeclField('parental_consent_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="up-pcc2" style={{ fontSize: '0.85rem' }}>Einwilligungen der Sorgeberechtigten liegen vor. *</label>
|
||||||
<label htmlFor="up-pcc2" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Einwilligungen der Sorgeberechtigten liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Einwilligungskontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Elterliche Einwilligung per E-Mail vom 2026-04-15" value={decl.parental_consent_context} onChange={(e) => setDeclField('parental_consent_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '10px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Musik enthalten? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Musik enthalten? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cmu" checked={decl.contains_music === true} onChange={() => setDeclField('contains_music', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="up-cmu" checked={decl.contains_music === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-cmu" checked={decl.contains_music === false} onChange={() => setDeclField('contains_music', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_music', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="up-cmu" checked={decl.contains_music === false}
|
|
||||||
onChange={() => setDeclField('contains_music', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_music === true && (
|
{decl.contains_music === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="up-mrc" checked={decl.music_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('music_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="up-mrc" checked={decl.music_rights_confirmed} onChange={(e) => setDeclField('music_rights_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="up-mrc" style={{ fontSize: '0.85rem' }}>Musikrechte (GEMA / Lizenz) liegen vor. *</label>
|
||||||
<label htmlFor="up-mrc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Musikrechte (GEMA / Lizenz) liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Lizenz / GEMA-Kontext (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. CC BY 4.0 oder GEMA-Freimeldung Nr. …" value={decl.music_rights_context} onChange={(e) => setDeclField('music_rights_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: '4px' }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: '4px' }}>
|
||||||
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
<legend style={{ fontSize: '0.85rem', fontWeight: 600, marginBottom: '4px' }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
||||||
<div style={{ display: 'flex', gap: '14px' }}>
|
<div style={{ display: 'flex', gap: '14px', marginBottom: '4px' }}>
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-ctpc" checked={decl.contains_third_party_content === true} onChange={() => setDeclField('contains_third_party_content', true)} disabled={busy} /> Ja</label>
|
||||||
<input type="radio" name="up-ctpc" checked={decl.contains_third_party_content === true}
|
<label style={{ fontSize: '0.85rem' }}><input type="radio" name="up-ctpc" checked={decl.contains_third_party_content === false} onChange={() => setDeclField('contains_third_party_content', false)} disabled={busy} /> Nein</label>
|
||||||
onChange={() => setDeclField('contains_third_party_content', true)} disabled={busy} /> Ja
|
|
||||||
</label>
|
|
||||||
<label style={{ fontSize: '0.85rem' }}>
|
|
||||||
<input type="radio" name="up-ctpc" checked={decl.contains_third_party_content === false}
|
|
||||||
onChange={() => setDeclField('contains_third_party_content', false)} disabled={busy} /> Nein
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_third_party_content === true && (
|
{decl.contains_third_party_content === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginTop: '6px' }}>
|
<div style={{ paddingLeft: 2 }}>
|
||||||
<input type="checkbox" id="up-tprc" checked={decl.third_party_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', marginBottom: '4px' }}>
|
||||||
onChange={(e) => setDeclField('third_party_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="up-tprc" checked={decl.third_party_rights_confirmed} onChange={(e) => setDeclField('third_party_rights_confirmed', e.target.checked)} disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
||||||
disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} />
|
<label htmlFor="up-tprc" style={{ fontSize: '0.85rem' }}>Rechte an allen enthaltenen Fremdmaterialien liegen vor. *</label>
|
||||||
<label htmlFor="up-tprc" style={{ fontSize: '0.85rem' }}>
|
</div>
|
||||||
Rechte an allen enthaltenen Fremdmaterialien liegen vor. *
|
<label style={{ display: 'block', fontSize: '0.8rem', color: 'var(--text2)', marginBottom: '2px' }}>Rechtsgrundlage (optional)</label>
|
||||||
</label>
|
<textarea className="form-input" rows={2} placeholder="z.B. Verbandslogo mit Genehmigung vom 2026-03-10" value={decl.third_party_rights_context} onChange={(e) => setDeclField('third_party_rights_context', e.target.value)} disabled={busy} style={{ fontSize: '0.82rem', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,20 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
const INITIAL = {
|
const INITIAL = {
|
||||||
|
copyright_notice: '',
|
||||||
rights_holder_confirmed: false,
|
rights_holder_confirmed: false,
|
||||||
contains_identifiable_persons: null,
|
contains_identifiable_persons: null,
|
||||||
person_consent_confirmed: false,
|
person_consent_confirmed: false,
|
||||||
|
person_consent_context: '',
|
||||||
contains_minors: null,
|
contains_minors: null,
|
||||||
parental_consent_confirmed: false,
|
parental_consent_confirmed: false,
|
||||||
|
parental_consent_context: '',
|
||||||
contains_music: null,
|
contains_music: null,
|
||||||
music_rights_confirmed: false,
|
music_rights_confirmed: false,
|
||||||
|
music_rights_context: '',
|
||||||
contains_third_party_content: null,
|
contains_third_party_content: null,
|
||||||
third_party_rights_confirmed: false,
|
third_party_rights_confirmed: false,
|
||||||
}
|
third_party_rights_context: '',
|
||||||
|
|
||||||
function resetDecl() {
|
|
||||||
return { ...INITIAL }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -37,7 +38,7 @@ export default function RightsDeclarationDialog({
|
||||||
isPromotion = false,
|
isPromotion = false,
|
||||||
mode = 'upload',
|
mode = 'upload',
|
||||||
}) {
|
}) {
|
||||||
const [decl, setDecl] = useState(resetDecl)
|
const [decl, setDecl] = useState({ ...INITIAL })
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
if (!open) return null
|
if (!open) return null
|
||||||
|
|
@ -60,7 +61,7 @@ export default function RightsDeclarationDialog({
|
||||||
if (decl.contains_music && !decl.music_rights_confirmed)
|
if (decl.contains_music && !decl.music_rights_confirmed)
|
||||||
return 'Bitte bestätigen, dass die erforderlichen Musikrechte vorliegen.'
|
return 'Bitte bestätigen, dass die erforderlichen Musikrechte vorliegen.'
|
||||||
if (decl.contains_third_party_content === null)
|
if (decl.contains_third_party_content === null)
|
||||||
return 'Bitte angeben, ob fremde geschützte Inhalte (Logos, Grafiken etc.) enthalten sind.'
|
return 'Bitte angeben, ob fremde geschützte Inhalte enthalten sind.'
|
||||||
if (decl.contains_third_party_content && !decl.third_party_rights_confirmed)
|
if (decl.contains_third_party_content && !decl.third_party_rights_confirmed)
|
||||||
return 'Bitte bestätigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen.'
|
return 'Bitte bestätigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen.'
|
||||||
return ''
|
return ''
|
||||||
|
|
@ -71,11 +72,11 @@ export default function RightsDeclarationDialog({
|
||||||
if (err) { setError(err); return }
|
if (err) { setError(err); return }
|
||||||
setError('')
|
setError('')
|
||||||
onConfirm({ ...decl })
|
onConfirm({ ...decl })
|
||||||
setDecl(resetDecl())
|
setDecl({ ...INITIAL })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
setDecl(resetDecl())
|
setDecl({ ...INITIAL })
|
||||||
setError('')
|
setError('')
|
||||||
onCancel()
|
onCancel()
|
||||||
}
|
}
|
||||||
|
|
@ -108,13 +109,30 @@ export default function RightsDeclarationDialog({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ overflowY: 'auto', flex: 1, padding: '14px 16px' }}>
|
<div style={{ overflowY: 'auto', flex: 1, padding: '14px 16px' }}>
|
||||||
<p style={{ fontSize: '0.82rem', color: 'var(--text3)', marginBottom: 16 }}>
|
<p style={{ fontSize: '0.82rem', color: 'var(--text3)', marginBottom: 14 }}>
|
||||||
VORLÄUFIG – Texte noch nicht juristisch geprüft (p06-v1-conservative).
|
VORLÄUFIG – Texte noch nicht juristisch geprüft (p06-v1-conservative).
|
||||||
{isPromotion && (
|
{isPromotion && (
|
||||||
<> Die bestehende Erklärung gilt nicht für die Sichtbarkeit „{visLabel}". Bitte erneut bestätigen.</>
|
<> Die bestehende Erklärung gilt nicht für die Sichtbarkeit „{visLabel}". Bitte erneut bestätigen.</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* Copyright */}
|
||||||
|
<div style={{ marginBottom: 14 }}>
|
||||||
|
<label htmlFor="rdlg-cr" style={{ display: 'block', fontSize: '0.85rem', fontWeight: 600, marginBottom: 4 }}>
|
||||||
|
Copyright-Angabe (optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="rdlg-cr"
|
||||||
|
type="text"
|
||||||
|
className="form-input"
|
||||||
|
placeholder="z.B. © 2026 Lars Stommer, CC BY-NC"
|
||||||
|
value={decl.copyright_notice}
|
||||||
|
onChange={(e) => setField('copyright_notice', e.target.value)}
|
||||||
|
style={{ fontSize: '0.9rem' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* T1: Rechteinhaber */}
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 14 }}>
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 14 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -129,9 +147,10 @@ export default function RightsDeclarationDialog({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* T2 / T3: Personen */}
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
||||||
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Erkennbare Personen abgebildet? *</legend>
|
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Erkennbare Personen abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: 16 }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 6 }}>
|
||||||
<label style={{ fontSize: '0.9rem' }}>
|
<label style={{ fontSize: '0.9rem' }}>
|
||||||
<input type="radio" name="rdlg-cip" checked={decl.contains_identifiable_persons === true}
|
<input type="radio" name="rdlg-cip" checked={decl.contains_identifiable_persons === true}
|
||||||
onChange={() => setField('contains_identifiable_persons', true)} /> Ja
|
onChange={() => setField('contains_identifiable_persons', true)} /> Ja
|
||||||
|
|
@ -142,20 +161,34 @@ export default function RightsDeclarationDialog({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_identifiable_persons === true && (
|
{decl.contains_identifiable_persons === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginTop: 8 }}>
|
<div style={{ paddingLeft: 4 }}>
|
||||||
<input type="checkbox" id="rdlg-pcc" checked={decl.person_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 6 }}>
|
||||||
onChange={(e) => setField('person_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="rdlg-pcc" checked={decl.person_consent_confirmed}
|
||||||
style={{ marginTop: 3, flexShrink: 0 }} />
|
onChange={(e) => setField('person_consent_confirmed', e.target.checked)}
|
||||||
<label htmlFor="rdlg-pcc" style={{ fontSize: '0.9rem' }}>
|
style={{ marginTop: 3, flexShrink: 0 }} />
|
||||||
Einwilligungen aller erkennbaren Personen liegen vor. *
|
<label htmlFor="rdlg-pcc" style={{ fontSize: '0.9rem' }}>
|
||||||
|
Einwilligungen aller erkennbaren Personen liegen vor. *
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label style={{ display: 'block', fontSize: '0.82rem', color: 'var(--text2)', marginBottom: 3 }}>
|
||||||
|
Einwilligungskontext (optional)
|
||||||
</label>
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="z.B. Schriftliche Einwilligung vom 2026-05-01, im Archiv abgelegt"
|
||||||
|
value={decl.person_consent_context}
|
||||||
|
onChange={(e) => setField('person_consent_context', e.target.value)}
|
||||||
|
style={{ fontSize: '0.85rem', resize: 'vertical' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{/* T4 / T5: Minderjährige */}
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
||||||
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Minderjährige abgebildet? *</legend>
|
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Minderjährige abgebildet? *</legend>
|
||||||
<div style={{ display: 'flex', gap: 16 }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 6 }}>
|
||||||
<label style={{ fontSize: '0.9rem' }}>
|
<label style={{ fontSize: '0.9rem' }}>
|
||||||
<input type="radio" name="rdlg-cm" checked={decl.contains_minors === true}
|
<input type="radio" name="rdlg-cm" checked={decl.contains_minors === true}
|
||||||
onChange={() => setField('contains_minors', true)} /> Ja
|
onChange={() => setField('contains_minors', true)} /> Ja
|
||||||
|
|
@ -166,20 +199,34 @@ export default function RightsDeclarationDialog({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_minors === true && (
|
{decl.contains_minors === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginTop: 8 }}>
|
<div style={{ paddingLeft: 4 }}>
|
||||||
<input type="checkbox" id="rdlg-pcc2" checked={decl.parental_consent_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 6 }}>
|
||||||
onChange={(e) => setField('parental_consent_confirmed', e.target.checked)}
|
<input type="checkbox" id="rdlg-pcc2" checked={decl.parental_consent_confirmed}
|
||||||
style={{ marginTop: 3, flexShrink: 0 }} />
|
onChange={(e) => setField('parental_consent_confirmed', e.target.checked)}
|
||||||
<label htmlFor="rdlg-pcc2" style={{ fontSize: '0.9rem' }}>
|
style={{ marginTop: 3, flexShrink: 0 }} />
|
||||||
Einwilligungen der Sorgeberechtigten liegen vor. *
|
<label htmlFor="rdlg-pcc2" style={{ fontSize: '0.9rem' }}>
|
||||||
|
Einwilligungen der Sorgeberechtigten liegen vor. *
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label style={{ display: 'block', fontSize: '0.82rem', color: 'var(--text2)', marginBottom: 3 }}>
|
||||||
|
Einwilligungskontext (optional)
|
||||||
</label>
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="z.B. Elterliche Einwilligung per E-Mail vom 2026-04-15 erhalten"
|
||||||
|
value={decl.parental_consent_context}
|
||||||
|
onChange={(e) => setField('parental_consent_context', e.target.value)}
|
||||||
|
style={{ fontSize: '0.85rem', resize: 'vertical' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{/* T6 / T7: Musik */}
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: 14 }}>
|
||||||
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Musik enthalten? *</legend>
|
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Musik enthalten? *</legend>
|
||||||
<div style={{ display: 'flex', gap: 16 }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 6 }}>
|
||||||
<label style={{ fontSize: '0.9rem' }}>
|
<label style={{ fontSize: '0.9rem' }}>
|
||||||
<input type="radio" name="rdlg-cmu" checked={decl.contains_music === true}
|
<input type="radio" name="rdlg-cmu" checked={decl.contains_music === true}
|
||||||
onChange={() => setField('contains_music', true)} /> Ja
|
onChange={() => setField('contains_music', true)} /> Ja
|
||||||
|
|
@ -190,20 +237,34 @@ export default function RightsDeclarationDialog({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_music === true && (
|
{decl.contains_music === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginTop: 8 }}>
|
<div style={{ paddingLeft: 4 }}>
|
||||||
<input type="checkbox" id="rdlg-mrc" checked={decl.music_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 6 }}>
|
||||||
onChange={(e) => setField('music_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="rdlg-mrc" checked={decl.music_rights_confirmed}
|
||||||
style={{ marginTop: 3, flexShrink: 0 }} />
|
onChange={(e) => setField('music_rights_confirmed', e.target.checked)}
|
||||||
<label htmlFor="rdlg-mrc" style={{ fontSize: '0.9rem' }}>
|
style={{ marginTop: 3, flexShrink: 0 }} />
|
||||||
Musikrechte (GEMA / Lizenz) liegen vor. *
|
<label htmlFor="rdlg-mrc" style={{ fontSize: '0.9rem' }}>
|
||||||
|
Musikrechte (GEMA / Lizenz) liegen vor. *
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label style={{ display: 'block', fontSize: '0.82rem', color: 'var(--text2)', marginBottom: 3 }}>
|
||||||
|
Lizenz / GEMA-Kontext (optional)
|
||||||
</label>
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="z.B. Creative Commons BY 4.0, Lizenz-Nr. oder GEMA-Freimeldung"
|
||||||
|
value={decl.music_rights_context}
|
||||||
|
onChange={(e) => setField('music_rights_context', e.target.value)}
|
||||||
|
style={{ fontSize: '0.85rem', resize: 'vertical' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{/* T8 / T9: Fremdinhalte */}
|
||||||
<fieldset style={{ border: 'none', padding: 0, marginBottom: 4 }}>
|
<fieldset style={{ border: 'none', padding: 0, marginBottom: 4 }}>
|
||||||
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
<legend style={{ fontSize: '0.9rem', fontWeight: 600, marginBottom: 6 }}>Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? *</legend>
|
||||||
<div style={{ display: 'flex', gap: 16 }}>
|
<div style={{ display: 'flex', gap: 16, marginBottom: 6 }}>
|
||||||
<label style={{ fontSize: '0.9rem' }}>
|
<label style={{ fontSize: '0.9rem' }}>
|
||||||
<input type="radio" name="rdlg-ctpc" checked={decl.contains_third_party_content === true}
|
<input type="radio" name="rdlg-ctpc" checked={decl.contains_third_party_content === true}
|
||||||
onChange={() => setField('contains_third_party_content', true)} /> Ja
|
onChange={() => setField('contains_third_party_content', true)} /> Ja
|
||||||
|
|
@ -214,13 +275,26 @@ export default function RightsDeclarationDialog({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{decl.contains_third_party_content === true && (
|
{decl.contains_third_party_content === true && (
|
||||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginTop: 8 }}>
|
<div style={{ paddingLeft: 4 }}>
|
||||||
<input type="checkbox" id="rdlg-tprc" checked={decl.third_party_rights_confirmed}
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 6 }}>
|
||||||
onChange={(e) => setField('third_party_rights_confirmed', e.target.checked)}
|
<input type="checkbox" id="rdlg-tprc" checked={decl.third_party_rights_confirmed}
|
||||||
style={{ marginTop: 3, flexShrink: 0 }} />
|
onChange={(e) => setField('third_party_rights_confirmed', e.target.checked)}
|
||||||
<label htmlFor="rdlg-tprc" style={{ fontSize: '0.9rem' }}>
|
style={{ marginTop: 3, flexShrink: 0 }} />
|
||||||
Rechte an allen enthaltenen Fremdmaterialien liegen vor. *
|
<label htmlFor="rdlg-tprc" style={{ fontSize: '0.9rem' }}>
|
||||||
|
Rechte an allen enthaltenen Fremdmaterialien liegen vor. *
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label style={{ display: 'block', fontSize: '0.82rem', color: 'var(--text2)', marginBottom: 3 }}>
|
||||||
|
Rechtsgrundlage (optional)
|
||||||
</label>
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="form-input"
|
||||||
|
rows={2}
|
||||||
|
placeholder="z.B. Verbandslogo mit Genehmigung vom 2026-03-10, Lizenz-Nr. …"
|
||||||
|
value={decl.third_party_rights_context}
|
||||||
|
onChange={(e) => setField('third_party_rights_context', e.target.value)}
|
||||||
|
style={{ fontSize: '0.85rem', resize: 'vertical' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
||||||
|
|
@ -615,20 +615,26 @@ export async function bulkUploadMediaAssets(files, options = {}) {
|
||||||
if (options.club_id != null && options.club_id !== '') {
|
if (options.club_id != null && options.club_id !== '') {
|
||||||
formData.append('club_id', String(options.club_id))
|
formData.append('club_id', String(options.club_id))
|
||||||
}
|
}
|
||||||
// P-06: Rechte-Erklaerung
|
// Copyright + P-06: Rechte-Erklaerung + Kontextfelder
|
||||||
|
if (options.copyright_notice != null && String(options.copyright_notice).trim())
|
||||||
|
formData.append('copyright_notice', String(options.copyright_notice).trim())
|
||||||
const p06Fields = [
|
const p06Fields = [
|
||||||
'rights_holder_confirmed',
|
'rights_holder_confirmed',
|
||||||
'contains_identifiable_persons',
|
'contains_identifiable_persons',
|
||||||
'person_consent_confirmed',
|
'person_consent_confirmed',
|
||||||
|
'person_consent_context',
|
||||||
'contains_minors',
|
'contains_minors',
|
||||||
'parental_consent_confirmed',
|
'parental_consent_confirmed',
|
||||||
|
'parental_consent_context',
|
||||||
'contains_music',
|
'contains_music',
|
||||||
'music_rights_confirmed',
|
'music_rights_confirmed',
|
||||||
|
'music_rights_context',
|
||||||
'contains_third_party_content',
|
'contains_third_party_content',
|
||||||
'third_party_rights_confirmed',
|
'third_party_rights_confirmed',
|
||||||
|
'third_party_rights_context',
|
||||||
]
|
]
|
||||||
for (const f of p06Fields) {
|
for (const f of p06Fields) {
|
||||||
if (options[f] != null) formData.append(f, String(options[f]))
|
if (options[f] != null && options[f] !== '') formData.append(f, String(options[f]))
|
||||||
}
|
}
|
||||||
const arr = Array.isArray(files) ? files : [files]
|
const arr = Array.isArray(files) ? files : [files]
|
||||||
for (const f of arr) {
|
for (const f of arr) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Shinkan Jinkendo Frontend Version
|
// Shinkan Jinkendo Frontend Version
|
||||||
|
|
||||||
export const APP_VERSION = "0.8.76"
|
export const APP_VERSION = "0.8.77"
|
||||||
export const BUILD_DATE = "2026-05-11"
|
export const BUILD_DATE = "2026-05-11"
|
||||||
|
|
||||||
export const PAGE_VERSIONS = {
|
export const PAGE_VERSIONS = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user