Permitir consulta pública dos ecrãs reais com dados privados protegidos
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-30 20:14:24 +02:00
parent c6373365bf
commit 2134aa084b
83 changed files with 3894 additions and 3127 deletions
+13 -2
View File
@@ -56,7 +56,11 @@ export function AppSidebar() {
...(canReadPurchases
? [
{ title: 'Fornecedores', href: '/fornecedores', icon: Users },
{ title: 'Avaliação de fornecedores', href: '/fornecedores/avaliacoes', icon: Users },
{
title: 'Avaliação de fornecedores',
href: '/fornecedores/avaliacoes',
icon: Users,
},
{ title: 'Cotações', href: '/cotacoes', icon: Scale },
{
title: 'Ordens de compra',
@@ -157,7 +161,14 @@ export function AppSidebar() {
icon: FileCheck2,
},
...(payablesAvailable === true
? [{title: 'Obrigações a fornecedores', href: '/obrigacoes-fornecedores', icon: FileCheck2, fullPage: true}]
? [
{
title: 'Obrigações a fornecedores',
href: '/obrigacoes-fornecedores',
icon: FileCheck2,
fullPage: true,
},
]
: []),
{
title: 'Pagamentos externos',
@@ -34,6 +34,9 @@ import { roleLabels } from '@/types/procurement';
export function SgcWorkspaceShell({ children }: { children: ReactNode }) {
const page = usePage();
const user = page.props.auth?.user;
const publicNavigation = page.props.publicNavigation as
| { paths: string[] }
| undefined;
const [mobileOpen, setMobileOpen] = useState(false);
const [search, setSearch] = useState('');
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
@@ -44,6 +47,7 @@ export function SgcWorkspaceShell({ children }: { children: ReactNode }) {
const groups = procurementNavigation(
access?.active ? access.permissions : (user?.purchasing_role ?? ''),
page.props.payablesAvailable === true,
publicNavigation?.paths,
);
const activeId = activeProcurementEntry(groups, page.url);
const activeGroup = groups.find((group) =>
@@ -274,6 +278,13 @@ export function SgcWorkspaceShell({ children }: { children: ReactNode }) {
);
return (
<div className="min-h-screen bg-slate-50 text-foreground dark:bg-background print:block">
{publicNavigation && (
<p className="border-b bg-background p-3 text-center text-sm">
Consulta pública de desenvolvimento · Sem organização
seleccionada e sem registos privados. Alterações exigem
autenticação.
</p>
)}
<a
href="#sgc-main"
className="sr-only z-50 rounded bg-white p-3 text-black focus:not-sr-only focus:fixed focus:top-2 focus:left-2"
+15 -11
View File
@@ -31,6 +31,7 @@ const inventory = [...readers, 'warehouse'];
export function procurementNavigation(
role: string | string[],
payables: boolean,
publicPaths?: string[],
): ProcurementGroup[] {
const groups: ProcurementGroup[] = [
{
@@ -341,17 +342,20 @@ export function procurementNavigation(
return compact
.map((group) => ({
...group,
entries: group.entries.filter(
(entry) =>
(entry.id !== 'new_request' ||
!Array.isArray(role) ||
role.includes('requester')) &&
(!entry.roles ||
entry.roles.some((permission) =>
(Array.isArray(role) ? role : [role]).includes(
permission,
),
)),
entries: group.entries.filter((entry) =>
publicPaths
? publicPaths.includes(
new URL(entry.path, 'http://local').pathname,
)
: (entry.id !== 'new_request' ||
!Array.isArray(role) ||
role.includes('requester')) &&
(!entry.roles ||
entry.roles.some((permission) =>
(Array.isArray(role) ? role : [role]).includes(
permission,
),
)),
),
}))
.filter((group) => group.entries.length);
+2 -2
View File
@@ -222,7 +222,7 @@ export default function Budgets({
Dotação acumulada: {money(b.approved_total_minor)}
</p>
<p className="my-3">{b.reason}</p>
{!b.approved_by && b.created_by !== auth.user.id && (
{!b.approved_by && b.created_by !== auth.user?.id && (
<Button
onClick={() =>
router.post(`/orcamentos/${b.id}/aprovar`)
@@ -270,7 +270,7 @@ export default function Budgets({
</p>
)}
{r.status === 'pending' &&
r.created_by !== auth.user.id && (
r.created_by !== auth.user?.id && (
<ReinforcementReview id={r.id} />
)}
</article>
@@ -444,7 +444,12 @@ export default function SubmissionForms({
return (
<>
<Head title="Formulários de candidaturas" />
<Link className="m-5 inline-block underline" href="/administracao/candidaturas/portal">Configurar área externa do candidato</Link>
<Link
className="m-5 inline-block underline"
href="/administracao/candidaturas/portal"
>
Configurar área externa do candidato
</Link>
<div className="mx-auto max-w-5xl space-y-6 p-5 lg:p-8">
<PageTitle
title="Inscrições e candidaturas electrónicas"
+4 -2
View File
@@ -31,6 +31,7 @@ export default function Dashboard({
approvals,
}: Props) {
const canRequest = usePage().props.canRequest === true;
const publicNavigation = !!usePage().props.publicNavigation;
const cards = [
{
label: 'Requisições',
@@ -66,6 +67,7 @@ export default function Dashboard({
title="Visão geral das compras"
description="Acompanhe os pedidos, as aprovações e a actividade da sua empresa."
action={
!publicNavigation &&
(!company || canRequest) && (
<Button
asChild
@@ -87,7 +89,7 @@ export default function Dashboard({
)
}
/>
{!company ? (
{!company && !publicNavigation ? (
<Panel
title="Vamos configurar a sua empresa"
description="Crie a organização e o primeiro departamento. Depois pode adicionar a equipa e começar a registar pedidos."
@@ -120,7 +122,7 @@ export default function Dashboard({
</div>
))}
</div>
{company.rules_provisional && (
{company?.rules_provisional && (
<div className="rounded-xl border border-amber-200 bg-amber-50 px-5 py-4 text-sm text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100">
<strong>
Ambiente de desenvolvimento · regras
+2 -2
View File
@@ -283,11 +283,11 @@ export default function Investment({
{canManage &&
p.status === 'pending' &&
p.id === policies[0]?.id &&
p.created_by !== auth.user.id &&
p.created_by !== auth.user?.id &&
!reviews.some(
(r) =>
r.investment_policy_id === p.id &&
r.reviewed_by === auth.user.id,
r.reviewed_by === auth.user?.id,
) && <Decision id={p.id} />}
</Panel>
))}
+15 -4
View File
@@ -71,7 +71,14 @@ export default function Invoices({
return (
<>
<Head title="Facturas" />
{payablesAvailable && <a className="m-6 inline-block underline" href="/obrigacoes-fornecedores">Obrigações a fornecedores — ensaio sintético</a>}
{payablesAvailable && (
<a
className="m-6 inline-block underline"
href="/obrigacoes-fornecedores"
>
Obrigações a fornecedores — ensaio sintético
</a>
)}
<div className="mx-auto grid w-full max-w-6xl gap-6 p-6">
<PageTitle
eyebrow="SGC · Finanças"
@@ -287,8 +294,12 @@ export default function Invoices({
</p>
{payablesAvailable && (
<p className="my-3">
<a className="underline" href={`/obrigacoes-fornecedores?invoice=${invoice.id}`}>
Consultar ou preparar obrigação desta factura
<a
className="underline"
href={`/obrigacoes-fornecedores?invoice=${invoice.id}`}
>
Consultar ou preparar obrigação desta
factura
</a>
</p>
)}
@@ -303,7 +314,7 @@ export default function Invoices({
</ul>
{canManage &&
invoice.status === 'pending' &&
invoice.created_by !== auth.user.id && (
invoice.created_by !== auth.user?.id && (
<Button
disabled={validating !== null}
onClick={() => {
+1 -1
View File
@@ -300,7 +300,7 @@ export default function Payments({
)}
{canManage &&
p.status === 'pending' &&
p.created_by !== auth.user.id && (
p.created_by !== auth.user?.id && (
<Review id={p.id} />
)}
</Panel>
File diff suppressed because one or more lines are too long