Files
Investidor-V2/resources/js/pages/management/editorial-workflow.tsx
T

326 lines
13 KiB
TypeScript

import { Link, router } from '@inertiajs/react';
import { Field, Management, button, control } from './shared';
import { states } from '@/lib/editorial-states';
type Item = {
id: number;
title: string;
revision_no: number;
state: string;
responsible: string | null;
published: boolean;
legal_required: boolean;
};
const eventTitles: Record<string, string> = {
submitted: 'Submissão',
pending_review: 'Submissão',
review_started: 'Início da revisão',
in_review: 'Início da revisão',
returned: 'Devolução',
rejected: 'Rejeição',
approved: 'Aprovação',
published: 'Publicação',
archived: 'Arquivo',
withdrawn: 'Retirada da publicação',
publication_withdrawn: 'Retirada da publicação',
suspended: 'Suspensão da publicação',
submit: 'Submissão',
start_review: 'Início da revisão',
return: 'Devolução',
reject: 'Rejeição',
legal_validate: 'Validação jurídica',
approve: 'Aprovação',
publish: 'Publicação',
archive: 'Arquivo',
};
export default function EditorialWorkflow({
items,
notices,
filters = {},
resourceAccess,
opportunitiesUrl,
offersUrl = null,
offerOperationsUrl = null,
opportunityPending,
clustersUrl,
portfolioUrl,
portfolioPending = 0,
demographyUrl,
demographyPending = 0,
territorialUrl,
nativePending = { clusters: 0, territories: 0 },
subjectsUrl,
subjectsPending = 0,
zonesUrl,
}: {
resourceAccess: boolean;
opportunitiesUrl: string | null;
offersUrl?: string | null;
offerOperationsUrl?: string | null;
opportunityPending: number;
clustersUrl?: string | null;
portfolioUrl?: string | null;
portfolioPending?: number;
demographyUrl?: string | null;
demographyPending?: number;
territorialUrl?: string | null;
nativePending?: { clusters: number; territories: number; zones?: number };
zonesUrl?: string | null;
subjectsUrl?: string | null;
subjectsPending?: number;
filters?: { state?: string; kind?: string };
items: { data: Item[]; current_page: number; last_page: number };
notices: {
id: number;
action: string;
read_at: string | null;
href: string;
reference: string;
}[];
}) {
const pageUrl = (page: number) =>
`/management/editorial?${new URLSearchParams({ ...filters, page: String(page) })}`;
const areas = [
{
title: 'Conteúdos e oportunidades',
entries: [
{
label: 'Artigos',
href: resourceAccess
? '/management/editorial?kind=article'
: null,
},
{
label: 'Legislação',
href: resourceAccess
? '/management/editorial?kind=legislation'
: null,
},
{
label: 'Documentos',
href: resourceAccess
? '/management/editorial?kind=document'
: null,
},
{
label: 'Oportunidades',
href: opportunitiesUrl,
pending: opportunityPending,
},
{
label: 'Ofertas — revisão privada',
href: offersUrl,
},
{ label: 'Ofertas — acompanhamento', href: offerOperationsUrl },
{
label: 'Biblioteca e editores',
href: resourceAccess ? '/management/portal-services' : null,
},
],
},
{
title: 'Investimentos e competitividade',
entries: [
{
label: 'Carteira',
href: portfolioUrl,
pending: portfolioPending,
},
{
label: 'Competitividade dos clusters',
href: clustersUrl,
pending: nativePending.clusters,
},
{
label: 'Estruturas e relações',
href: subjectsUrl,
pending: subjectsPending,
},
],
},
{
title: 'Território e dados',
entries: [
{
label: 'Conteúdos territoriais',
href: territorialUrl,
pending: nativePending.territories,
},
{
label: 'Zonas e indicadores',
href: zonesUrl,
pending: nativePending.zones ?? 0,
},
{
label: 'Demografia',
href: demographyUrl,
pending: demographyPending,
},
],
},
].filter((area) => area.entries.some((item) => item.href));
return (
<Management title="Circuito editorial">
<p className="max-w-3xl text-sm text-slate-600">
Acompanhe a preparação, revisão e publicação numa única área de
trabalho. Seleccione uma actividade para continuar nas tarefas
da sua equipa.
</p>
<section
aria-label="Actividades do dashboard"
className="grid gap-4 md:grid-cols-2 xl:grid-cols-3"
>
{areas.map((area) => (
<section
key={area.title}
className="overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm"
>
<h2 className="border-b border-slate-100 bg-slate-50 px-5 py-4 text-sm font-semibold text-slate-900">
{area.title}
</h2>
<ul className="divide-y divide-slate-100">
{area.entries.map((item) =>
item.href ? (
<li key={item.label}>
<Link
href={item.href}
className="flex min-h-14 items-center justify-between gap-3 px-5 py-3 text-sm font-medium text-slate-700 transition hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-red-700"
>
<span>{item.label}</span>
{'pending' in item && (
<span className="shrink-0 rounded-full bg-slate-100 px-2.5 py-1 text-xs text-slate-600">
{item.pending} pendentes
</span>
)}
</Link>
</li>
) : null,
)}
</ul>
</section>
))}
</section>
{resourceAccess && (
<>
<h2 className="text-lg font-semibold text-slate-900">
Fila de conteúdos
</h2>
<form
action="/management/editorial"
className="my-4 flex flex-wrap items-end gap-3"
>
<Field label="Conteúdo">
<select
className={control}
name="kind"
defaultValue={filters.kind ?? ''}
>
<option value="">Todos</option>
<option value="article">Artigos</option>
<option value="legislation">Legislação</option>
<option value="document">Documentos</option>
</select>
</Field>
<Field label="Estado da edição">
<select
className={control}
name="state"
defaultValue={filters.state ?? ''}
>
<option value="">Todos</option>
{Object.entries(states).map(
([state, label]) => (
<option key={state} value={state}>
{label}
</option>
),
)}
</select>
</Field>
<button className={button}>Filtrar fila</button>
</form>
<section aria-label="Fila editorial" className="space-y-3">
{items.data.map((item) => (
<article
key={item.id}
className="rounded border p-4"
>
<Link
className="font-semibold underline"
href={`/management/editorial/resources/${item.id}`}
>
{item.title}
</Link>
<p>
Edição {item.revision_no} ·{' '}
{states[item.state] ?? item.state} ·
Responsável:{' '}
{item.responsible ?? 'Por atribuir'}
</p>
<p>
{item.published
? 'Versão pública disponível'
: 'Sem versão pública'}
{item.legal_required &&
' · Exige validação jurídica'}
</p>
</article>
))}
</section>
<nav aria-label="Paginação" className="flex gap-4">
{items.current_page > 1 && (
<Link href={pageUrl(items.current_page - 1)}>
Anterior
</Link>
)}
<span>
Página {items.current_page} de {items.last_page}
</span>
{items.current_page < items.last_page && (
<Link href={pageUrl(items.current_page + 1)}>
Seguinte
</Link>
)}
</nav>
</>
)}
<section
id="notificacoes"
aria-label="Notificações editoriais"
className="rounded-xl border border-slate-200 bg-white p-5 shadow-sm"
>
<h2 className="mb-3 text-lg font-semibold">Notificações</h2>
{notices.length === 0 && (
<p className="text-sm text-slate-500">
Não há notificações neste âmbito.
</p>
)}
{notices.map((n) => (
<p
key={n.id}
className="flex flex-wrap items-center justify-between gap-3 border-t border-slate-100 py-3 text-sm"
>
<Link className="underline" href={n.href}>
{eventTitles[n.action] ?? 'Alteração editorial'} ·{' '}
{n.reference}
</Link>
{!n.read_at && (
<button
className="ml-3 underline"
onClick={() =>
router.post(
`/management/editorial/notices/${n.id}/read`,
)
}
>
Marcar como lida
</button>
)}
</p>
))}
</section>
</Management>
);
}