338 lines
13 KiB
TypeScript
338 lines
13 KiB
TypeScript
import { Link, usePage } from '@inertiajs/react';
|
|
import { ChevronDown, LayoutDashboard } from 'lucide-react';
|
|
import type { ReactNode } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import { UserMenuContent } from '@/components/user-menu-content';
|
|
import { SgcWorkspaceShell } from '@/components/sgc-workspace-shell';
|
|
|
|
type Entry = { label: string; path: string; demo: string; roles?: string[] };
|
|
const readers = ['admin', 'procurement', 'finance', 'department_head'];
|
|
const inventory = [...readers, 'warehouse'];
|
|
const groups: { label: string; entries: Entry[] }[] = [
|
|
{
|
|
label: 'Compras',
|
|
entries: [
|
|
{ label: 'Planeamento', path: '/planeamento', demo: '' },
|
|
{
|
|
label: 'Requisições',
|
|
path: '/requisicoes',
|
|
demo: '/consulta/requisicoes',
|
|
},
|
|
{
|
|
label: 'Fornecedores',
|
|
path: '/fornecedores',
|
|
demo: '/consulta/fornecedores',
|
|
roles: readers,
|
|
},
|
|
{
|
|
label: 'Cotações',
|
|
path: '/cotacoes',
|
|
demo: '/consulta/cotacoes',
|
|
roles: readers,
|
|
},
|
|
{
|
|
label: 'Ordens de compra',
|
|
path: '/ordens',
|
|
demo: '/consulta/ordens',
|
|
roles: readers,
|
|
},
|
|
{
|
|
label: 'Recomendação (proposta)',
|
|
path: '',
|
|
demo: '/proposta/recomendacao',
|
|
roles: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: 'Finanças',
|
|
entries: [
|
|
{
|
|
label: 'Orçamentos',
|
|
path: '/orcamentos',
|
|
demo: '/consulta?ecra=reinforcements',
|
|
roles: ['admin', 'finance'],
|
|
},
|
|
{
|
|
label: 'Facturas',
|
|
path: '/facturas',
|
|
demo: '/consulta/facturas',
|
|
roles: ['admin', 'finance'],
|
|
},
|
|
{
|
|
label: 'Reforços orçamentais',
|
|
path: '/orcamentos',
|
|
demo: '/consulta/reforcos',
|
|
roles: ['admin', 'finance'],
|
|
},
|
|
{
|
|
label: 'Dados bancários',
|
|
path: '/fornecedores',
|
|
demo: '/consulta/dados-bancarios',
|
|
roles: ['admin', 'finance'],
|
|
},
|
|
{
|
|
label: 'Pagamentos externos',
|
|
path: '/pagamentos',
|
|
demo: '/consulta/pagamentos',
|
|
roles: ['admin', 'finance'],
|
|
},
|
|
{
|
|
label: 'Regras de investimento',
|
|
path: '/regras-investimento',
|
|
demo: '/consulta/investimento',
|
|
roles: ['admin', 'finance', 'procurement'],
|
|
},
|
|
{
|
|
label: 'Política de admissão',
|
|
path: '/politica-admissao',
|
|
demo: '/proposta/admissao',
|
|
roles: ['admin'],
|
|
},
|
|
{
|
|
label: 'Política de recomendação',
|
|
path: '/politica-recomendacao',
|
|
demo: '',
|
|
roles: ['admin'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: 'Armazém',
|
|
entries: [
|
|
{
|
|
label: 'Recepções',
|
|
path: '/recepcoes',
|
|
demo: '/consulta/recepcoes',
|
|
roles: inventory,
|
|
},
|
|
{
|
|
label: 'Equipamentos e peças',
|
|
path: '/equipamentos',
|
|
demo: '/consulta/equipamentos',
|
|
roles: inventory,
|
|
},
|
|
{
|
|
label: 'Stock e movimentos',
|
|
path: '/stock',
|
|
demo: '/consulta/stock',
|
|
roles: ['admin', 'procurement', 'finance', 'warehouse'],
|
|
},
|
|
{
|
|
label: 'Saídas e inventários',
|
|
path: '/stock/operacoes',
|
|
demo: '/consulta/operacoes-stock',
|
|
roles: ['admin', 'procurement', 'finance', 'warehouse'],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: 'Gestão',
|
|
entries: [
|
|
{
|
|
label: 'Conflitos de interesses',
|
|
path: '/conflitos',
|
|
demo: '/consulta/conflitos',
|
|
},
|
|
{
|
|
label: 'Cancelamento e revisão',
|
|
path: '/revisao-compras',
|
|
demo: '',
|
|
},
|
|
{
|
|
label: 'Empresa e equipa',
|
|
path: '/empresa',
|
|
demo: '',
|
|
roles: ['admin'],
|
|
},
|
|
{
|
|
label: 'Aparência',
|
|
path: '/settings/appearance',
|
|
demo: '/consulta/aparencia',
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
function SgcPreviewShell({
|
|
children,
|
|
demo = false,
|
|
}: {
|
|
children: ReactNode;
|
|
demo?: boolean;
|
|
}) {
|
|
const page = usePage();
|
|
const user = page.props.auth?.user;
|
|
const role = user?.purchasing_role ?? '';
|
|
const overview = demo ? '/proposta/principal' : '/dashboard';
|
|
const active = (entry: Entry) =>
|
|
demo
|
|
? page.url.split('?')[0] === entry.demo.split('?')[0] &&
|
|
page.url === entry.demo
|
|
: page.url === entry.path || page.url.startsWith(`${entry.path}/`);
|
|
return (
|
|
<div className="flex min-h-screen flex-col bg-background text-foreground">
|
|
<a
|
|
href="#sgc-main"
|
|
className="sr-only z-50 rounded bg-white p-3 text-black focus:not-sr-only focus:absolute"
|
|
>
|
|
Saltar para o conteúdo
|
|
</a>
|
|
{demo && (
|
|
<div className="bg-[#1f1f1f] px-4 py-2 text-center text-xs text-slate-200">
|
|
Demonstração · Dados fictícios · Sem operações reais
|
|
</div>
|
|
)}
|
|
<header className="border-b border-white/10 bg-[#090909] text-white print:hidden">
|
|
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-4 px-5 py-5 lg:px-8">
|
|
<Link href={overview} className="flex items-center gap-3">
|
|
<span className="grid size-11 place-items-center rounded-xl bg-[#ed1c24] font-bold text-white">
|
|
SGC
|
|
</span>
|
|
<span>
|
|
<span className="block text-sm font-semibold">
|
|
Sistema de Gestão de Compras
|
|
</span>
|
|
<span className="mt-1 block text-xs text-slate-400">
|
|
Compras com controlo e transparência
|
|
</span>
|
|
</span>
|
|
</Link>
|
|
{user && !demo ? (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
className="text-white hover:bg-white/10 hover:text-white"
|
|
>
|
|
{user.name}
|
|
<ChevronDown className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<UserMenuContent user={user} />
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<Button
|
|
asChild
|
|
variant="outline"
|
|
className="border-white/20 bg-transparent text-white hover:bg-white/10 hover:text-white"
|
|
>
|
|
<Link href="/login">Entrar na minha empresa</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<nav
|
|
aria-label="Navegação principal"
|
|
className="mx-auto flex max-w-7xl flex-wrap gap-1 px-5 pb-3 lg:px-8"
|
|
>
|
|
<Link
|
|
href={overview}
|
|
aria-current={
|
|
page.url === overview ? 'page' : undefined
|
|
}
|
|
className={`inline-flex min-h-11 items-center gap-2 rounded-lg border-b-2 px-4 py-2 text-sm font-medium ${page.url === overview ? 'border-[#ed1c24] bg-white/5' : 'border-transparent text-slate-200 hover:bg-white/10'}`}
|
|
>
|
|
<LayoutDashboard className="size-4" />
|
|
Visão geral
|
|
</Link>
|
|
{groups.map((group) => {
|
|
const entries = group.entries.filter((entry) =>
|
|
demo
|
|
? entry.demo !== ''
|
|
: !entry.roles || entry.roles.includes(role),
|
|
);
|
|
if (!entries.length) return null;
|
|
return (
|
|
<DropdownMenu key={group.label}>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
className={`min-h-11 border-b-2 text-slate-200 hover:bg-white/10 hover:text-white data-[state=open]:bg-white/10 ${entries.some(active) ? 'border-[#ed1c24] text-white' : 'border-transparent'}`}
|
|
>
|
|
{group.label}
|
|
<ChevronDown className="size-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="start"
|
|
className="min-w-60 rounded-xl border-slate-200 bg-white p-2 text-slate-900 shadow-xl"
|
|
>
|
|
{entries.map((entry) => (
|
|
<DropdownMenuItem
|
|
key={entry.label}
|
|
asChild
|
|
className="min-h-11 rounded-md focus:bg-red-50 focus:text-red-700"
|
|
>
|
|
<Link
|
|
href={
|
|
demo
|
|
? entry.demo
|
|
: entry.path
|
|
}
|
|
aria-current={
|
|
active(entry)
|
|
? 'page'
|
|
: undefined
|
|
}
|
|
>
|
|
{entry.label}
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
})}
|
|
</nav>
|
|
</header>
|
|
<main id="sgc-main" className="min-w-0 flex-1" tabIndex={-1}>
|
|
{children}
|
|
</main>
|
|
<footer className="mt-auto bg-[#090909] text-slate-300 print:hidden">
|
|
<div className="mx-auto flex max-w-7xl flex-wrap items-center justify-between gap-3 px-5 py-6 text-xs lg:px-8">
|
|
<span>
|
|
<strong className="font-semibold text-white">
|
|
SGC
|
|
</strong>{' '}
|
|
· Sistema de Gestão de Compras
|
|
</span>
|
|
<span>
|
|
{demo
|
|
? 'Ambiente de demonstração'
|
|
: 'Espaço de trabalho da sua empresa'}
|
|
<span className="mx-3 text-[#ed1c24]">•</span>
|
|
<Link
|
|
href={demo ? '/' : '/settings/profile'}
|
|
className="hover:text-white"
|
|
>
|
|
{demo ? 'Página inicial' : 'A minha conta'}
|
|
</Link>
|
|
</span>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SgcShell({
|
|
children,
|
|
demo = false,
|
|
}: {
|
|
children: ReactNode;
|
|
demo?: boolean;
|
|
}) {
|
|
return demo ? (
|
|
<SgcPreviewShell demo>{children}</SgcPreviewShell>
|
|
) : (
|
|
<SgcWorkspaceShell>{children}</SgcWorkspaceShell>
|
|
);
|
|
}
|