import { useEffect, useMemo, useState } from 'react' import { filterAssets, loadAdminAssets, loadAssetAccessEvents, loadAssetClaims, updateAssetStatus, type AssetAccessEvent, type AssetClaim, type AssetRecord, type AssetStatus, } from '../admin-assets' type LoadState = 'loading' | 'ready' | 'error' const STATUS_FILTERS: Array<{ value: 'all' | AssetStatus; label: string }> = [ { value: 'all', label: 'All' }, { value: 'active', label: 'Active' }, { value: 'archived', label: 'Archived' }, { value: 'quarantined', label: 'Quarantined' }, { value: 'disabled', label: 'Disabled' }, { value: 'deleted', label: 'Deleted' }, ] function statusClassName(status: AssetStatus): string { if (status === 'active') return 'border-emerald-200 bg-emerald-50 text-emerald-700' if (status === 'quarantined') return 'border-amber-200 bg-amber-50 text-amber-700' if (status === 'disabled' || status === 'deleted') return 'border-rose-200 bg-rose-50 text-rose-700' if (status === 'archived') return 'border-zinc-200 bg-zinc-100 text-zinc-700' return 'border-zinc-200 bg-zinc-100 text-zinc-600' } function formatDate(value: number | null): string { if (value === null) return 'Unknown' return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short', }).format(value) } function claimStatusClassName(status: string): string { const normalized = status.toLowerCase() if (normalized.includes('resolved')) return 'text-emerald-700' if (normalized.includes('rejected')) return 'text-zinc-700' if (normalized.includes('review') || normalized.includes('received')) return 'text-amber-700' if (normalized.includes('disabled')) return 'text-rose-700' return 'text-zinc-600' } export function AdminAssetsPage() { const [state, setState] = useState('loading') const [error, setError] = useState('') const [assets, setAssets] = useState([]) const [statusFilter, setStatusFilter] = useState<'all' | AssetStatus>('all') const [query, setQuery] = useState('') const [selectedAssetID, setSelectedAssetID] = useState(null) const [accessEvents, setAccessEvents] = useState([]) const [claims, setClaims] = useState([]) const [claimsPlaceholder, setClaimsPlaceholder] = useState('') const [loadingDetail, setLoadingDetail] = useState(false) const loadAssets = async () => { try { setState('loading') setError('') const rows = await loadAdminAssets() setAssets(rows) setSelectedAssetID(current => current ?? rows[0]?.id ?? null) setState('ready') } catch (err) { setAssets([]) setSelectedAssetID(null) setState('error') setError(err instanceof Error ? err.message : 'Failed to load asset library') } } const selectedAsset = useMemo(() => { return assets.find(asset => asset.id === selectedAssetID) ?? null }, [assets, selectedAssetID]) const filteredAssets = useMemo(() => { return filterAssets(assets, statusFilter, query) }, [assets, query, statusFilter]) useEffect(() => { void loadAssets() }, []) useEffect(() => { const assetID = selectedAsset?.id if (!assetID) { setAccessEvents([]) setClaims([]) setClaimsPlaceholder('') return } const loadDetails = async () => { setLoadingDetail(true) try { const [events, claimRows] = await Promise.all([ loadAssetAccessEvents(assetID), loadAssetClaims(assetID), ]) setAccessEvents(events) setClaims(claimRows) setClaimsPlaceholder(claimRows.length === 0 ? 'No copyright claims are recorded for this asset yet.' : '') } catch { setAccessEvents([]) setClaims([]) setClaimsPlaceholder('Copyright claims are currently unavailable for this asset.') } finally { setLoadingDetail(false) } } void loadDetails() }, [selectedAsset?.id]) const onStatusAction = async (asset: AssetRecord, action: 'quarantine' | 'disable' | 'restore') => { try { const updated = await updateAssetStatus(asset.id, action) setAssets(current => current.map(row => row.id === updated.id ? updated : row)) setSelectedAssetID(updated.id) } catch (err) { setError(err instanceof Error ? err.message : 'Failed to update asset status') setState('error') } } if (state === 'loading') { return (

Asset Library

Loading protected assets and copyright queue...

) } if (state === 'error') { return (

Asset Library

{error}

) } return (

Asset Library

Review asset protection posture, copyright metadata, access events, and enforcement actions.

{STATUS_FILTERS.map(filter => ( ))}
{filteredAssets.map(asset => ( setSelectedAssetID(asset.id)} className={`cursor-pointer border-t border-zinc-100 ${selectedAsset?.id === asset.id ? 'bg-zinc-50' : 'hover:bg-zinc-50/70'}`} > ))}
Asset Status Visibility Owner Licence Protection Related Actions

{asset.title}

{asset.id}

{asset.status} {asset.visibility} {asset.owner} {asset.licence} {asset.protectionLevel} {asset.relatedResource}
event.stopPropagation()}> {asset.status !== 'quarantined' ? ( ) : null} {asset.status !== 'disabled' ? ( ) : null} {asset.status !== 'active' ? ( ) : null}
{filteredAssets.length === 0 ? (
No assets matched this filter.
) : null}
) }