import React, { useEffect, useState } from 'react'
import { router } from '@inertiajs/react'
import { studioSurface, trackStudioEvent } from '../../utils/studioEvents'
function formatDate(value) {
if (!value) return 'Unscheduled'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return 'Unscheduled'
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
}
function metricValue(item, key) {
return Number(item?.metrics?.[key] ?? 0).toLocaleString()
}
function readinessClasses(readiness) {
if (!readiness) return 'border-white/15 bg-white/5 text-slate-300'
if (readiness.can_publish && readiness.score >= readiness.max) return 'border-emerald-400/30 bg-emerald-400/10 text-emerald-100'
if (readiness.can_publish) return 'border-sky-400/30 bg-sky-400/10 text-sky-100'
return 'border-amber-400/30 bg-amber-400/10 text-amber-100'
}
function statusClasses(status) {
switch (status) {
case 'published':
return 'border-emerald-400/30 bg-emerald-400/10 text-emerald-200'
case 'draft':
case 'pending_review':
return 'border-amber-400/30 bg-amber-400/10 text-amber-100'
case 'scheduled':
case 'processing':
return 'border-sky-400/30 bg-sky-400/10 text-sky-100'
case 'archived':
case 'hidden':
case 'rejected':
return 'border-white/15 bg-white/5 text-slate-300'
default:
return 'border-white/15 bg-white/5 text-slate-200'
}
}
function ActionLink({ href, icon, label, onClick }) {
if (!href) return null
return (
{label}
)
}
function RequestActionButton({ action, onExecute, busyKey }) {
if (!action || action.type !== 'request') return null
const isBusy = busyKey === `${action.key}:${action.url}`
return (
)
}
function PreviewLink({ item }) {
if (!item?.preview_url) return null
return
}
function GridCard({ item, onExecuteAction, busyKey }) {
const handleEditClick = () => {
trackStudioEvent('studio_item_edited', {
surface: studioSurface(),
module: item.module,
item_module: item.module,
item_id: item.numeric_id,
meta: {
action: 'edit',
},
})
}
return (
{item.image_url ? (

) : (
)}
{item.module_label}
{item.title}
{item.subtitle || item.visibility || 'Untitled metadata'}
{String(item.status || 'unknown').replace('_', ' ')}
{item.workflow?.readiness && (
{item.workflow.readiness.label}
{item.workflow.is_stale_draft && (
Stale draft
)}
{item.workflow.readiness.score}/{item.workflow.readiness.max} ready
)}
{item.description || 'No description yet.'}
{Array.isArray(item.workflow?.readiness?.missing) && item.workflow.readiness.missing.length > 0 && (
{item.workflow.readiness.missing.slice(0, 2).join(' • ')}
)}
Views
{metricValue(item, 'views')}
Reactions
{metricValue(item, 'appreciation')}
Comments
{metricValue(item, 'comments')}
Updated {formatDate(item.updated_at)}
{item.published_at && Published {formatDate(item.published_at)}}
{(item.actions || []).map((action) => (
))}
{Array.isArray(item.workflow?.cross_module_actions) && item.workflow.cross_module_actions.length > 0 && (
{item.workflow.cross_module_actions.slice(0, 2).map((action) => (
))}
)}
)
}
function ListRow({ item, onExecuteAction, busyKey }) {
const handleEditClick = () => {
trackStudioEvent('studio_item_edited', {
surface: studioSurface(),
module: item.module,
item_module: item.module,
item_id: item.numeric_id,
meta: {
action: 'edit',
},
})
}
return (
{item.image_url ? (

) : (
)}
{item.module_label}
{String(item.status || 'unknown').replace('_', ' ')}
{item.title}
{item.subtitle || item.visibility || 'Untitled metadata'}
{item.description || 'No description yet.'}
{item.workflow?.readiness && (
{item.workflow.readiness.label}
)}
{item.workflow?.is_stale_draft && (
Stale draft
)}
{metricValue(item, 'views')} views
{metricValue(item, 'appreciation')} reactions
{metricValue(item, 'comments')} comments
Updated {formatDate(item.updated_at)}
{Array.isArray(item.workflow?.readiness?.missing) && item.workflow.readiness.missing.length > 0 && (
{item.workflow.readiness.missing.slice(0, 2).join(' • ')}
)}
{(item.actions || []).map((action) => (
))}
{(item.workflow?.cross_module_actions || []).slice(0, 2).map((action) => (
))}
)
}
function AdvancedFilterControl({ filter, onChange }) {
if (filter.type === 'select') {
return (
)
}
return (
)
}
export default function StudioContentBrowser({
listing,
quickCreate = [],
hideModuleFilter = false,
hideBucketFilter = false,
emptyTitle = 'Nothing here yet',
emptyBody = 'Try adjusting filters or create something new.',
}) {
const [viewMode, setViewMode] = useState('grid')
const [busyKey, setBusyKey] = useState(null)
const filters = listing?.filters || {}
const items = listing?.items || []
const meta = listing?.meta || {}
const advancedFilters = listing?.advanced_filters || []
useEffect(() => {
const stored = window.localStorage.getItem('studio-content-view')
if (stored === 'grid' || stored === 'list') {
setViewMode(stored)
return
}
if (listing?.default_view === 'grid' || listing?.default_view === 'list') {
setViewMode(listing.default_view)
}
}, [listing?.default_view])
const updateQuery = (patch) => {
const next = {
...filters,
...patch,
}
if (patch.page == null) {
next.page = 1
}
trackStudioEvent('studio_filter_used', {
surface: studioSurface(),
module: filters.module || listing?.module || null,
meta: {
patch,
},
})
router.get(window.location.pathname, next, {
preserveScroll: true,
preserveState: true,
replace: true,
})
}
const updateView = (nextMode) => {
setViewMode(nextMode)
window.localStorage.setItem('studio-content-view', nextMode)
trackStudioEvent('studio_filter_used', {
surface: studioSurface(),
module: filters.module || listing?.module || null,
meta: {
view_mode: nextMode,
},
})
}
const executeAction = async (action) => {
if (!action?.url || action.type !== 'request') {
return
}
if (action.confirm && !window.confirm(action.confirm)) {
return
}
const requestKey = `${action.key}:${action.url}`
setBusyKey(requestKey)
try {
const response = await fetch(action.url, {
method: String(action.method || 'post').toUpperCase(),
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: action.payload ? JSON.stringify(action.payload) : undefined,
})
const payload = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(payload?.message || payload?.error || 'Request failed')
}
if (action.key === 'archive') {
trackStudioEvent('studio_item_archived', {
surface: studioSurface(),
module: filters.module || null,
item_module: action.item_module || null,
item_id: action.item_id || null,
meta: {
action: action.key,
url: action.url,
},
})
}
if (action.key === 'restore') {
trackStudioEvent('studio_item_restored', {
surface: studioSurface(),
module: filters.module || null,
item_module: action.item_module || null,
item_id: action.item_id || null,
meta: {
action: action.key,
url: action.url,
},
})
}
if (action.redirect_pattern && payload?.data?.id) {
window.location.assign(action.redirect_pattern.replace('__ID__', String(payload.data.id)))
return
}
if (payload?.redirect) {
window.location.assign(payload.redirect)
return
}
router.reload({ preserveScroll: true, preserveState: true })
} catch (error) {
window.alert(error?.message || 'Action failed.')
} finally {
setBusyKey(null)
}
}
return (
0 ? 'xl:grid-cols-5' : 'xl:grid-cols-4'}`}>
{!hideModuleFilter && (
)}
{!hideBucketFilter && (
)}
{advancedFilters.map((filter) => (
updateQuery({ [key]: value })} />
))}
{[
{ value: 'grid', icon: 'fa-solid fa-table-cells-large', label: 'Grid view' },
{ value: 'list', icon: 'fa-solid fa-list', label: 'List view' },
].map((option) => (
))}
{quickCreate.map((action) => (
trackStudioEvent('studio_quick_create_used', {
surface: studioSurface(),
module: action.key,
meta: {
href: action.url,
label: action.label,
},
})}
className="inline-flex items-center gap-2 rounded-full border border-sky-300/20 bg-sky-300/10 px-4 py-2 text-xs font-semibold text-sky-100 transition hover:border-sky-300/35 hover:bg-sky-300/15"
>
New {action.label}
))}
Showing {items.length} of {Number(meta.total || 0).toLocaleString()} items
Page {meta.current_page || 1} of {meta.last_page || 1}
{items.length > 0 ? (
viewMode === 'grid' ? (
{items.map((item) => )}
) : (
{items.map((item) => )}
)
) : (
)}
Creator Studio
)
}