168 lines
7.5 KiB
TypeScript
168 lines
7.5 KiB
TypeScript
import { Link } from '@inertiajs/react';
|
|
import { states as interestStates } from '@/pages/management/shared';
|
|
import { taskStates } from '@/pages/crm/types';
|
|
|
|
type WorkItem = {
|
|
id: string;
|
|
title: string;
|
|
reference: string | null;
|
|
status: string;
|
|
due_at: string | null;
|
|
overdue: boolean;
|
|
href: string;
|
|
};
|
|
type WorkGroup = {
|
|
total: number;
|
|
overdue: number;
|
|
items: WorkItem[];
|
|
page: number;
|
|
last_page: number;
|
|
previous: string | null;
|
|
next: string | null;
|
|
};
|
|
export type DashboardQueue = {
|
|
as_of: string;
|
|
desk_states: Record<string, string>;
|
|
groups: Partial<Record<'interests' | 'desk' | 'crm_tasks', WorkGroup>>;
|
|
};
|
|
const groups = {
|
|
interests: 'Manifestações atribuídas a mim',
|
|
desk: 'Pedidos do Balcão atribuídos a mim',
|
|
crm_tasks: 'Tarefas do CRM atribuídas a mim',
|
|
} as const;
|
|
const date = (value: string) =>
|
|
new Intl.DateTimeFormat('pt-MZ', {
|
|
dateStyle: 'medium',
|
|
timeStyle: 'short',
|
|
timeZone: 'Africa/Maputo',
|
|
}).format(new Date(value));
|
|
|
|
export default function DashboardWorkQueue({
|
|
queue,
|
|
}: {
|
|
queue: DashboardQueue | null | undefined;
|
|
}) {
|
|
if (!queue) return null;
|
|
return (
|
|
<section aria-labelledby="my-work-title" className="space-y-4">
|
|
<h2 id="my-work-title" className="text-xl font-semibold">
|
|
O meu acompanhamento
|
|
</h2>
|
|
<p className="text-muted-foreground text-sm">
|
|
Pedidos e tarefas atribuídos a si, por prazo. Actualizado em{' '}
|
|
<time dateTime={queue.as_of}>{date(queue.as_of)} (Maputo)</time>
|
|
.
|
|
</p>
|
|
<div className="grid gap-4 lg:grid-cols-3">
|
|
{Object.entries(groups).map(([kind, title]) => {
|
|
const group = queue.groups[kind as keyof typeof groups];
|
|
if (!group) return null;
|
|
const states =
|
|
kind === 'interests'
|
|
? interestStates
|
|
: kind === 'desk'
|
|
? queue.desk_states
|
|
: taskStates;
|
|
return (
|
|
<section
|
|
key={kind}
|
|
aria-labelledby={`work-${kind}`}
|
|
className="space-y-3 rounded-xl border p-4"
|
|
>
|
|
<h3 id={`work-${kind}`} className="font-semibold">
|
|
{title}
|
|
</h3>
|
|
<p>
|
|
{group.total} por acompanhar · {group.overdue}{' '}
|
|
com prazo ultrapassado
|
|
</p>
|
|
{group.items.length ? (
|
|
<ul className="space-y-3">
|
|
{group.items.map((item) => (
|
|
<li
|
|
key={item.id}
|
|
className="space-y-1 border-t pt-3"
|
|
>
|
|
<Link
|
|
className="focus-visible:outline-primary font-medium underline focus-visible:outline-2"
|
|
href={item.href}
|
|
>
|
|
{item.title}
|
|
</Link>
|
|
{item.reference && (
|
|
<p className="text-sm">
|
|
{item.reference}
|
|
</p>
|
|
)}
|
|
<p className="text-sm">
|
|
{states[item.status] ??
|
|
item.status}
|
|
</p>
|
|
<p className="text-sm">
|
|
{item.due_at ? (
|
|
<>
|
|
<time
|
|
dateTime={
|
|
item.due_at
|
|
}
|
|
>
|
|
{date(item.due_at)}{' '}
|
|
(Maputo)
|
|
</time>
|
|
{item.overdue && (
|
|
<strong>
|
|
{' '}
|
|
· Prazo
|
|
ultrapassado
|
|
</strong>
|
|
)}
|
|
</>
|
|
) : (
|
|
'Sem prazo definido'
|
|
)}
|
|
</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
<p>
|
|
Não tem registos activos atribuídos neste
|
|
acompanhamento.
|
|
</p>
|
|
)}
|
|
{group.last_page > 1 && (
|
|
<nav
|
|
aria-label={`Páginas: ${title}`}
|
|
className="flex flex-wrap items-center gap-3"
|
|
>
|
|
{group.previous && (
|
|
<Link
|
|
href={group.previous}
|
|
preserveScroll
|
|
className="underline"
|
|
>
|
|
Anterior
|
|
</Link>
|
|
)}
|
|
<span>
|
|
Página {group.page} de {group.last_page}
|
|
</span>
|
|
{group.next && (
|
|
<Link
|
|
href={group.next}
|
|
preserveScroll
|
|
className="underline"
|
|
>
|
|
Seguinte
|
|
</Link>
|
|
)}
|
|
</nav>
|
|
)}
|
|
</section>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|