diff --git a/src/pages/ProductionPlans.tsx b/src/pages/ProductionPlans.tsx
index 0bd5f51..7e16049 100644
--- a/src/pages/ProductionPlans.tsx
+++ b/src/pages/ProductionPlans.tsx
@@ -67,6 +67,12 @@ const ProductionPlans = () => {
return () => window.clearTimeout(timer);
}, [loadOperationalUsers, loadPlans]);
+ useEffect(() => {
+ if (!message) return undefined;
+ const timer = window.setTimeout(() => setMessage(''), 5_000);
+ return () => window.clearTimeout(timer);
+ }, [message]);
+
const activePlans = useMemo(() => plans.filter(plan => !['finished', 'canceled'].includes(plan.status)), [plans]);
const totalOpenQuantity = useMemo(() => activePlans.reduce((total, plan) => total + plan.totalQuantity, 0), [activePlans]);
@@ -123,6 +129,29 @@ const ProductionPlans = () => {
}
};
+ const changeExpectedDate = async (plan: ProductionPlan, expectedDate: string) => {
+ setBusyPlanId(plan.id);
+ setMessage('');
+ try {
+ const updated = await updateProductionPlan(plan.id, {
+ name: plan.name,
+ contractor: plan.contractor,
+ responsibleUserId: plan.responsibleUserId,
+ responsibleName: plan.responsibleName,
+ plannedStartDate: plan.plannedStartDate,
+ expectedDate: expectedDate || null,
+ notes: plan.notes,
+ source: plan.source
+ });
+ setPlans(current => current.map(item => item.id === updated.id ? updated : item));
+ setMessage(expectedDate ? `Prazo de ${updated.number} atualizado.` : `Prazo de ${updated.number} removido.`);
+ } catch (error) {
+ setMessage(error instanceof Error ? error.message : 'Não foi possível atualizar o prazo.');
+ } finally {
+ setBusyPlanId(null);
+ }
+ };
+
const openReview = (plan: ProductionPlan) => {
const checklist = plan.review?.checklist || {};
setReviewForm({
@@ -175,8 +204,7 @@ const ProductionPlans = () => {
Suprimentos
-
PCP
-
Planos de Produção
+
Planos de Produção
Acompanhe cada frente de produção, o responsável e o estágio das OPs sem perder o histórico do plano.
@@ -203,16 +231,14 @@ const ProductionPlans = () => {
{plans.map(plan => (
-
-
{plan.number}{plan.statusLabel}{plan.review && {reviewDecisionLabel[plan.review.decision]}}
{plan.name}
{plan.notes &&
{plan.notes}
}
-
-
+
{plan.number}{plan.statusLabel}{plan.review && {reviewDecisionLabel[plan.review.decision]}}
{plan.name}
{plan.notes &&
{plan.notes}
}
Escopo
{plan.orderCount} {plan.orderCount === 1 ? 'OP' : 'OPs'} · {formatNumber(plan.totalQuantity)} un.
Corte previsto pelo plano
Andamento
Cortado {formatNumber(plan.actualCutQuantity)} · final {formatNumber(plan.finishedQuantity)}
{statusOptions.filter(option => Number(plan.statusCounts[option.value] || 0) > 0).map(option => {plan.statusCounts[option.value]} {option.label.toLowerCase()})}
-
Prazo
{formatDate(plan.expectedDate)}
Início: {formatDate(plan.plannedStartDate)}
+
Responsável
{plan.contractor || 'Sem facção definida'}
+
Ações do plano
Revise a liberação antes de avançar a operação.
{responsiblePickerPlanId === plan.id &&
Atribuir responsável
Selecione um usuário ativo do Graphs para acompanhar este plano.
{isLoadingOperationalUsers ?
Carregando usuários...
: operationalUsers.map(user =>
)}{!isLoadingOperationalUsers && !operationalUsers.length &&
Nenhum usuário ativo encontrado. Cadastre ou reative um usuário para atribuir o plano.
}
{plan.responsibleName &&
}
}
{reviewingPlanId === plan.id &&
Liberação operacional
Registre a decisão antes de avançar o plano. Este registro não muda o estoque nem substitui o recebimento.
{[{ key: 'materialsReserved', label: 'Materiais reservados' }, { key: 'cutSpecificationReady', label: 'Ficha e corte definidos' }, { key: 'sizeAndColorReviewed', label: 'Tamanhos e cores revisados' }, { key: 'responsibleAndDateDefined', label: 'Responsável e data definidos' }].map(item => )}
}