Connect supplies to project demand
This commit is contained in:
@@ -1124,6 +1124,24 @@ const purchaseStatusLabels: Record<SupplyPurchaseNeed['status'], string> = {
|
||||
ok: 'Coberto',
|
||||
};
|
||||
|
||||
const getNeedUnit = (need: SupplyPurchaseNeed) => need.unit || 'kg';
|
||||
|
||||
const formatNeedQuantity = (need: SupplyPurchaseNeed, value: number) => (
|
||||
`${formatNumber(value)} ${getNeedUnit(need)}`
|
||||
);
|
||||
|
||||
const summarizePurchaseNeeds = (needs: SupplyPurchaseNeed[]) => {
|
||||
const totalsByUnit = needs.reduce<Record<string, number>>((totals, need) => {
|
||||
if (need.purchaseKg <= 0) return totals;
|
||||
const unit = getNeedUnit(need);
|
||||
totals[unit] = (totals[unit] || 0) + need.purchaseKg;
|
||||
return totals;
|
||||
}, {});
|
||||
|
||||
const summaries = Object.entries(totalsByUnit).map(([unit, total]) => `${formatNumber(total)} ${unit}`);
|
||||
return summaries.length ? summaries.join(' + ') : '0 kg';
|
||||
};
|
||||
|
||||
const PurchaseNeedsScreen = () => {
|
||||
const [summary, setSummary] = useState<SupplySummary>(emptySupplySummary);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -1159,17 +1177,19 @@ const PurchaseNeedsScreen = () => {
|
||||
!normalizedSearch || normalizeSearch(`${need.material} ${need.suppliers.join(' ')} ${need.colors.join(' ')}`).includes(normalizedSearch)
|
||||
));
|
||||
const purchaseItemCount = summary.purchaseNeeds.filter(need => need.purchaseKg > 0).length;
|
||||
const suggestedPurchaseKg = summary.purchaseNeeds.reduce((total, need) => total + need.purchaseKg, 0);
|
||||
const suggestedPurchaseSummary = summarizePurchaseNeeds(summary.purchaseNeeds);
|
||||
const pendingSupplierCount = new Set(summary.purchaseNeeds.flatMap(need => need.suppliers)).size;
|
||||
const missingReferenceCount = summary.purchaseNeeds.filter(need => need.missingReference).length;
|
||||
|
||||
return (
|
||||
<div className={pageClassName}>
|
||||
<Header title="Necessidade de Compra" subtitle="Materiais abaixo do mínimo e necessidade projetada para compra." backTo="/supplies" />
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
{[
|
||||
{ label: 'Itens a comprar', value: `${purchaseItemCount}` },
|
||||
{ label: 'Compra sugerida', value: `${formatNumber(suggestedPurchaseKg)} kg` },
|
||||
{ label: 'Fornecedores envolvidos', value: `${pendingSupplierCount}` },
|
||||
{ label: 'Compra sugerida', value: suggestedPurchaseSummary },
|
||||
{ label: 'Fornecedores', value: `${pendingSupplierCount}` },
|
||||
{ label: 'Sem referência', value: `${missingReferenceCount}` },
|
||||
].map(stat => (
|
||||
<div key={stat.label} className="rounded-2xl border border-dark-border bg-dark-card p-5 shadow-sm">
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">{stat.label}</p>
|
||||
@@ -1193,10 +1213,12 @@ const PurchaseNeedsScreen = () => {
|
||||
estoque_kg: need.stockKg,
|
||||
pendente_kg: need.pendingKg,
|
||||
comprar_kg: need.purchaseKg,
|
||||
unidade: getNeedUnit(need),
|
||||
prioridade: need.priority,
|
||||
cobertura: purchaseStatusLabels[need.status],
|
||||
cobertura: need.missingReference ? 'Sem referência de consumo' : purchaseStatusLabels[need.status],
|
||||
fornecedores: need.suppliers.join(' | '),
|
||||
cores: need.colors.join(' | '),
|
||||
produtos: (need.products || []).map(product => `${product.productId} ${product.name}`).join(' | '),
|
||||
})))}
|
||||
className={buttonClassName}
|
||||
>
|
||||
@@ -1229,21 +1251,31 @@ const PurchaseNeedsScreen = () => {
|
||||
<div>
|
||||
<p className="text-sm font-bold text-dark-text">{need.material}</p>
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||
{(need.colors.length ? need.colors.join(', ') : 'Todas as cores')} · {(need.suppliers.length ? need.suppliers.join(', ') : 'Sem fornecedor')}
|
||||
{need.missingReference
|
||||
? 'Cadastre produto/material em Cadastros > Referência de Consumo'
|
||||
: `${(need.colors.length ? need.colors.join(', ') : 'Todas as cores')} · ${(need.suppliers.length ? need.suppliers.join(', ') : 'Sem fornecedor')}`}
|
||||
</p>
|
||||
{need.products?.length ? (
|
||||
<p className="mt-1 text-xs font-semibold text-dark-muted">
|
||||
{need.products.slice(0, 2).map(product => product.productId).join(', ')}
|
||||
{need.products.length > 2 ? ` +${need.products.length - 2}` : ''}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNumber(need.plannedKg)} kg</p>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNumber(need.stockKg)} kg</p>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNumber(need.pendingKg)} kg</p>
|
||||
<p className={`text-sm font-bold ${need.purchaseKg > 0 ? 'text-red-300' : 'text-emerald-300'}`}>{formatNumber(need.purchaseKg)} kg</p>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.plannedKg)}</p>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.stockKg)}</p>
|
||||
<p className="text-sm font-bold text-dark-text">{formatNeedQuantity(need, need.pendingKg)}</p>
|
||||
<p className={`text-sm font-bold ${need.purchaseKg > 0 ? 'text-red-300' : 'text-emerald-300'}`}>{formatNeedQuantity(need, need.purchaseKg)}</p>
|
||||
<span className={`w-fit rounded-full border px-2.5 py-1 text-xs font-bold ${
|
||||
need.status === 'critical'
|
||||
need.missingReference
|
||||
? 'border-red-400/30 bg-red-400/10 text-red-300'
|
||||
: need.status === 'critical'
|
||||
? 'border-red-400/30 bg-red-400/10 text-red-300'
|
||||
: need.status === 'attention'
|
||||
? 'border-amber-400/30 bg-amber-400/10 text-amber-300'
|
||||
: 'border-emerald-400/30 bg-emerald-400/10 text-emerald-300'
|
||||
}`}>
|
||||
{purchaseStatusLabels[need.status]}
|
||||
{need.missingReference ? 'Sem referência' : purchaseStatusLabels[need.status]}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
11
src/types.ts
11
src/types.ts
@@ -232,6 +232,17 @@ export interface SupplyPurchaseNeed {
|
||||
status: 'critical' | 'attention' | 'ok';
|
||||
suppliers: string[];
|
||||
colors: string[];
|
||||
unit?: string;
|
||||
source?: string;
|
||||
missingReference?: boolean;
|
||||
products?: Array<{
|
||||
productId: string;
|
||||
name: string;
|
||||
suggestedQuantity: number;
|
||||
quantitySold: number;
|
||||
stockQuantity: number;
|
||||
yieldPerKg?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SupplyStats {
|
||||
|
||||
Reference in New Issue
Block a user