import React, { useMemo, useState } from 'react' import { Head, usePage } from '@inertiajs/react' import ArtworkViewer from '../../components/viewer/ArtworkViewer' import NovaSelect from '../../components/ui/NovaSelect' function requestJson(url, { method = 'GET', body } = {}) { return fetch(url, { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', 'X-Requested-With': 'XMLHttpRequest', }, body: body ? JSON.stringify(body) : undefined, }).then(async (response) => { const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Request failed') } return payload }) } function Badge({ children, tone = 'slate' }) { const tones = { slate: 'border-white/10 bg-white/[0.05] text-slate-200', amber: 'border-amber-300/20 bg-amber-400/10 text-amber-100', rose: 'border-rose-300/20 bg-rose-400/10 text-rose-100', emerald: 'border-emerald-300/20 bg-emerald-400/10 text-emerald-100', sky: 'border-sky-300/20 bg-sky-400/10 text-sky-100', } return {children} } export default function ArtworkMaturityQueue() { const { props } = usePage() const [items, setItems] = useState(props.initialItems || []) const [stats, setStats] = useState(props.stats || {}) const [status, setStatus] = useState(props.initialFilters?.status || 'suspected') const [aiAction, setAiAction] = useState(props.initialFilters?.ai_action || 'all') const [aiStatus, setAiStatus] = useState(props.initialFilters?.ai_status || 'all') const [busyId, setBusyId] = useState(null) const [noteById, setNoteById] = useState({}) const [error, setError] = useState('') const [previewItem, setPreviewItem] = useState(null) const endpoints = props.endpoints || {} const filterOptions = props.filterOptions || {} const reviewActions = props.reviewActions || [] function queueStatusKey(key) { return key === 'mature' ? 'reviewed' : key } async function load(nextStatus, nextAiAction = aiAction, nextAiStatus = aiStatus) { setStatus(nextStatus) setAiAction(nextAiAction) setAiStatus(nextAiStatus) setError('') try { const query = new URLSearchParams({ status: nextStatus, ai_action: nextAiAction, ai_status: nextAiStatus, }) const payload = await requestJson(`${endpoints.list}?${query.toString()}`) setItems(payload.data || []) setStats(payload.meta?.stats || {}) } catch (loadError) { setError(loadError.message) } } async function review(itemId, action) { setBusyId(itemId) setError('') try { const payload = await requestJson(String(endpoints.reviewPattern || '').replace('__ARTWORK__', String(itemId)), { method: 'POST', body: { action, note: noteById[itemId] || '', }, }) setStats(payload.stats || {}) setItems((current) => current.filter((item) => item.id !== itemId).concat(status === 'reviewed' ? [payload.artwork] : [])) if (status !== 'reviewed') { setItems((current) => current.filter((item) => item.id !== itemId)) } } catch (reviewError) { setError(reviewError.message) } finally { setBusyId(null) } } const statusSummary = useMemo(() => [ { key: 'suspected', label: 'Suspected', value: Number(stats.suspected || 0) }, { key: 'audit', label: 'Audit candidates', value: Number(stats.audit || 0) }, { key: 'reviewed', label: 'Reviewed', value: Number(stats.reviewed || 0) }, { key: 'mature', label: 'Marked mature', value: Number(stats.mature || 0) }, ], [stats]) return (

Moderator surface

Artwork maturity review

Review uploads where the uploader declaration and AI suspicion do not match, plus legacy artworks detected by the non-mutating thumbnail audit. Audit candidates stay read-only until a moderator confirms the final maturity state.

{statusSummary.map((entry) => ( (() => { const queueKey = queueStatusKey(entry.key) return ( ) })() ))}
AI action hint
load(status, value, aiStatus)} className="mt-2" options={(filterOptions.aiAction || []).map((option) => ({ value: option.value, label: option.label }))} searchable={false} />
AI processing status
load(status, aiAction, value)} className="mt-2" options={(filterOptions.aiStatus || []).map((option) => ({ value: option.value, label: option.label }))} searchable={false} />
{error ?
{error}
: null}
{items.length === 0 ? (
{status === 'audit' ? 'No legacy artworks are currently flagged by the thumbnail audit.' : 'No artworks are waiting in this queue.'}
) : items.map((item) => ( (() => { const evidence = item.audit || item.maturity || {} return (
{item.audit ? audit candidate : null} {item.maturity?.status || 'unknown'} {item.maturity?.is_mature_effective ? effective mature : currently safe} {item.maturity?.source ? source: {String(item.maturity.source).replaceAll('_', ' ')} : null} {item.audit?.legacy_unset ? legacy unset : null} {evidence.ai_action_hint ? AI: {String(evidence.ai_action_hint).replaceAll('_', ' ')} : null} {evidence.ai_status ? status: {String(evidence.ai_status).replaceAll('_', ' ')} : null}

{item.title}

{item.publisher} {item.category ? `• ${item.category}` : ''} {item.content_type ? `• ${item.content_type}` : ''}

Open artwork {item.admin_url ? ( Open in cPad ) : null}
{Array.isArray(evidence.ai_labels) && evidence.ai_labels.length > 0 ? evidence.ai_labels.map((label) => {label}) : no AI labels}
AI score
{evidence.ai_score != null ? Number(evidence.ai_score).toFixed(4) : 'n/a'}
AI label
{evidence.ai_label || 'n/a'}
{item.audit ? 'Audit detected' : 'Published'}
{item.audit?.detected_at ? new Date(item.audit.detected_at).toLocaleString() : item.published_at ? new Date(item.published_at).toLocaleString() : 'Draft / unavailable'}
Confidence
{evidence.ai_confidence != null ? Number(evidence.ai_confidence).toFixed(4) : 'n/a'}
Vision model
{evidence.ai_model || 'n/a'}
Current DB state
{String(item.maturity?.source || 'legacy').replaceAll('_', ' ')} • {String(item.maturity?.status || 'clear').replaceAll('_', ' ')}
{evidence.ai_advisory ? (
AI advisory
{evidence.ai_advisory}
) : null}