Focus stock priorities on actionable diagnosis
This commit is contained in:
@@ -135,17 +135,17 @@ type DemandRow = ProductAnalyticsItem & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const demandQueueLabels: Record<DemandQueue, string> = {
|
const demandQueueLabels: Record<DemandQueue, string> = {
|
||||||
apparel: 'Cortar / Repor',
|
apparel: 'Repor produtos',
|
||||||
accessories: 'Repor acessórios',
|
accessories: 'Repor acessórios',
|
||||||
inputs: 'Comprar insumos',
|
inputs: 'Insumos a comprar',
|
||||||
review: 'Revisar dados',
|
review: 'Revisar dados',
|
||||||
dead: 'Estoque parado',
|
dead: 'Estoque parado',
|
||||||
};
|
};
|
||||||
|
|
||||||
const demandQueueDescriptions: Record<DemandQueue, string> = {
|
const demandQueueDescriptions: Record<DemandQueue, string> = {
|
||||||
apparel: 'Produtos acabados com demanda ativa e cobertura abaixo da meta.',
|
apparel: 'Diagnóstico de reposição de produtos acabados; o corte é definido no Plano de Corte.',
|
||||||
accessories: 'Acessórios acabados com demanda ativa e cobertura abaixo da meta.',
|
accessories: 'Acessórios acabados com demanda ativa e cobertura abaixo da meta.',
|
||||||
inputs: 'Embalagens, matérias-primas e insumos de produção com necessidade de reposição.',
|
inputs: 'Embalagens, matérias-primas e insumos com necessidade comercial de reposição.',
|
||||||
review: 'SKUs com informações pendentes para qualificar o planejamento.',
|
review: 'SKUs com informações pendentes para qualificar o planejamento.',
|
||||||
dead: 'Itens com saldo disponível e baixa movimentação no período.',
|
dead: 'Itens com saldo disponível e baixa movimentação no período.',
|
||||||
};
|
};
|
||||||
@@ -600,7 +600,7 @@ const DemandPlanningScreen = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={pageClassName}>
|
<div className={pageClassName}>
|
||||||
<div className="grid grid-cols-1 gap-4 2xl:grid-cols-[minmax(520px,1fr)_auto] 2xl:items-start">
|
<div className="grid grid-cols-1 gap-4 2xl:grid-cols-[minmax(520px,1fr)_auto] 2xl:items-start">
|
||||||
<Header title="Planejamento Operacional" subtitle="Prioridades de reposição, compra e revisão calculadas por demanda, estoque e cobertura." backTo="/supplies" />
|
<Header title="Prioridades de Estoque" subtitle="Diagnóstico de reposição, compra e dados. O Plano de Corte continua sendo a decisão de produção." backTo="/supplies" />
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
onChange={(range) => {
|
onChange={(range) => {
|
||||||
@@ -712,41 +712,37 @@ const DemandPlanningScreen = () => {
|
|||||||
) : queueRows.length ? (
|
) : queueRows.length ? (
|
||||||
<div className="overflow-hidden rounded-2xl border border-dark-border bg-dark-card shadow-sm">
|
<div className="overflow-hidden rounded-2xl border border-dark-border bg-dark-card shadow-sm">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full min-w-[1080px] table-fixed border-collapse">
|
<table className="w-full min-w-[860px] table-fixed border-collapse">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col className="w-[120px]" />
|
|
||||||
<col />
|
<col />
|
||||||
<col className="w-[140px]" />
|
|
||||||
<col className="w-[120px]" />
|
<col className="w-[120px]" />
|
||||||
<col className="w-[120px]" />
|
<col className="w-[120px]" />
|
||||||
<col className="w-[120px]" />
|
<col className="w-[120px]" />
|
||||||
<col className="w-[130px]" />
|
<col className="w-[130px]" />
|
||||||
<col className="w-[100px]" />
|
<col className="w-[140px]" />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead className="border-b border-dark-border bg-dark-header text-xs font-bold uppercase tracking-widest text-dark-muted">
|
<thead className="border-b border-dark-border bg-dark-header text-xs font-bold uppercase tracking-widest text-dark-muted">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" className="px-4 py-3 text-left">SKU</th>
|
|
||||||
<th scope="col" className="px-4 py-3 text-left">Produto</th>
|
<th scope="col" className="px-4 py-3 text-left">Produto</th>
|
||||||
<th scope="col" className="px-4 py-3 text-left">Tipo</th>
|
|
||||||
<th scope="col" className="px-4 py-3 text-right">Média/dia</th>
|
<th scope="col" className="px-4 py-3 text-right">Média/dia</th>
|
||||||
<th scope="col" className="px-4 py-3 text-right">Estoque</th>
|
<th scope="col" className="px-4 py-3 text-right">Estoque</th>
|
||||||
<th scope="col" className="px-4 py-3 text-right">Cobertura</th>
|
<th scope="col" className="px-4 py-3 text-right">Cobertura</th>
|
||||||
<th scope="col" className="px-4 py-3 text-right">{queue === 'dead' ? 'Valor estoque' : 'Sugestão'}</th>
|
<th scope="col" className="px-4 py-3 text-right">{queue === 'dead' ? 'Valor estoque' : 'Sugestão'}</th>
|
||||||
<th scope="col" className="px-4 py-3 text-right">Ações</th>
|
<th scope="col" className="px-4 py-3 text-right">Próximo passo</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y divide-dark-border">
|
<tbody className="divide-y divide-dark-border">
|
||||||
{paginatedRows.map(row => (
|
{paginatedRows.map(row => (
|
||||||
<tr key={row.id} className="transition-colors hover:bg-dark-input/50">
|
<tr key={row.id} className="transition-colors hover:bg-dark-input/50">
|
||||||
<td className="px-4 py-2.5 font-mono text-[11px] text-dark-muted">#{row.id}</td>
|
|
||||||
<td className="px-4 py-2.5">
|
<td className="px-4 py-2.5">
|
||||||
<p className="truncate text-sm font-bold text-dark-text" title={row.name}>{row.name}</p>
|
<p className="truncate text-sm font-bold text-dark-text" title={row.name}>{row.name}</p>
|
||||||
<p className="mt-1 truncate text-xs font-semibold text-dark-muted">
|
<div className="mt-1 flex flex-wrap items-center gap-1.5">
|
||||||
Cor: {row.color || '-'} · Tam.: {row.size || '-'}
|
<span className="font-mono text-[10px] font-semibold text-dark-muted">#{row.id}</span>
|
||||||
|
<ProductTypeBadge type={row.productType} />
|
||||||
|
<span className="text-xs font-semibold text-dark-muted">Cor: {row.color || '-'} · Tam.: {row.size || '-'}</span>
|
||||||
{row.missingData.length ? ` · Falta: ${row.missingData.join(', ')}` : ''}
|
{row.missingData.length ? ` · Falta: ${row.missingData.join(', ')}` : ''}
|
||||||
</p>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2.5"><ProductTypeBadge type={row.productType} /></td>
|
|
||||||
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatNumber(row.dailySales, 2)}</td>
|
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatNumber(row.dailySales, 2)}</td>
|
||||||
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatNumber(row.stock, 0)} un.</td>
|
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatNumber(row.stock, 0)} un.</td>
|
||||||
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatDays(row.daysOfCover)}</td>
|
<td className="px-4 py-2.5 text-right text-sm font-bold text-dark-text">{formatDays(row.daysOfCover)}</td>
|
||||||
@@ -757,24 +753,42 @@ const DemandPlanningScreen = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2.5 text-right">
|
<td className="px-4 py-2.5 text-right">
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2">
|
||||||
|
{queue === 'apparel' && (
|
||||||
|
<RouterLink
|
||||||
|
to="/cutting"
|
||||||
|
className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-brand-primary/10 px-2.5 text-xs font-bold text-brand-primary transition-opacity hover:opacity-80"
|
||||||
|
title="Abrir Plano de Corte"
|
||||||
|
>
|
||||||
|
<Scissors className="h-3.5 w-3.5" /> Corte
|
||||||
|
</RouterLink>
|
||||||
|
)}
|
||||||
|
{queue === 'inputs' && (
|
||||||
|
<RouterLink
|
||||||
|
to="/supplies/purchase-needs?tab=materials&filter=to_buy"
|
||||||
|
className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-brand-primary/10 px-2.5 text-xs font-bold text-brand-primary transition-opacity hover:opacity-80"
|
||||||
|
title="Abrir Necessidade de Compra"
|
||||||
|
>
|
||||||
|
<BarChart3 className="h-3.5 w-3.5" /> Compra
|
||||||
|
</RouterLink>
|
||||||
|
)}
|
||||||
{queue === 'review' && (
|
{queue === 'review' && (
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/planning-issues"
|
to="/planning-issues"
|
||||||
className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-dark-input text-dark-text transition-colors hover:bg-dark-border"
|
className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-dark-input px-2.5 text-xs font-bold text-dark-text transition-colors hover:bg-dark-border"
|
||||||
title="Abrir dados pendentes"
|
title="Abrir dados pendentes"
|
||||||
aria-label="Abrir dados pendentes"
|
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" /> Corrigir
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
)}
|
)}
|
||||||
|
{queue !== 'apparel' && queue !== 'inputs' && queue !== 'review' && (
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to={`/products/${row.id}`}
|
to={`/products/${row.id}`}
|
||||||
className="inline-flex h-8 w-8 items-center justify-center rounded-lg bg-brand-primary/10 text-brand-primary transition-opacity hover:opacity-80"
|
className="inline-flex h-8 items-center gap-1.5 rounded-lg bg-brand-primary/10 px-2.5 text-xs font-bold text-brand-primary transition-opacity hover:opacity-80"
|
||||||
title={`Ver SKU ${row.id}`}
|
title={`Ver SKU ${row.id}`}
|
||||||
aria-label={`Ver SKU ${row.id}`}
|
|
||||||
>
|
>
|
||||||
<ArrowRight className="h-3.5 w-3.5" />
|
Ver produto <ArrowRight className="h-3.5 w-3.5" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user