feat: adicionar somatorias de peso nos 4 cards de KPIs do mapa termico de kits
This commit is contained in:
@@ -120,6 +120,12 @@ export const MapaTermicoKitsView: React.FC<MapaTermicoKitsViewProps> = ({
|
||||
});
|
||||
}, [listaKits, selectedFase, filtroStatus, busca]);
|
||||
|
||||
// Formatação de pesos em kg
|
||||
const formatarKg = (valorKg: number) => {
|
||||
if (!valorKg || valorKg <= 0) return '0 kg';
|
||||
return `${valorKg.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })} kg`;
|
||||
};
|
||||
|
||||
// KPIs calculados com base nas peças filtradas
|
||||
const resumoExibido = useMemo(() => {
|
||||
let total100 = 0;
|
||||
@@ -127,18 +133,30 @@ export const MapaTermicoKitsView: React.FC<MapaTermicoKitsViewProps> = ({
|
||||
let totalTravados = 0;
|
||||
let pesoTot = 0;
|
||||
let pesoCort = 0;
|
||||
let peso100 = 0;
|
||||
let pesoUnLiberadas = 0;
|
||||
let pesoParciais = 0;
|
||||
let pesoNaoIniciados = 0;
|
||||
let unTot = 0;
|
||||
let unLib = 0;
|
||||
|
||||
pecasFiltradas.forEach((item) => {
|
||||
unTot += item.quantidade_total;
|
||||
unLib += item.kits_completos_cortados;
|
||||
pesoTot += item.peso_total_componentes;
|
||||
pesoTot += item.peso_total_peca;
|
||||
pesoCort += item.peso_cortado_componentes;
|
||||
pesoUnLiberadas += item.peso_kits_liberados;
|
||||
|
||||
if (item.status_termico === 'verde') total100++;
|
||||
else if (item.status_termico === 'verde_parcial' || item.status_termico === 'amarelo') totalParciais++;
|
||||
else totalTravados++;
|
||||
if (item.status_termico === 'verde') {
|
||||
total100++;
|
||||
peso100 += item.peso_total_peca;
|
||||
} else if (item.status_termico === 'verde_parcial' || item.status_termico === 'amarelo') {
|
||||
totalParciais++;
|
||||
pesoParciais += item.peso_total_peca;
|
||||
} else {
|
||||
totalTravados++;
|
||||
pesoNaoIniciados += item.peso_total_peca;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -148,6 +166,10 @@ export const MapaTermicoKitsView: React.FC<MapaTermicoKitsViewProps> = ({
|
||||
totalKitsNaoIniciados: totalTravados,
|
||||
pesoTotalOFKg: pesoTot,
|
||||
pesoCortadoTotalKg: pesoCort,
|
||||
peso100PorcentoKg: peso100,
|
||||
pesoUnidadesLiberadasKg: pesoUnLiberadas,
|
||||
pesoParciaisKg: pesoParciais,
|
||||
pesoNaoIniciadosKg: pesoNaoIniciados,
|
||||
percentualGeralOF: pesoTot > 0 ? Math.min(100, Math.round((pesoCort / pesoTot) * 100)) : 0,
|
||||
totalUnidadesTotal: unTot,
|
||||
totalUnidadesLiberadasSolda: unLib,
|
||||
@@ -268,72 +290,92 @@ export const MapaTermicoKitsView: React.FC<MapaTermicoKitsViewProps> = ({
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2.5 sm:gap-3">
|
||||
{/* KPI 1: 100% Liberadas */}
|
||||
<div className="p-3.5 rounded-2xl bg-emerald-50/80 dark:bg-emerald-950/40 border border-emerald-200 dark:border-emerald-800 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-black uppercase text-emerald-700 dark:text-emerald-400">
|
||||
100% Liberadas
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-[11px] font-black uppercase text-emerald-700 dark:text-emerald-400">
|
||||
100% Liberadas
|
||||
</span>
|
||||
<span className="text-[11px] font-black text-emerald-800 dark:text-emerald-300 bg-emerald-200/80 dark:bg-emerald-900/70 px-1.5 py-0.5 rounded-md border border-emerald-300/80 dark:border-emerald-700 shadow-2xs">
|
||||
{formatarKg(resumoExibido.peso100PorcentoKg)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black text-emerald-900 dark:text-emerald-100 mt-0.5">
|
||||
<div className="text-2xl font-black text-emerald-900 dark:text-emerald-100 mt-1">
|
||||
{resumoExibido.totalKits100Porcento}
|
||||
</div>
|
||||
<div className="text-[10px] text-emerald-700/80 dark:text-emerald-400/80 font-medium">
|
||||
<div className="text-[10px] text-emerald-700/80 dark:text-emerald-400/80 font-medium truncate">
|
||||
peças com todos os kits cortados
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-xl bg-emerald-100 text-emerald-700 dark:bg-emerald-900/60 dark:text-emerald-300">
|
||||
<div className="p-2.5 rounded-xl bg-emerald-100 text-emerald-700 dark:bg-emerald-900/60 dark:text-emerald-300 shrink-0 ml-2">
|
||||
<CheckCircle2 className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI 2: Kits Montáveis Liberados */}
|
||||
<div className="p-3.5 rounded-2xl bg-blue-50/80 dark:bg-blue-950/40 border border-blue-200 dark:border-blue-800 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-black uppercase text-blue-700 dark:text-blue-400">
|
||||
Unidades Liberadas
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-[11px] font-black uppercase text-blue-700 dark:text-blue-400">
|
||||
Unidades Liberadas
|
||||
</span>
|
||||
<span className="text-[11px] font-black text-blue-800 dark:text-blue-300 bg-blue-200/80 dark:bg-blue-900/70 px-1.5 py-0.5 rounded-md border border-blue-300/80 dark:border-blue-700 shadow-2xs">
|
||||
{formatarKg(resumoExibido.pesoUnidadesLiberadasKg)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black text-blue-900 dark:text-blue-100 mt-0.5">
|
||||
<div className="text-2xl font-black text-blue-900 dark:text-blue-100 mt-1">
|
||||
{resumoExibido.totalUnidadesLiberadasSolda} <span className="text-xs text-blue-600 font-bold">/ {resumoExibido.totalUnidadesTotal} un</span>
|
||||
</div>
|
||||
<div className="text-[10px] text-blue-700/80 dark:text-blue-400/80 font-medium">
|
||||
<div className="text-[10px] text-blue-700/80 dark:text-blue-400/80 font-medium truncate">
|
||||
kits prontos na bancada de solda
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-xl bg-blue-100 text-blue-700 dark:bg-blue-900/60 dark:text-blue-300">
|
||||
<div className="p-2.5 rounded-xl bg-blue-100 text-blue-700 dark:bg-blue-900/60 dark:text-blue-300 shrink-0 ml-2">
|
||||
<PackageCheck className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI 3: Em Andamento / Quase Prontos */}
|
||||
<div className="p-3.5 rounded-2xl bg-amber-50/80 dark:bg-amber-950/40 border border-amber-200 dark:border-amber-800 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-black uppercase text-amber-700 dark:text-amber-400">
|
||||
Corte em Andamento
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-[11px] font-black uppercase text-amber-700 dark:text-amber-400">
|
||||
Corte em Andamento
|
||||
</span>
|
||||
<span className="text-[11px] font-black text-amber-800 dark:text-amber-300 bg-amber-200/80 dark:bg-amber-900/70 px-1.5 py-0.5 rounded-md border border-amber-300/80 dark:border-amber-700 shadow-2xs">
|
||||
{formatarKg(resumoExibido.pesoParciaisKg)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black text-amber-900 dark:text-amber-100 mt-0.5">
|
||||
<div className="text-2xl font-black text-amber-900 dark:text-amber-100 mt-1">
|
||||
{resumoExibido.totalKitsParciais}
|
||||
</div>
|
||||
<div className="text-[10px] text-amber-700/80 dark:text-amber-400/80 font-medium">
|
||||
<div className="text-[10px] text-amber-700/80 dark:text-amber-400/80 font-medium truncate">
|
||||
kits parciais (prioridade de corte)
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-xl bg-amber-100 text-amber-700 dark:bg-amber-900/60 dark:text-amber-300">
|
||||
<div className="p-2.5 rounded-xl bg-amber-100 text-amber-700 dark:bg-amber-900/60 dark:text-amber-300 shrink-0 ml-2">
|
||||
<Clock className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* KPI 4: Travados / 0% */}
|
||||
<div className="p-3.5 rounded-2xl bg-rose-50/80 dark:bg-rose-950/40 border border-rose-200 dark:border-rose-800 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<div className="text-[11px] font-black uppercase text-rose-700 dark:text-rose-400">
|
||||
Não Iniciados
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-1.5 flex-wrap">
|
||||
<span className="text-[11px] font-black uppercase text-rose-700 dark:text-rose-400">
|
||||
Não Iniciados
|
||||
</span>
|
||||
<span className="text-[11px] font-black text-rose-800 dark:text-rose-300 bg-rose-200/80 dark:bg-rose-900/70 px-1.5 py-0.5 rounded-md border border-rose-300/80 dark:border-rose-700 shadow-2xs">
|
||||
{formatarKg(resumoExibido.pesoNaoIniciadosKg)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-2xl font-black text-rose-900 dark:text-rose-100 mt-0.5">
|
||||
<div className="text-2xl font-black text-rose-900 dark:text-rose-100 mt-1">
|
||||
{resumoExibido.totalKitsNaoIniciados}
|
||||
</div>
|
||||
<div className="text-[10px] text-rose-700/80 dark:text-rose-400/80 font-medium">
|
||||
<div className="text-[10px] text-rose-700/80 dark:text-rose-400/80 font-medium truncate">
|
||||
sem nenhum componente cortado
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-2.5 rounded-xl bg-rose-100 text-rose-700 dark:bg-rose-900/60 dark:text-rose-300">
|
||||
<div className="p-2.5 rounded-xl bg-rose-100 text-rose-700 dark:bg-rose-900/60 dark:text-rose-300 shrink-0 ml-2">
|
||||
<AlertTriangle className="w-6 h-6" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,8 +527,13 @@ export const MapaTermicoKitsView: React.FC<MapaTermicoKitsViewProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 line-clamp-1">
|
||||
{peca.descricao || 'Peça Estrutural'}
|
||||
<div className="text-xs text-slate-500 dark:text-slate-400 flex items-center gap-1.5 flex-wrap">
|
||||
<span className="line-clamp-1">{peca.descricao || 'Peça Estrutural'}</span>
|
||||
{peca.peso_unitario > 0 && (
|
||||
<span className="font-semibold text-slate-600 dark:text-slate-400 whitespace-nowrap">
|
||||
• {peca.peso_unitario.toFixed(1)} kg/un (Total: {peca.peso_total_peca.toFixed(1)} kg)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,9 @@ export interface PecaKitInfo {
|
||||
quantidade_total: number;
|
||||
etapa_fase: string;
|
||||
tem_componentes: boolean;
|
||||
peso_unitario: number;
|
||||
peso_total_peca: number;
|
||||
peso_kits_liberados: number;
|
||||
componentes: ComponenteKitInfo[];
|
||||
kits_completos_cortados: number; // Quantidade de kits 100% montáveis liberados
|
||||
percentual_termico: number; // 0 a 100%
|
||||
@@ -46,6 +49,10 @@ export interface ResumoKitsOF {
|
||||
totalKitsNaoIniciados: number;
|
||||
pesoTotalOFKg: number;
|
||||
pesoCortadoTotalKg: number;
|
||||
peso100PorcentoKg: number;
|
||||
pesoUnidadesLiberadasKg: number;
|
||||
pesoParciaisKg: number;
|
||||
pesoNaoIniciadosKg: number;
|
||||
percentualGeralOF: number;
|
||||
totalUnidadesTotal: number;
|
||||
totalUnidadesLiberadasSolda: number;
|
||||
@@ -64,6 +71,8 @@ export const calcularKitsOF = (
|
||||
quantidade: number;
|
||||
etapa_fase?: string | null;
|
||||
tem_componentes?: boolean | null;
|
||||
peso_unitario?: number | null;
|
||||
peso_total?: number | null;
|
||||
}>,
|
||||
componentes: Array<{
|
||||
id: string;
|
||||
@@ -153,12 +162,17 @@ export const calcularKitsOF = (
|
||||
const temComps = peca.tem_componentes !== false && compsRaw.length > 0;
|
||||
const jaApontadoSolda = apontadoNaSoldaPorPeca.get(peca.id) || 0;
|
||||
|
||||
const pesoUnitPeca = Number(peca.peso_unitario) || (qtdTotalPeca > 0 && peca.peso_total ? Number(peca.peso_total) / qtdTotalPeca : 0);
|
||||
const pesoTotalPeca = Number(peca.peso_total) || (qtdTotalPeca * pesoUnitPeca);
|
||||
|
||||
// Cenário A: Peça sem componentes (monobloco / S/M)
|
||||
if (!temComps) {
|
||||
const qtdCortada = cortadoPecaNoCorte.get(peca.id) || 0;
|
||||
const kitsCompletos = Math.min(qtdTotalPeca, qtdCortada);
|
||||
const pct = qtdTotalPeca > 0 ? Math.min(100, Math.round((kitsCompletos / qtdTotalPeca) * 100)) : 0;
|
||||
const saldoSolda = Math.max(0, kitsCompletos - jaApontadoSolda);
|
||||
const pesoCortado = kitsCompletos * pesoUnitPeca;
|
||||
const pesoLiberado = kitsCompletos * pesoUnitPeca;
|
||||
|
||||
mapaKits.set(peca.id, {
|
||||
peca_id: peca.id,
|
||||
@@ -168,6 +182,9 @@ export const calcularKitsOF = (
|
||||
quantidade_total: qtdTotalPeca,
|
||||
etapa_fase: fase,
|
||||
tem_componentes: false,
|
||||
peso_unitario: pesoUnitPeca,
|
||||
peso_total_peca: pesoTotalPeca,
|
||||
peso_kits_liberados: pesoLiberado,
|
||||
componentes: [],
|
||||
kits_completos_cortados: kitsCompletos,
|
||||
percentual_termico: pct,
|
||||
@@ -180,8 +197,8 @@ export const calcularKitsOF = (
|
||||
? 'amarelo'
|
||||
: 'vermelho',
|
||||
componente_gargalo: null,
|
||||
peso_total_componentes: 0,
|
||||
peso_cortado_componentes: 0,
|
||||
peso_total_componentes: pesoTotalPeca,
|
||||
peso_cortado_componentes: pesoCortado,
|
||||
ja_apontado_solda: jaApontadoSolda,
|
||||
saldo_disponivel_solda: peca.tem_componentes === false ? 0 : saldoSolda,
|
||||
bloqueado_para_solda: peca.tem_componentes === false || saldoSolda <= 0,
|
||||
@@ -293,6 +310,11 @@ export const calcularKitsOF = (
|
||||
}
|
||||
}
|
||||
|
||||
const pesoUnitComps = pesoTotalComps > 0 && qtdTotalPeca > 0 ? pesoTotalComps / qtdTotalPeca : 0;
|
||||
const pesoUnitPecaFinal = Number(peca.peso_unitario) || pesoUnitComps;
|
||||
const pesoTotalPecaFinal = pesoTotalComps > 0 ? pesoTotalComps : (Number(peca.peso_total) || (qtdTotalPeca * pesoUnitPecaFinal));
|
||||
const pesoLiberadoFinal = kitsCompletos * pesoUnitPecaFinal;
|
||||
|
||||
mapaKits.set(peca.id, {
|
||||
peca_id: peca.id,
|
||||
marca: peca.marca,
|
||||
@@ -301,12 +323,15 @@ export const calcularKitsOF = (
|
||||
quantidade_total: qtdTotalPeca,
|
||||
etapa_fase: fase,
|
||||
tem_componentes: true,
|
||||
peso_unitario: pesoUnitPecaFinal,
|
||||
peso_total_peca: pesoTotalPecaFinal,
|
||||
peso_kits_liberados: pesoLiberadoFinal,
|
||||
componentes: listaCompsInfo,
|
||||
kits_completos_cortados: kitsCompletos,
|
||||
percentual_termico: percentualTermico,
|
||||
status_termico: statusTermico,
|
||||
componente_gargalo: gargaloComp,
|
||||
peso_total_componentes: pesoTotalComps,
|
||||
peso_total_componentes: pesoTotalPecaFinal,
|
||||
peso_cortado_componentes: pesoCortadoComps,
|
||||
ja_apontado_solda: jaApontadoSolda,
|
||||
saldo_disponivel_solda: saldoSolda,
|
||||
@@ -345,7 +370,7 @@ export const useKitsProducao = (ofNumber?: string, fase?: string) => {
|
||||
// 2. Buscar peças da OF
|
||||
let pecasQuery = supabase
|
||||
.from('pecas')
|
||||
.select('id, marca, descricao, perfil_principal, quantidade, etapa_fase, tem_componentes')
|
||||
.select('id, marca, descricao, perfil_principal, quantidade, etapa_fase, tem_componentes, peso_unitario, peso_total')
|
||||
.eq('of_number', ofNumber);
|
||||
|
||||
if (fase && fase !== 'all' && fase !== 'Todas as fases' && fase !== 'todas') {
|
||||
@@ -411,18 +436,30 @@ export const useKitsProducao = (ofNumber?: string, fase?: string) => {
|
||||
let totalTravados = 0;
|
||||
let pesoTot = 0;
|
||||
let pesoCort = 0;
|
||||
let peso100 = 0;
|
||||
let pesoUnLiberadas = 0;
|
||||
let pesoParciais = 0;
|
||||
let pesoNaoIniciados = 0;
|
||||
let unTot = 0;
|
||||
let unLib = 0;
|
||||
|
||||
lista.forEach((item) => {
|
||||
unTot += item.quantidade_total;
|
||||
unLib += item.kits_completos_cortados;
|
||||
pesoTot += item.peso_total_componentes;
|
||||
pesoTot += item.peso_total_peca;
|
||||
pesoCort += item.peso_cortado_componentes;
|
||||
pesoUnLiberadas += item.peso_kits_liberados;
|
||||
|
||||
if (item.status_termico === 'verde') total100++;
|
||||
else if (item.status_termico === 'verde_parcial' || item.status_termico === 'amarelo') totalParciais++;
|
||||
else totalTravados++;
|
||||
if (item.status_termico === 'verde') {
|
||||
total100++;
|
||||
peso100 += item.peso_total_peca;
|
||||
} else if (item.status_termico === 'verde_parcial' || item.status_termico === 'amarelo') {
|
||||
totalParciais++;
|
||||
pesoParciais += item.peso_total_peca;
|
||||
} else {
|
||||
totalTravados++;
|
||||
pesoNaoIniciados += item.peso_total_peca;
|
||||
}
|
||||
});
|
||||
|
||||
const resumo: ResumoKitsOF = {
|
||||
@@ -432,6 +469,10 @@ export const useKitsProducao = (ofNumber?: string, fase?: string) => {
|
||||
totalKitsNaoIniciados: totalTravados,
|
||||
pesoTotalOFKg: pesoTot,
|
||||
pesoCortadoTotalKg: pesoCort,
|
||||
peso100PorcentoKg: peso100,
|
||||
pesoUnidadesLiberadasKg: pesoUnLiberadas,
|
||||
pesoParciaisKg: pesoParciais,
|
||||
pesoNaoIniciadosKg: pesoNaoIniciados,
|
||||
percentualGeralOF: pesoTot > 0 ? Math.round((pesoCortadoTotalKg(pesoTot, pesoCort)) * 100) : 0,
|
||||
totalUnidadesTotal: unTot,
|
||||
totalUnidadesLiberadasSolda: unLib,
|
||||
|
||||
Reference in New Issue
Block a user