import React, { useEffect, useState } from 'react' import { router, usePage } from '@inertiajs/react' import StudioLayout from '../../Layouts/StudioLayout' import DateTimePicker from '../../components/ui/DateTimePicker' import { studioSurface, trackStudioEvent } from '../../utils/studioEvents' import { formatReleaseCountdown, formatScheduledDate } from '../../utils/scheduleCountdown' import NovaSelect from '../../components/ui/NovaSelect' async function requestJson(url, method = 'POST') { const response = await 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', }, }) const payload = await response.json().catch(() => ({})) if (!response.ok) { throw new Error(payload?.message || 'Request failed') } return payload } export default function StudioScheduled() { const { props } = usePage() const listing = props.listing || {} const filters = listing.filters || {} const summary = listing.summary || {} const agenda = listing.agenda || [] const items = listing.items || [] const meta = listing.meta || {} const rangeOptions = listing.range_options || [] const endpoints = props.endpoints || {} const [busyId, setBusyId] = useState(null) const [nowMs, setNowMs] = useState(() => Date.now()) const updateFilters = (patch) => { const next = { ...filters, ...patch } if (patch.page == null) next.page = 1 trackStudioEvent('studio_scheduled_opened', { surface: studioSurface(), module: next.module, meta: patch, }) router.get(window.location.pathname, next, { preserveScroll: true, preserveState: true, replace: true, }) } const actionUrl = (pattern, item) => String(pattern || '').replace('__MODULE__', item.module).replace('__ID__', item.numeric_id) const runAction = async (item, key) => { const url = actionUrl(key === 'publish' ? endpoints.publishNowPattern : endpoints.unschedulePattern, item) if (!url) return setBusyId(`${key}:${item.id}`) try { await requestJson(url) trackStudioEvent(key === 'publish' ? 'studio_schedule_updated' : 'studio_schedule_cleared', { surface: studioSurface(), module: item.module, item_module: item.module, item_id: item.numeric_id, }) router.reload({ only: ['listing', 'overview'] }) } catch (error) { window.alert(error?.message || 'Unable to update schedule.') } finally { setBusyId(null) } } useEffect(() => { const hasTimedEntries = Boolean(summary.next_publish_at) || items.some((item) => Boolean(item.scheduled_at || item.published_at)) if (!hasTimedEntries) return undefined const timer = window.setInterval(() => { setNowMs(Date.now()) }, 1000) return () => window.clearInterval(timer) }, [items, summary.next_publish_at]) return (
Scheduled total
{Number(summary.total || 0).toLocaleString()}
Next publish slot
{formatReleaseCountdown(summary.next_publish_at, nowMs)}
{summary.next_publish_at &&
{formatScheduledDate(summary.next_publish_at)}
}
{(summary.by_module || []).map((entry) => (
{entry.label}
{Number(entry.count || 0).toLocaleString()}
))}

Agenda

{agenda.length > 0 ? agenda.slice(0, 6).map((day) => (
{day.label} {day.count} items
{day.items.slice(0, 2).map((item) => item.title).join(' • ')}
)) :
No scheduled items yet.
}
Module updateFilters({ module: val })} options={listing.module_options || []} searchable={false} />
Date range updateFilters({ range: val })} options={rangeOptions} searchable={false} />
Start date updateFilters({ range: 'custom', start_date: nextValue })} mode="date" placeholder="Start date" clearable className="bg-black/20" />
End date updateFilters({ range: 'custom', end_date: nextValue })} mode="date" placeholder="End date" clearable className="bg-black/20" />
{items.length > 0 ? items.map((item) => (
{item.module_label} {item.status}

{item.title}

{formatReleaseCountdown(item.scheduled_at || item.published_at, nowMs)} {formatScheduledDate(item.scheduled_at || item.published_at)} {item.visibility && Visibility: {item.visibility}} {item.updated_at && Last edited {formatScheduledDate(item.updated_at)}} {item.schedule_timezone && {item.schedule_timezone}}
Edit Reschedule {item.preview_url && Preview}
)) :
No scheduled content matches this view.
}
Page {meta.current_page || 1} of {meta.last_page || 1}
) }