diff --git a/src/App.tsx b/src/App.tsx index f5e4eec..90e9c5b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,7 @@ const ProductGroupDetails = React.lazy(() => import('./pages/ProductGroupDetails const Replenishment = React.lazy(() => import('./pages/Replenishment')); const Cutting = React.lazy(() => import('./pages/Cutting')); const ProductionOrders = React.lazy(() => import('./pages/ProductionOrders')); +const Supplies = React.lazy(() => import('./pages/Supplies')); const Clients = React.lazy(() => import('./pages/Clients')); const ClientDetails = React.lazy(() => import('./pages/ClientDetails')); const Campaigns = React.lazy(() => import('./pages/Campaigns')); @@ -53,6 +54,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> } /> diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 30b93c7..5ca84a8 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { Outlet, Link, useLocation } from 'react-router-dom'; -import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, LogOut, Megaphone, Grid3X3, Shield, Moon, Sun, ClipboardList, ShoppingCart, Scissors, Tags } from 'lucide-react'; +import { LayoutDashboard, Users, BarChart3, ChevronLeft, ChevronRight, Package, LogOut, Megaphone, Grid3X3, Shield, Moon, Sun, Tags, Boxes } from 'lucide-react'; import type { DateRange, OrderData } from '../types'; import { isSuperAdmin, logout } from '../dataService'; import { rangeForLastDays } from '../dateRanges'; @@ -74,9 +74,7 @@ const Layout = () => { items: [ { name: 'Produtos', href: '/products', icon: Package }, { name: 'Cadastros', href: '/registrations', icon: Tags }, - { name: 'Reposição', href: '/replenishment', icon: ShoppingCart }, - { name: 'Corte', href: '/cutting', icon: Scissors }, - { name: 'Ordens de Produção', href: '/production-orders', icon: ClipboardList }, + { name: 'Suprimentos', href: '/supplies', icon: Boxes }, ], }, { diff --git a/src/pages/Supplies.tsx b/src/pages/Supplies.tsx new file mode 100644 index 0000000..1d33371 --- /dev/null +++ b/src/pages/Supplies.tsx @@ -0,0 +1,338 @@ +import { useState } from 'react'; +import { Link as RouterLink, Navigate, useParams } from 'react-router-dom'; +import { + AlertTriangle, + ArrowLeft, + ArrowRight, + BarChart3, + Boxes, + ClipboardCheck, + ClipboardList, + Download, + Link as LinkIcon, + Package, + RefreshCw, + Repeat2, + Ruler, + Search, + Scissors, + ShoppingCart, + Truck, + Warehouse, +} from 'lucide-react'; + +type InventoryTab = 'dashboard' | 'balance' | 'receipts' | 'inventory' | 'movements'; + +const pageClassName = 'mx-auto flex w-full max-w-7xl flex-col gap-6'; +const panelClassName = 'rounded-2xl border border-dark-border bg-dark-card shadow-sm'; +const mutedPanelClassName = 'rounded-2xl border border-dark-border bg-dark-card/70 shadow-sm'; +const buttonClassName = 'inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer'; +const inputClassName = 'h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary'; +const emptyStateClassName = 'flex min-h-[190px] flex-col items-center justify-center gap-2 px-4 py-10 text-center'; + +const stats = [ + { label: 'Total em estoque', value: '0 kg' }, + { label: 'Lotes ativos', value: '0' }, + { label: 'Rolos', value: '0' }, + { label: 'Alertas', value: '0' }, +]; + +const inventoryTabs: Array<{ id: InventoryTab; name: string; icon: typeof BarChart3 }> = [ + { id: 'dashboard', name: 'Dashboard', icon: Warehouse }, + { id: 'balance', name: 'Saldo', icon: BarChart3 }, + { id: 'receipts', name: 'Recebimentos', icon: Truck }, + { id: 'inventory', name: 'Inventário', icon: ClipboardCheck }, + { id: 'movements', name: 'Movimentações', icon: Repeat2 }, +]; + +const receiptCategories = [ + { name: 'Malha / Tecido', icon: Ruler }, + { name: 'Embalagem', icon: Package }, + { name: 'Material de Limpeza', icon: Boxes }, + { name: 'Acessórios / Botões', icon: Warehouse }, + { name: 'Etiquetas', icon: LinkIcon }, + { name: 'Outro material', icon: ClipboardList }, +]; + +const Header = ({ title, subtitle, backTo }: { title: string; subtitle: string; backTo?: string }) => ( +
+ {backTo && ( + + + Suprimentos + + )} +
+

{title}

+

{subtitle}

+
+
+); + +const ModuleCard = ({ + title, + description, + icon: Icon, + to, + disabled, +}: { + title: string; + description: string; + icon: typeof Package; + to?: string; + disabled?: boolean; +}) => { + const content = ( + <> +
+ +
+
+
+

{title}

+ {disabled ? ( + Em breve + ) : ( + + )} +
+

{description}

+
+ + ); + + if (disabled || !to) { + return ( +
+ {content} +
+ ); + } + + return ( + + {content} + + ); +}; + +const SuppliesHub = () => ( +
+
+
+ + + + + + + +
+
+); + +const StatGrid = () => ( +
+ {stats.map(stat => ( +
+

{stat.value}

+

{stat.label}

+
+ ))} +
+); + +const InventoryDashboard = () => ( +
+ +
+

Por categoria de material

+
+ {[ + { title: 'Malha / Tecido', icon: Ruler }, + { title: 'Botões / Acessórios', icon: Warehouse }, + ].map(item => ( +
+ +

{item.title}

+

Em breve

+

Aguardando cadastro

+ Não configurado +
+ ))} +
+
+
+

Alertas de estoque

+

Nenhum alerta no momento.

+
+
+
+

Últimas entradas

+ +
+

Nenhuma entrada registrada.

+
+
+); + +const BalanceTab = () => ( +
+
+ + +
+
+
+ + + +
+
+
+ +
+
+
+

Saldo por tipo

+ Clique para expandir lotes +
+
+ +

Nenhum item em estoque

+

Registre entradas para ver o saldo aqui.

+
+
+
+); + +const ReceiptsTab = () => ( +
+
+ {['Novo recebimento', 'Pendentes', 'Histórico'].map((item, index) => ( + + ))} +
+
+

Registrar recebimento

+
+ Preencha o que chegou. O financeiro vincula OC/NF e aprova o lançamento. +
+

1. Qual categoria de produto chegou?

+
+ {receiptCategories.map(category => ( + + ))} +
+
+
+); + +const InventoryCountTab = () => ( +
+ +
+

Inventário físico

+
+ Compare o estoque do sistema com a contagem física. O ajuste gera movimentação com justificativa. +
+
+ + +
+
+ +

Nenhum lote para inventariar

+
+
+
+); + +const MovementsTab = () => ( +
+ +
+
+ + +
+
+ +

Nenhuma movimentação ainda

+
+
+
+); + +const InventoryScreen = () => { + const [activeTab, setActiveTab] = useState('dashboard'); + + return ( +
+
+
+ {inventoryTabs.map(tab => ( + + ))} +
+ {activeTab === 'dashboard' && } + {activeTab === 'balance' && } + {activeTab === 'receipts' && } + {activeTab === 'inventory' && } + {activeTab === 'movements' && } +
+ ); +}; + +const FabricPlanningScreen = () => ( +
+
+
+
+
+

Planos de malha

+

Entrada operacional preparada para a próxima etapa do módulo.

+
+ Em preparação +
+
+ +

Nenhum plano de malha cadastrado

+

Quando houver planos, eles alimentarão recebimentos e corte.

+
+
+
+); + +const Supplies = () => { + const { section } = useParams<{ section?: string }>(); + + if (!section) return ; + if (section === 'inventory') return ; + if (section === 'fabric-planning') return ; + + return ; +}; + +export default Supplies;