Consolida recepcoes, stock, equipamentos e identidade SGC
This commit is contained in:
@@ -8,7 +8,7 @@ export default function AppLogo() {
|
||||
</div>
|
||||
<div className="ml-1 grid flex-1 text-left text-sm">
|
||||
<span className="mb-0.5 truncate leading-tight font-semibold">
|
||||
ProcuraFlow
|
||||
SGC
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
Scale,
|
||||
ShieldAlert,
|
||||
FileCheck2,
|
||||
PackageCheck,
|
||||
Warehouse,
|
||||
} from 'lucide-react';
|
||||
import AppLogo from '@/components/app-logo';
|
||||
import { NavMain } from '@/components/nav-main';
|
||||
@@ -23,42 +25,89 @@ import {
|
||||
import { dashboard } from '@/routes';
|
||||
import type { NavItem } from '@/types';
|
||||
|
||||
const mainNavItems: NavItem[] = [
|
||||
const generalNavItems: NavItem[] = [
|
||||
{
|
||||
title: 'Visão geral',
|
||||
href: dashboard(),
|
||||
icon: LayoutGrid,
|
||||
},
|
||||
{ title: 'Requisições', href: '/requisicoes', icon: ClipboardList },
|
||||
{ title: 'Conflitos de interesses', href: '/conflitos', icon: ShieldAlert },
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
const { auth } = usePage().props;
|
||||
const purchasingItems = [
|
||||
const canReadPurchases = [
|
||||
'admin',
|
||||
'procurement',
|
||||
'finance',
|
||||
'department_head',
|
||||
].includes(auth.user.purchasing_role ?? '')
|
||||
? [
|
||||
...mainNavItems,
|
||||
{ title: 'Fornecedores', href: '/fornecedores', icon: Users },
|
||||
{ title: 'Cotações', href: '/cotacoes', icon: Scale },
|
||||
{ title: 'Ordens de compra', href: '/ordens', icon: FileCheck2 },
|
||||
]
|
||||
: mainNavItems;
|
||||
const items =
|
||||
].includes(auth.user.purchasing_role ?? '');
|
||||
const purchasingItems: NavItem[] = [
|
||||
{ title: 'Requisições', href: '/requisicoes', icon: ClipboardList },
|
||||
...(canReadPurchases
|
||||
? [
|
||||
{ title: 'Fornecedores', href: '/fornecedores', icon: Users },
|
||||
{ title: 'Cotações', href: '/cotacoes', icon: Scale },
|
||||
{
|
||||
title: 'Ordens de compra',
|
||||
href: '/ordens',
|
||||
icon: FileCheck2,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const inventoryItems = [
|
||||
...([
|
||||
'admin',
|
||||
'procurement',
|
||||
'finance',
|
||||
'warehouse',
|
||||
'department_head',
|
||||
].includes(auth.user.purchasing_role ?? '')
|
||||
? [{ title: 'Recepção', href: '/recepcoes', icon: PackageCheck }]
|
||||
: []),
|
||||
...([
|
||||
'admin',
|
||||
'procurement',
|
||||
'finance',
|
||||
'warehouse',
|
||||
'department_head',
|
||||
].includes(auth.user.purchasing_role ?? '')
|
||||
? [
|
||||
{
|
||||
title: 'Equipamentos e peças',
|
||||
href: '/equipamentos',
|
||||
icon: Warehouse,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(['admin', 'procurement', 'finance', 'warehouse'].includes(
|
||||
auth.user.purchasing_role ?? '',
|
||||
)
|
||||
? [
|
||||
{
|
||||
title: 'Stock e movimentos',
|
||||
href: '/stock',
|
||||
icon: Warehouse,
|
||||
},
|
||||
{
|
||||
title: 'Saídas e inventários',
|
||||
href: '/stock/operacoes',
|
||||
icon: ClipboardList,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
const administrationItems =
|
||||
auth.user.purchasing_role === 'admin'
|
||||
? [
|
||||
...purchasingItems,
|
||||
{
|
||||
title: 'Empresa e equipa',
|
||||
href: '/empresa',
|
||||
icon: Building2,
|
||||
},
|
||||
]
|
||||
: purchasingItems;
|
||||
: [];
|
||||
return (
|
||||
<Sidebar collapsible="icon" variant="inset">
|
||||
<SidebarHeader>
|
||||
@@ -74,10 +123,26 @@ export function AppSidebar() {
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<NavMain items={items} />
|
||||
<NavMain title="Geral" items={generalNavItems} />
|
||||
<NavMain title="Compras" items={purchasingItems} />
|
||||
{inventoryItems.length > 0 && (
|
||||
<NavMain title="Armazém" items={inventoryItems} />
|
||||
)}
|
||||
{administrationItems.length > 0 && (
|
||||
<NavMain
|
||||
title="Administração"
|
||||
items={administrationItems}
|
||||
/>
|
||||
)}
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter>
|
||||
<Link
|
||||
href="/"
|
||||
className="px-3 py-2 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Página inicial
|
||||
</Link>
|
||||
<NavUser />
|
||||
</SidebarFooter>
|
||||
</Sidebar>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import type { Page, ReceiptProgress } from '@/types/inventory';
|
||||
import { quantity, receiptStates } from '@/types/inventory';
|
||||
|
||||
export function InventoryNotice() {
|
||||
return (
|
||||
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-100">
|
||||
Ambiente de desenvolvimento. Não autoriza compras reais.{' '}
|
||||
<strong>O orçamento ainda não é validado.</strong>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export function ReceiptProgressTable({
|
||||
progress,
|
||||
}: {
|
||||
progress: ReceiptProgress;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<p className="mb-4 text-sm font-semibold">
|
||||
{receiptStates[progress.state]}
|
||||
{progress.has_divergences
|
||||
? ' · Existem divergências de qualidade'
|
||||
: ''}
|
||||
</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead className="border-b text-muted-foreground">
|
||||
<tr>
|
||||
<th className="pr-4 pb-3">Artigo</th>
|
||||
<th className="pr-4">Autorizado</th>
|
||||
<th className="pr-4">Recebido</th>
|
||||
<th className="pr-4">Aceite</th>
|
||||
<th className="pr-4">Rejeitado</th>
|
||||
<th className="pr-4">Por entregar</th>
|
||||
<th>Por aceitar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{progress.lines.map((line) => (
|
||||
<tr
|
||||
key={line.id}
|
||||
className="border-b last:border-0"
|
||||
>
|
||||
<td className="py-4 pr-4">
|
||||
<strong>{line.description}</strong>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{line.unit}
|
||||
</p>
|
||||
</td>
|
||||
{[
|
||||
line.ordered_milli,
|
||||
line.received_milli,
|
||||
line.accepted_milli,
|
||||
line.rejected_milli,
|
||||
line.delivery_pending_milli,
|
||||
line.acceptance_pending_milli,
|
||||
].map((value, index) => (
|
||||
<td
|
||||
key={index}
|
||||
className="pr-4 whitespace-nowrap"
|
||||
>
|
||||
{quantity(value)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export function InventoryPagination<T>({ page }: { page: Page<T> }) {
|
||||
return (
|
||||
<div className="mt-4 flex gap-4 text-sm text-teal-700 dark:text-teal-400">
|
||||
{page.prev_page_url && (
|
||||
<Link href={page.prev_page_url}>Anterior</Link>
|
||||
)}
|
||||
{page.next_page_url && (
|
||||
<Link href={page.next_page_url}>Seguinte</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,20 +7,36 @@ import {
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useCurrentUrl } from '@/hooks/use-current-url';
|
||||
import { toUrl } from '@/lib/utils';
|
||||
import type { NavItem } from '@/types';
|
||||
|
||||
export function NavMain({ items }: { items: NavItem[] }) {
|
||||
const { isCurrentUrl } = useCurrentUrl();
|
||||
export function NavMain({
|
||||
items,
|
||||
title = 'Navegação',
|
||||
}: {
|
||||
items: NavItem[];
|
||||
title?: string;
|
||||
}) {
|
||||
const { isCurrentOrParentUrl, currentUrl } = useCurrentUrl();
|
||||
const receivingOrder = /^\/ordens\/\d+\/recepcoes/.test(currentUrl);
|
||||
|
||||
return (
|
||||
<SidebarGroup className="px-2 py-0">
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarGroupLabel>{title}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item) => (
|
||||
<SidebarMenuItem key={item.title}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={isCurrentUrl(item.href)}
|
||||
isActive={
|
||||
(toUrl(item.href) === '/recepcoes' &&
|
||||
receivingOrder) ||
|
||||
(isCurrentOrParentUrl(item.href) &&
|
||||
!(
|
||||
toUrl(item.href) === '/ordens' &&
|
||||
receivingOrder
|
||||
))
|
||||
}
|
||||
tooltip={{ children: item.title }}
|
||||
>
|
||||
<Link href={item.href} prefetch>
|
||||
|
||||
Reference in New Issue
Block a user