import React, { useState, useEffect, useCallback } from 'react'
import { Link } from 'react-router-dom'
import api from '../utils/api'
import { notifyOrgInboxChanged, useOrgInbox } from '../context/OrgInboxContext'
const CLUB_ROLE_OPTIONS = [
{ code: 'club_admin', label: 'Vereinsadmin' },
{ code: 'trainer', label: 'Trainer' },
{ code: 'division_lead', label: 'Spartenleitung' },
{ code: 'content_editor', label: 'Inhalte bearbeiten' },
]
const REASON_LABELS = {
copyright: 'Urheberrecht',
image_rights: 'Bildrechte',
privacy: 'Datenschutz / Persönlichkeitsrecht',
minors: 'Minderjährige',
illegal_content: 'Rechtswidriger Inhalt',
youth_protection: 'Jugendschutz',
offensive_content: 'Beleidigender Inhalt',
other: 'Sonstiges',
}
const STATUS_LABELS = {
submitted: 'Eingegangen',
under_review: 'In Bearbeitung',
resolved_no_action: 'Abgeschlossen (kein Handlungsbedarf)',
resolved_legal_hold: 'Abgeschlossen (Legal Hold)',
rejected_invalid: 'Abgewiesen (ungültig)',
}
const STATUS_COLORS = {
submitted: 'var(--accent)',
under_review: '#e8960a',
resolved_no_action: 'var(--text3)',
resolved_legal_hold: 'var(--danger)',
rejected_invalid: 'var(--text3)',
}
function formatWhen(iso) {
if (!iso) return ''
const s = String(iso)
const d = s.includes('T') ? s.split('T')[0] : s.slice(0, 10)
const t = s.includes('T') ? s.split('T')[1] : ''
const time = t ? t.slice(0, 5) : ''
return time ? `${d} · ${time}` : d
}
function PriorityBadge({ priority }) {
if (priority !== 'high') return null
return (
DRINGEND
)
}
const WORKFLOW_STEPS = [
{ key: 'submitted', label: 'Eingegangen' },
{ key: 'under_review', label: 'In Bearbeitung' },
{ key: 'closed', label: 'Abgeschlossen' },
]
function WorkflowBar({ status }) {
const closed = ['resolved_no_action', 'resolved_legal_hold', 'rejected_invalid'].includes(status)
const step = closed ? 2 : status === 'under_review' ? 1 : 0
return (
{WORKFLOW_STEPS.map((s, i) => {
const active = i === step
const done = i < step
return (
{done ? '✓' : i + 1}
{s.label}
{i < WORKFLOW_STEPS.length - 1 && (
)}
)
})}
)
}
function ReportDetailModal({ report, onClose, onRefresh, isSuperadmin, isClubAdmin }) {
const [resolutionNote, setResolutionNote] = useState(report.resolution_note || '')
const [legalHoldNote, setLegalHoldNote] = useState('')
const [saving, setSaving] = useState(false)
const [error, setError] = useState(null)
const [showLegalHoldForm, setShowLegalHoldForm] = useState(false)
const isClosed = ['resolved_no_action', 'resolved_legal_hold', 'rejected_invalid'].includes(report.status)
const isOpen = !isClosed
async function patchAndClose(body) {
setSaving(true)
setError(null)
try {
await api.patchContentReport(report.id, body)
notifyOrgInboxChanged()
onRefresh()
onClose()
} catch (err) {
setError(err.message || String(err))
setSaving(false)
}
}
async function handleStatus(status) {
if ((status === 'resolved_no_action' || status === 'rejected_invalid') && !resolutionNote.trim()) {
setError('Bitte eine Begründung eingeben.')
return
}
await patchAndClose({ status, resolution_note: resolutionNote.trim() || undefined })
}
async function handleSaveNote() {
if (!resolutionNote.trim()) { setError('Notiz ist leer.'); return }
await patchAndClose({ resolution_note: resolutionNote.trim() })
}
async function handleLegalHold() {
if (!legalHoldNote.trim()) { setError('Begründung für Legal Hold erforderlich.'); return }
await patchAndClose({ legalHoldNote })
}
async function handleLegalHoldSubmit() {
if (!legalHoldNote.trim()) { setError('Begründung für Legal Hold erforderlich.'); return }
setSaving(true)
setError(null)
try {
await api.setLegalHoldFromReport(report.id, { reason_note: legalHoldNote.trim() })
notifyOrgInboxChanged()
onRefresh()
onClose()
} catch (err) {
setError(err.message || String(err))
setSaving(false)
}
}
const targetLabel = report.target_filename || report.target_exercise_name
? `${report.target_filename || report.target_exercise_name} (#${report.target_id})`
: `#${report.target_id}`
return (
{ if (e.target === e.currentTarget) onClose() }}
>
{/* Header */}
Meldung #{report.id}
{STATUS_LABELS[report.status] || report.status}
{/* Workflow Bar */}
{/* Details */}
Ziel
{report.target_type === 'media_asset' ? 'Medium' : 'Übung'} {targetLabel}
Meldegrund
{REASON_LABELS[report.report_reason] || report.report_reason}
Beschreibung
{report.report_description}
Meldende Person
{report.reporter_name}
{report.reporter_email ? ` · ${report.reporter_email}` : ''}
{report.reporter_profile_id ? ` · Profil #${report.reporter_profile_id}` : ' · anonym'}
Eingegangen
{formatWhen(report.submitted_at || report.created_at)}
{report.reviewed_by_name && (
Geprüft von
{report.reviewed_by_name}{report.reviewed_at ? ` · ${formatWhen(report.reviewed_at)}` : ''}
)}
{report.target_legal_hold_active && (
Legal Hold aktiv auf diesem Medium
)}
{/* Resolution Note (Bearbeitungskommentar) */}
{error && (
{error}
)}
{/* Actions */}
{isOpen && (
{report.status === 'submitted' && (
)}
{(isSuperadmin || (isClubAdmin && report.target_visibility !== 'official')) && !showLegalHoldForm && (
)}
{isSuperadmin && showLegalHoldForm && (
)}
)}
{isClosed && (
)}
)
}
export default function InboxPage() {
const {
canAccessOrgInbox,
canAccessContentReports,
isSuperadmin,
isPlatformAdmin,
isClubAdmin,
refreshOrgInbox,
inboxJoinRequests,
contentReports,
contentReportCount,
contentReportsError,
} = useOrgInbox()
const [loading, setLoading] = useState(true)
const [acceptModal, setAcceptModal] = useState(null)
const [reportModal, setReportModal] = useState(null)
const [showArchive, setShowArchive] = useState(false)
const load = useCallback(async () => {
if (!canAccessOrgInbox && !canAccessContentReports) {
setLoading(false)
return
}
setLoading(true)
try {
await refreshOrgInbox()
} finally {
setLoading(false)
}
}, [canAccessOrgInbox, canAccessContentReports, refreshOrgInbox])
useEffect(() => {
load()
}, [load])
if (!canAccessOrgInbox && !canAccessContentReports) {
return (
Posteingang
Kein Zugriff. Nur Plattform-Admins und Vereinsadmins sehen den Posteingang.
Zur Übersicht
)
}
return (
Posteingang
Beitrittsanträge und Inhaltsmeldungen für deine Zuständigkeitsbereiche.
{loading ? (
) : (
<>
{/* Abschnitt 1: Beitrittsanträge */}
{canAccessOrgInbox && (
Beitrittsanträge
{inboxJoinRequests.length > 0 && (
{inboxJoinRequests.length}
)}
{inboxJoinRequests.length === 0 ? (
Keine offenen Beitrittsanträge.
) : (
{inboxJoinRequests.map((req) => (
{req.club_name || 'Verein'}
{req.club_abbreviation ? (
({req.club_abbreviation})
) : null}
{req.applicant_name || req.applicant_email || 'Bewerber/in'}
{req.applicant_email} · Profil #{req.profile_id} · {formatWhen(req.created_at)}
{req.message ?
{req.message}
: null}
))}
)}
)}
{/* Abschnitt 2: Inhaltsmeldungen */}
{canAccessContentReports && (() => {
const openReports = contentReports.filter((r) => r.status === 'submitted' || r.status === 'under_review')
const archivedReports = contentReports.filter((r) => r.status !== 'submitted' && r.status !== 'under_review')
function ReportCard({ rep }) {
return (
setReportModal(rep)}
>
Meldung #{rep.id}
{rep.target_type === 'media_asset' ? 'Medium' : 'Übung'} #{rep.target_id}
{rep.target_filename || rep.target_exercise_name ? ` – ${rep.target_filename || rep.target_exercise_name}` : ''}
{STATUS_LABELS[rep.status] || rep.status}
{REASON_LABELS[rep.report_reason] || rep.report_reason}
{' · '}
{rep.reporter_name}
{rep.reporter_profile_id ? ` (Profil #${rep.reporter_profile_id})` : ' (anonym)'}
{' · '}
{formatWhen(rep.submitted_at || rep.created_at)}
)
}
return (
Inhaltsmeldungen
{contentReportCount > 0 && (
{contentReportCount} neu
)}
{contentReportsError ? (
Fehler beim Laden: {contentReportsError}
) : (
<>
{openReports.length === 0 ? (
Keine offenen Inhaltsmeldungen.
) : (
{openReports.map((rep) => )}
)}
{archivedReports.length > 0 && (
{showArchive && (
{archivedReports.map((rep) => )}
)}
)}
>
)}
)
})()}
>
)}
{acceptModal && (
Antrag annehmen
{acceptModal.label}
)}
{reportModal && (
setReportModal(null)}
onRefresh={load}
/>
)}
)
}