This commit is contained in:
2026-11-03 11:51:44 +02:00
parent badaada043
commit e753f7a0a4
309 changed files with 14537 additions and 529 deletions
+67 -3
View File
@@ -1,5 +1,5 @@
import { Head, Link, router } from '@inertiajs/react';
import { PageTitle, Panel } from '@/components/procurement';
import { Head, Link, router, useForm } from '@inertiajs/react';
import { PageTitle, Panel, controlClass } from '@/components/procurement';
import { Button } from '@/components/ui/button';
import { roleLabels, statusLabels } from '@/types/procurement';
@@ -7,7 +7,9 @@ type Props = {
ready: boolean;
unread: number;
view: string;
search: string;
notices: {
current_page: number;
total: number;
data: {
id: number;
@@ -24,7 +26,14 @@ type Props = {
next_page_url: string | null;
};
};
export default function Notices({ ready, unread, view, notices }: Props) {
export default function Notices({
ready,
unread,
view,
search,
notices,
}: Props) {
const form = useForm({ view, search });
return (
<>
<Head title="Avisos de aprovação" />
@@ -61,6 +70,56 @@ export default function Notices({ ready, unread, view, notices }: Props) {
}
description="Intervenções concluídas deixam a fila pendente. Resultados registados mantêm-se visíveis ao requisitante com acesso actual. Não são criados avisos retroactivos para decisões antigas."
>
<details
className="mb-4 rounded border p-3"
open={Boolean(search)}
>
<summary className="cursor-pointer text-sm">
Localizar por referência da requisição
</summary>
<form
className="mt-3 flex flex-wrap items-end gap-3"
onSubmit={(event) => {
event.preventDefault();
form.transform((data) => ({
...data,
view,
}));
form.get('/requisicoes/avisos', {
preserveState: true,
});
}}
>
<label className="text-sm">
Referência
<input
className={controlClass}
maxLength={100}
value={form.data.search}
onChange={(event) =>
form.setData(
'search',
event.target.value,
)
}
/>
</label>
<Button disabled={form.processing}>Procurar</Button>
<Link
className="text-sm underline"
href={`/requisicoes/avisos?view=${view}`}
>
Limpar pesquisa
</Link>
{form.errors.search && (
<p role="alert">{form.errors.search}</p>
)}
</form>
</details>
<p className="mb-3 text-sm text-muted-foreground">
{notices.total} aviso(s) nesta consulta · Página{' '}
{notices.current_page}
</p>
{!ready ? (
<p>Instalação dos avisos internos pendente.</p>
) : notices.data.length === 0 ? (
@@ -102,6 +161,11 @@ export default function Notices({ ready, unread, view, notices }: Props) {
onClick={() =>
router.post(
`/requisicoes/avisos/${notice.id}/ler`,
{
view,
search,
page: notices.current_page,
},
)
}
>
+43 -2
View File
@@ -8,7 +8,9 @@ type Props = {
modules: Record<string, string>;
counts: Record<string, number>;
checkedAt: string;
filters: { module: string; search: string };
asOf: string;
deadlineCounts: Record<string, number>;
filters: { module: string; search: string; due: string };
tasks: {
total: number;
data: {
@@ -19,6 +21,7 @@ type Props = {
blocked: boolean;
created_at: string | null;
due_on: string | null;
due_status: string;
href: string;
}[];
prev_page_url: string | null;
@@ -31,11 +34,20 @@ export default function Tasks({
modules,
counts,
checkedAt,
asOf,
deadlineCounts,
filters,
tasks,
}: Props) {
const [search, setSearch] = useState(filters.search);
const [module, setModule] = useState(filters.module);
const [due, setDue] = useState(filters.due);
const deadlines: Record<string, string> = {
overdue: 'Prazo ultrapassado',
today: 'Vence hoje',
upcoming: 'Prazo futuro',
undated: 'Sem prazo registado',
};
const path =
area === 'people' ? '/pessoas/tarefas' : '/requisicoes/tarefas';
return (
@@ -81,7 +93,7 @@ export default function Tasks({
event.preventDefault();
router.get(
path,
{ module, search },
{ module, search, due },
{ preserveState: true },
);
}}
@@ -114,6 +126,23 @@ export default function Tasks({
}
/>
</label>
<label>
Prazo
<select
className={controlClass}
value={due}
onChange={(event) => setDue(event.target.value)}
>
<option value="all">Todos</option>
{Object.entries(deadlines).map(
([value, label]) => (
<option key={value} value={value}>
{label}
</option>
),
)}
</select>
</label>
<Button type="submit" variant="outline">
Filtrar
</Button>
@@ -121,6 +150,16 @@ export default function Tasks({
Limpar filtros
</Link>
</form>
<p className="mb-4 text-sm text-muted-foreground">
No módulo e pesquisa escolhidos, em {asOf}:{' '}
{Object.entries(deadlines)
.map(
([value, label]) =>
`${label}: ${deadlineCounts[value] ?? 0}`,
)
.join(' · ')}
.
</p>
<p className="mb-4 text-sm text-muted-foreground">
{tasks.total} tarefa(s) no filtro. Documentos mais
antigos aparecem primeiro; esta ordem não define
@@ -162,6 +201,8 @@ export default function Tasks({
<p className="mt-2 text-sm">
Prazo registado no processo:{' '}
{task.due_on}
{' · '}
{deadlines[task.due_status]}
</p>
)}
</article>