From 56ce81850b0047cc9428e6bcb0546adc57c40a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Fri, 28 Aug 2026 09:26:02 -0300 Subject: [PATCH] Add status tabs to production plans --- src/pages/ProductionPlans.tsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/pages/ProductionPlans.tsx b/src/pages/ProductionPlans.tsx index 86fa430..83790cf 100644 --- a/src/pages/ProductionPlans.tsx +++ b/src/pages/ProductionPlans.tsx @@ -13,6 +13,18 @@ const statusOptions: Array<{ value: ProductionOrderStatus; label: string; classN { value: 'canceled', label: 'Cancelada', className: 'border-zinc-500/35 bg-zinc-500/10 text-zinc-400' } ]; +type ProductionPlanStatusTab = 'all' | 'open' | 'cutting' | 'sewing' | 'review' | 'finished' | 'canceled'; + +const statusTabs: Array<{ key: ProductionPlanStatusTab; label: string; dotClass: string }> = [ + { key: 'all', label: 'Todos', dotClass: 'bg-dark-muted' }, + { key: 'open', label: 'Em aberto', dotClass: 'bg-amber-400' }, + { key: 'cutting', label: 'Em corte', dotClass: 'bg-amber-300' }, + { key: 'sewing', label: 'Em costura', dotClass: 'bg-sky-400' }, + { key: 'review', label: 'Em revisão', dotClass: 'bg-violet-400' }, + { key: 'finished', label: 'Finalizada', dotClass: 'bg-emerald-400' }, + { key: 'canceled', label: 'Cancelada', dotClass: 'bg-zinc-500' } +]; + const statusStyle = (status: string) => statusOptions.find(option => option.value === status)?.className || 'border-dark-border bg-dark-input text-dark-muted'; const formatNumber = (value: number) => new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 2 }).format(value); const formatDate = (value: string | null) => value ? new Intl.DateTimeFormat('pt-BR').format(new Date(`${value.slice(0, 10)}T00:00:00`)) : '-'; @@ -32,6 +44,7 @@ const ProductionPlans = () => { const [responsiblePickerPlanId, setResponsiblePickerPlanId] = useState(null); const [reviewingPlanId, setReviewingPlanId] = useState(null); const [isReviewUserPickerOpen, setIsReviewUserPickerOpen] = useState(false); + const [statusFilter, setStatusFilter] = useState('all'); const [reviewForm, setReviewForm] = useState({ decision: 'pending' as ProductionPlanReviewDecision, materialsReserved: false, @@ -77,6 +90,9 @@ const ProductionPlans = () => { const activePlans = useMemo(() => plans.filter(plan => !['finished', 'canceled'].includes(plan.status)), [plans]); const totalOpenQuantity = useMemo(() => activePlans.reduce((total, plan) => total + plan.totalQuantity, 0), [activePlans]); const reviewingPlan = useMemo(() => plans.find(plan => plan.id === reviewingPlanId) || null, [plans, reviewingPlanId]); + const filteredPlans = useMemo(() => ( + statusFilter === 'all' ? plans : plans.filter(plan => plan.status === statusFilter) + ), [plans, statusFilter]); const refreshPlans = async () => { setIsRefreshing(true); @@ -234,10 +250,10 @@ const ProductionPlans = () => { {message &&
{message}
}
-

Fila de planos

Mover um plano atualiza as OPs locais ativas. OPs finalizadas ou canceladas preservam o histórico.

{plans.length} {plans.length === 1 ? 'plano' : 'planos'}
- {isLoading ?
: plans.length ? ( +

Fila de planos

Mover um plano atualiza as OPs locais ativas. OPs finalizadas ou canceladas preservam o histórico.

{filteredPlans.length} {filteredPlans.length === 1 ? 'plano' : 'planos'}
{statusTabs.map(tab => { const count = tab.key === 'all' ? plans.length : plans.filter(plan => plan.status === tab.key).length; const isSelected = statusFilter === tab.key; return ; })}
+ {isLoading ?
: filteredPlans.length ? (
- {plans.map(plan => ( + {filteredPlans.map(plan => (
{plan.number}{plan.statusLabel}{plan.review && {reviewDecisionLabel[plan.review.decision]}}

{plan.name}

{plan.notes &&

{plan.notes}

}
@@ -250,7 +266,7 @@ const ProductionPlans = () => {
))}
- ) :

Nenhum plano confirmado

Gere OPs a partir do Plano de Corte para criar o primeiro plano PCP.

} + ) :

{plans.length ? 'Nenhum plano neste status' : 'Nenhum plano confirmado'}

{plans.length ? 'Escolha outro status para visualizar os demais planos.' : 'Gere OPs a partir do Plano de Corte para criar o primeiro plano PCP.'}

}
{reviewingPlan &&
busyPlanId !== reviewingPlan.id && setReviewingPlanId(null)}>