import React from 'react' import { Head, usePage } from '@inertiajs/react' import CollectionCard from '../../components/profile/collections/CollectionCard' const DEFAULT_SEARCH_FILTERS = { q: '', type: '', visibility: '', lifecycle_state: '', workflow_state: '', health_state: '', placement_eligibility: '', } const DEFAULT_BULK_FORM = { action: 'archive', campaign_key: '', campaign_label: '', lifecycle_state: 'archived', } function getCsrfToken() { if (typeof document === 'undefined') return '' return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '' } function titleize(value) { return String(value || '') .split('_') .filter(Boolean) .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) .join(' ') } function buildSearchUrl(baseUrl, filters) { const url = new URL(baseUrl, window.location.origin) Object.entries(filters || {}).forEach(([key, value]) => { if (value === '' || value === null || value === undefined) { return } url.searchParams.set(key, String(value)) }) return url.toString() } async function fetchSearchResults(baseUrl, filters) { const response = await fetch(buildSearchUrl(baseUrl, filters), { method: 'GET', credentials: 'same-origin', headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Search failed.') } return payload } async function requestJson(url, { method = 'POST', body } = {}) { const response = await fetch(url, { method, credentials: 'same-origin', headers: { Accept: 'application/json', 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken(), 'X-Requested-With': 'XMLHttpRequest', }, body: body ? JSON.stringify(body) : undefined, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Request failed.') } return payload } function SummaryCard({ label, value, icon, tone = 'sky' }) { const toneClasses = { sky: 'border-sky-300/15 bg-sky-400/10 text-sky-100', amber: 'border-amber-300/15 bg-amber-400/10 text-amber-100', rose: 'border-rose-300/15 bg-rose-400/10 text-rose-100', emerald: 'border-emerald-300/15 bg-emerald-400/10 text-emerald-100', } return (
{label}
{value}
) } function CollectionStrip({ title, eyebrow, collections, emptyLabel, endpoints }) { function resolve(pattern, collectionId) { return pattern ? pattern.replace('__COLLECTION__', String(collectionId)) : null } return (

{eyebrow}

{title}

{collections.length}
{collections.length ? (
{collections.map((collection) => (
{resolve(endpoints?.managePattern, collection.id) ? ( Manage ) : null} {resolve(endpoints?.analyticsPattern, collection.id) ? ( Analytics ) : null} {resolve(endpoints?.historyPattern, collection.id) ? ( History ) : null}
))}
) : (
{emptyLabel}
)}
) } function WarningList({ warnings, endpoints }) { function resolve(pattern, collectionId) { return pattern ? pattern.replace('__COLLECTION__', String(collectionId)) : null } if (!warnings.length) { return null } return (

Health

Warnings and blockers

{warnings.length}
{warnings.map((warning) => (

{warning.title}

State: {warning.health?.health_state || 'unknown'}

{warning.health?.health_score ?? 'n/a'}
{Array.isArray(warning.health?.flags) && warning.health.flags.length ? (
{warning.health.flags.map((flag) => ( {flag} ))}
) : null}
{resolve(endpoints?.managePattern, warning.collection_id) ? Manage : null} {resolve(endpoints?.healthPattern, warning.collection_id) ? Health JSON : null}
))}
) } function SearchField({ label, value, onChange, children }) { return ( ) } function BulkActionsPanel({ selectedCount, totalCount, form, onFormChange, onApply, onClear, onToggleAll, busy, error, notice, }) { if (!selectedCount && !notice && !error) { return null } return (

Bulk actions

Apply safe actions to selected collections

{selectedCount ? ( ) : null}
{selectedCount} selected
{(notice || error) ? (
{error || notice}
) : null}
{form.action === 'assign_campaign' ? ( <> onFormChange('campaign_key', event.target.value)} placeholder="spring-launch" className="w-full rounded-2xl border border-white/10 bg-[#09111d] px-4 py-3 text-sm text-white outline-none transition placeholder:text-slate-500 focus:border-sky-300/40" /> onFormChange('campaign_label', event.target.value)} placeholder="Spring Launch" className="w-full rounded-2xl border border-white/10 bg-[#09111d] px-4 py-3 text-sm text-white outline-none transition placeholder:text-slate-500 focus:border-sky-300/40" /> ) : null} {form.action === 'update_lifecycle' ? ( ) : null}
) } function SearchResults({ state, endpoints, selectedIds, onToggleSelected }) { function resolve(pattern, collectionId) { return pattern ? pattern.replace('__COLLECTION__', String(collectionId)) : null } if (!state.hasSearched) { return (
Run a search to slice your collection library by workflow, health, visibility, and placement readiness.
) } if (state.error) { return
{state.error}
} if (!state.collections.length) { return (
No collections matched the current filters.
) } return (
{Object.entries(state.filters || {}).filter(([, value]) => value !== '' && value !== null && value !== undefined).map(([key, value]) => ( {titleize(key)}: {value === '1' ? 'Eligible' : value === '0' ? 'Blocked' : titleize(value)} ))}
{state.meta ? {state.meta.total || state.collections.length} results : null}
{state.collections.map((collection) => (
{collection.workflow_state ? Workflow: {titleize(collection.workflow_state)} : null} {collection.health_state ? Health: {titleize(collection.health_state)} : null} {collection.placement_eligibility === true ? Placement Eligible : null} {collection.placement_eligibility === false ? Placement Blocked : null}
{resolve(endpoints?.managePattern, collection.id) ? ( Manage ) : null} {resolve(endpoints?.analyticsPattern, collection.id) ? ( Analytics ) : null} {resolve(endpoints?.historyPattern, collection.id) ? ( History ) : null} {resolve(endpoints?.healthPattern, collection.id) ? ( Health ) : null}
))}
) } export default function CollectionDashboard() { const { props } = usePage() const [summary, setSummary] = React.useState(props.summary || {}) const topPerforming = Array.isArray(props.topPerforming) ? props.topPerforming : [] const needsAttention = Array.isArray(props.needsAttention) ? props.needsAttention : [] const expiringCampaigns = Array.isArray(props.expiringCampaigns) ? props.expiringCampaigns : [] const healthWarnings = Array.isArray(props.healthWarnings) ? props.healthWarnings : [] const filterOptions = props.filterOptions || {} const endpoints = props.endpoints || {} const seo = props.seo || {} const [searchFilters, setSearchFilters] = React.useState(DEFAULT_SEARCH_FILTERS) const [searchState, setSearchState] = React.useState({ busy: false, error: '', collections: [], meta: null, filters: null, hasSearched: false, }) const [selectedIds, setSelectedIds] = React.useState([]) const [bulkForm, setBulkForm] = React.useState(DEFAULT_BULK_FORM) const [bulkState, setBulkState] = React.useState({ busy: false, error: '', notice: '' }) React.useEffect(() => { setSummary(props.summary || {}) }, [props.summary]) React.useEffect(() => { const visibleIds = new Set((searchState.collections || []).map((collection) => Number(collection.id))) setSelectedIds((current) => current.filter((id) => visibleIds.has(Number(id)))) }, [searchState.collections]) function updateFilter(key, value) { setSearchFilters((current) => ({ ...current, [key]: value, })) } async function handleSearch(event) { event.preventDefault() if (!endpoints.search) { setSearchState({ busy: false, error: 'Search endpoint is unavailable.', collections: [], meta: null, filters: null, hasSearched: true }) return } setSearchState((current) => ({ ...current, busy: true, error: '', hasSearched: true })) try { const payload = await fetchSearchResults(endpoints.search, searchFilters) setSearchState({ busy: false, error: '', collections: Array.isArray(payload.collections) ? payload.collections : [], meta: payload.meta || null, filters: payload.filters || { ...searchFilters }, hasSearched: true, }) } catch (error) { setSearchState({ busy: false, error: error.message || 'Search failed.', collections: [], meta: null, filters: null, hasSearched: true }) } } function resetSearch() { setSearchFilters(DEFAULT_SEARCH_FILTERS) setSearchState({ busy: false, error: '', collections: [], meta: null, filters: null, hasSearched: false }) setSelectedIds([]) } function updateBulkForm(key, value) { setBulkForm((current) => ({ ...current, [key]: value, })) } function toggleSelected(collectionId) { setSelectedIds((current) => ( current.includes(collectionId) ? current.filter((id) => id !== collectionId) : [...current, collectionId] )) } function toggleSelectAllVisible() { const visibleIds = (searchState.collections || []).map((collection) => Number(collection.id)) if (!visibleIds.length) { return } setSelectedIds((current) => ( current.length === visibleIds.length && visibleIds.every((id) => current.includes(id)) ? [] : visibleIds )) } async function applyBulkAction() { if (!selectedIds.length) { return } if (!endpoints.bulkActions) { setBulkState({ busy: false, error: 'Bulk action endpoint is unavailable.', notice: '' }) return } if (bulkForm.action === 'archive' && !window.confirm(`Archive ${selectedIds.length} selected collection${selectedIds.length === 1 ? '' : 's'}?`)) { return } const payload = { action: bulkForm.action, collection_ids: selectedIds, } if (bulkForm.action === 'assign_campaign') { payload.campaign_key = bulkForm.campaign_key payload.campaign_label = bulkForm.campaign_label } if (bulkForm.action === 'update_lifecycle') { payload.lifecycle_state = bulkForm.lifecycle_state } setBulkState({ busy: true, error: '', notice: '' }) try { const response = await requestJson(endpoints.bulkActions, { method: 'POST', body: payload }) const updates = new Map((Array.isArray(response.collections) ? response.collections : []).map((collection) => [Number(collection.id), collection])) setSearchState((current) => ({ ...current, collections: (current.collections || []).map((collection) => updates.get(Number(collection.id)) || collection), })) setSummary(response.summary || summary) setSelectedIds([]) setBulkState({ busy: false, error: '', notice: response.message || 'Bulk action applied.' }) } catch (error) { setBulkState({ busy: false, error: error.message || 'Bulk action failed.', notice: '' }) } } return ( <> {seo.title || 'Collections Dashboard — Skinbase Nova'} {seo.canonical ? : null}