Files
SkinbaseNova/resources/js/Pages/Moderation/Enhance/Index.jsx

110 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react'
import { Head, Link, router, usePage } from '@inertiajs/react'
import EnhanceStatusBadge from '../../../components/enhance/EnhanceStatusBadge'
import EnhanceStubWarning from '../../../components/enhance/EnhanceStubWarning'
import { formatEnhanceDate } from '../../../utils/enhanceFormatting'
function formatDate(value) {
return formatEnhanceDate(value)
}
export default function ModerationEnhanceIndex() {
const { props } = usePage()
const [filters, setFilters] = React.useState(props.filters || {})
const jobs = props.jobs?.data || []
const flash = props.flash || {}
React.useEffect(() => {
setFilters(props.filters || {})
}, [props.filters])
function applyFilters(event) {
event.preventDefault()
router.get(props.indexUrl, filters, { preserveScroll: true, preserveState: true, replace: true })
}
return (
<div className="w-full px-6 pb-16 pt-8">
<Head title="Enhance Jobs" />
<section className="rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_36%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.92))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/80">Moderation surface</p>
<h1 className="mt-3 text-3xl font-semibold tracking-[-0.04em] text-white">Enhance Jobs</h1>
<p className="mt-2 max-w-3xl text-sm leading-relaxed text-slate-300">Review queued, processing, failed, and completed image upscale jobs without changing original artwork assets.</p>
</div>
<form onSubmit={applyFilters} className="mt-6 grid gap-3 md:grid-cols-2 xl:grid-cols-7">
<select value={filters.status || 'all'} onChange={(event) => setFilters((current) => ({ ...current, status: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.statuses || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All statuses' : option}</option>)}
</select>
<select value={filters.engine || 'all'} onChange={(event) => setFilters((current) => ({ ...current, engine: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.engines || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All engines' : option}</option>)}
</select>
<select value={filters.mode || 'all'} onChange={(event) => setFilters((current) => ({ ...current, mode: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.modes || []).map((option) => <option key={option} value={option}>{option === 'all' ? 'All modes' : option}</option>)}
</select>
<select value={filters.scale || 'all'} onChange={(event) => setFilters((current) => ({ ...current, scale: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none">
{(props.options?.scales || []).map((option) => <option key={String(option)} value={option}>{option === 'all' ? 'All scales' : `${option}x`}</option>)}
</select>
<input value={filters.user || ''} onChange={(event) => setFilters((current) => ({ ...current, user: event.target.value }))} placeholder="User name or username" className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<input type="date" value={filters.date_from || ''} onChange={(event) => setFilters((current) => ({ ...current, date_from: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<input type="date" value={filters.date_to || ''} onChange={(event) => setFilters((current) => ({ ...current, date_to: event.target.value }))} className="rounded-2xl border border-white/10 bg-slate-950/70 px-4 py-3 text-sm text-white outline-none" />
<button type="submit" className="rounded-2xl border border-white/10 bg-white/[0.06] px-5 py-3 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.1] xl:col-span-7">Apply filters</button>
</form>
</section>
<EnhanceStubWarning config={props.enhanceConfig} moderation className="mt-6" />
{flash.success ? <div className="mt-6 rounded-2xl border border-emerald-300/20 bg-emerald-400/10 px-4 py-3 text-sm text-emerald-50">{flash.success}</div> : null}
{flash.error ? <div className="mt-6 rounded-2xl border border-rose-300/20 bg-rose-400/10 px-4 py-3 text-sm text-rose-100">{flash.error}</div> : null}
<div className="mt-8 overflow-hidden rounded-[28px] border border-white/10 bg-[#08111d] shadow-[0_18px_48px_rgba(2,6,23,0.2)]">
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-white/[0.07] text-left text-xs font-semibold uppercase tracking-wider text-slate-500">
<th className="px-5 py-3.5">Preview</th>
<th className="px-5 py-3.5">Job</th>
<th className="px-5 py-3.5">User</th>
<th className="px-5 py-3.5">Artwork</th>
<th className="px-5 py-3.5">Status</th>
<th className="px-5 py-3.5">Mode</th>
<th className="px-5 py-3.5">Scale</th>
<th className="px-5 py-3.5">Dimensions</th>
<th className="px-5 py-3.5">Created</th>
<th className="px-5 py-3.5 text-right">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-white/[0.04]">
{jobs.length === 0 ? <tr><td colSpan={10} className="px-5 py-12 text-center text-slate-400">No enhance jobs match the current filters.</td></tr> : null}
{jobs.map((job) => (
<tr key={job.id} className="transition hover:bg-white/[0.025]">
<td className="px-5 py-4">
<div className="h-16 w-16 overflow-hidden rounded-xl border border-white/10 bg-black/20">
{job.preview_url || job.source_url ? <img src={job.preview_url || job.source_url} alt={`Enhance job ${job.id}`} className="h-full w-full object-cover" /> : null}
</div>
</td>
<td className="px-5 py-4 text-white">
<div className="font-semibold">#{job.id}</div>
<div className="mt-1 text-xs uppercase tracking-[0.16em] text-slate-500">{job.engine}</div>
</td>
<td className="px-5 py-4 text-slate-300">{job.user?.name || '—'}{job.user?.username ? <div className="text-xs text-slate-500">@{job.user.username}</div> : null}</td>
<td className="px-5 py-4 text-slate-300">{job.artwork?.title ? <a href={job.artwork.url} className="text-sky-300 hover:text-sky-200">{job.artwork.title}</a> : ''}</td>
<td className="px-5 py-4"><EnhanceStatusBadge status={job.status} /></td>
<td className="px-5 py-4 text-slate-300">{job.mode}</td>
<td className="px-5 py-4 text-slate-300">{job.scale}x</td>
<td className="px-5 py-4 text-slate-300">{job.input_width} × {job.input_height}{job.output_width && job.output_height ? <div className="text-xs text-slate-500"> {job.output_width} × {job.output_height}</div> : null}</td>
<td className="px-5 py-4 text-slate-400">{formatDate(job.created_at)}</td>
<td className="px-5 py-4 text-right">
<Link href={job.show_url} className="rounded-full border border-white/10 bg-white/[0.05] px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-white transition hover:bg-white/[0.09]">Open</Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)
}