feat: filtro exclusivo por marca de peca e controle dimensional de comprimento no romaneio

This commit is contained in:
2026-09-22 20:53:07 +00:00
parent c07bb81cd2
commit 816f406800
14 changed files with 284 additions and 67 deletions
@@ -59,11 +59,11 @@ export const ApontamentosFilters: React.FC<ApontamentosFiltersProps> = ({
{/* Primeira linha de filtros - mais compacta */} {/* Primeira linha de filtros - mais compacta */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-2"> <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-2">
<div className="space-y-1"> <div className="space-y-1">
<label className="text-xs font-medium">Buscar</label> <label className="text-xs font-medium">Buscar Marca</label>
<div className="relative"> <div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" /> <Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" />
<Input <Input
placeholder="OF, Marca..." placeholder="Marca da peça..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="pl-7 h-8 text-xs" className="pl-7 h-8 text-xs"
@@ -143,18 +143,17 @@ export const useApontamentosFilters = (apontamentos: ApontamentoProducao[]) => {
let resultado = [...apontamentos]; // Começar com TODOS os apontamentos let resultado = [...apontamentos]; // Começar com TODOS os apontamentos
// Aplicar filtro de busca textual APENAS se preenchido // Aplicar filtro de busca por MARCA DA PEÇA apenas se preenchido
if (searchTerm && searchTerm.trim() !== '') { if (searchTerm && searchTerm.trim() !== '') {
const searchLower = searchTerm.toLowerCase(); const searchLower = searchTerm.toLowerCase().trim();
const beforeSearch = resultado.length; const beforeSearch = resultado.length;
resultado = resultado.filter(apt => resultado = resultado.filter(apt =>
(apt.of_number && apt.of_number.toLowerCase().includes(searchLower)) ||
(apt.peca?.marca && apt.peca.marca.toLowerCase().includes(searchLower)) || (apt.peca?.marca && apt.peca.marca.toLowerCase().includes(searchLower)) ||
(apt.componente?.marca_componente && apt.componente.marca_componente.toLowerCase().includes(searchLower)) (apt.componente?.marca_componente && apt.componente.marca_componente.toLowerCase().includes(searchLower))
); );
console.log(`🔎 Filtro texto "${searchTerm}": ${beforeSearch} → ${resultado.length} registros`); console.log(`🔎 Filtro marca "${searchTerm}": ${beforeSearch} → ${resultado.length} registros`);
} }
// Aplicar filtro por OF APENAS se selecionado // Aplicar filtro por OF APENAS se selecionado
+120 -48
View File
@@ -10,7 +10,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Package, Plus, Wrench, Expand } from 'lucide-react'; import { Package, Plus, Wrench, Expand, AlertTriangle } from 'lucide-react';
import { RomaneioExpedicao } from '@/hooks/useRomaneios'; import { RomaneioExpedicao } from '@/hooks/useRomaneios';
import { useProcessosFabricacao } from '@/hooks/useProcessosFabricacao'; import { useProcessosFabricacao } from '@/hooks/useProcessosFabricacao';
import { usePecas } from '@/hooks/usePecas'; import { usePecas } from '@/hooks/usePecas';
@@ -31,6 +31,7 @@ interface PecaDisponivel {
etapa_fase: string; etapa_fase: string;
quantidade_disponivel: number; quantidade_disponivel: number;
peso_unitario: number; peso_unitario: number;
comprimento?: number | null;
prioridade: string; prioridade: string;
} }
@@ -40,6 +41,7 @@ interface ItemRomaneioPeca {
quantidade: number; quantidade: number;
peso_unitario: number; peso_unitario: number;
peso_total: number; peso_total: number;
comprimento?: number | null;
marca: string; marca: string;
descricao: string; descricao: string;
etapa_fase: string; etapa_fase: string;
@@ -111,6 +113,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
descricao, descricao,
etapa_fase, etapa_fase,
peso_unitario, peso_unitario,
comprimento,
prioridade, prioridade,
of_number of_number
) )
@@ -165,6 +168,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
etapa_fase: apontamento.peca.etapa_fase || '', etapa_fase: apontamento.peca.etapa_fase || '',
quantidade_disponivel: quantidade, // Bruto temporário quantidade_disponivel: quantidade, // Bruto temporário
peso_unitario: apontamento.peca.peso_unitario || 0, peso_unitario: apontamento.peca.peso_unitario || 0,
comprimento: apontamento.peca.comprimento || null,
prioridade: apontamento.peca.prioridade || 'P4' prioridade: apontamento.peca.prioridade || 'P4'
}); });
} }
@@ -210,7 +214,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
.from('itens_romaneio_pecas') .from('itens_romaneio_pecas')
.select(` .select(`
*, *,
pecas!inner(marca, descricao, etapa_fase, peso_unitario, prioridade) pecas!inner(marca, descricao, etapa_fase, peso_unitario, comprimento, prioridade)
`) `)
.eq('romaneio_id', romaneio.id); .eq('romaneio_id', romaneio.id);
@@ -224,6 +228,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
quantidade: item.quantidade_expedida, quantidade: item.quantidade_expedida,
peso_unitario: item.peso_unitario, peso_unitario: item.peso_unitario,
peso_total: item.peso_total, peso_total: item.peso_total,
comprimento: item.pecas?.comprimento || item.comprimento || null,
marca: item.marca, marca: item.marca,
descricao: item.descricao || '', descricao: item.descricao || '',
etapa_fase: item.fase || '', etapa_fase: item.fase || '',
@@ -308,7 +313,26 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
}, [pecasDisponiveis, faseFilter, marcaFilter]); }, [pecasDisponiveis, faseFilter, marcaFilter]);
const handleAdicionarPeca = async () => { const handleAdicionarPeca = async () => {
const limiteVeiculo = romaneio.comprimento_maximo_veiculo || 12000;
if (adicionarTodas) { if (adicionarTodas) {
// Verificar se há peças que excedem a capacidade do veículo
const pecasExcedentes = pecasFiltradas.filter(p => p.comprimento && p.comprimento > limiteVeiculo);
if (pecasExcedentes.length > 0) {
const listaMarcas = pecasExcedentes.map(p => `${p.marca} (${p.comprimento}mm)`).join(', ');
const confirmar = window.confirm(
`⚠️ ALERTA DE CAPACIDADE DO VEÍCULO:\n\n` +
`Atenção: ${pecasExcedentes.length} peça(s) excedem o limite de ${limiteVeiculo} mm (${(limiteVeiculo / 1000).toFixed(1)}m) do veículo:\n` +
`${listaMarcas}\n\n` +
`Essas peças são maiores que o espaço de carga do veículo selecionado.\n\n` +
`Deseja realmente incluí-las no romaneio?`
);
if (!confirmar) {
toast.warning('Operação cancelada para evitar carregar peças que não cabem no caminhão.');
return;
}
}
// Adicionar todas as peças filtradas com suas quantidades disponíveis // Adicionar todas as peças filtradas com suas quantidades disponíveis
for (const peca of pecasFiltradas) { for (const peca of pecasFiltradas) {
try { try {
@@ -345,6 +369,21 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
return; return;
} }
// Validação de comprimento do veículo
if (pecaSelecionada.comprimento && pecaSelecionada.comprimento > limiteVeiculo) {
const confirmar = window.confirm(
`⚠️ ALERTA DE CAPACIDADE DO VEÍCULO:\n\n` +
`A peça "${pecaSelecionada.marca}" tem comprimento de ${pecaSelecionada.comprimento} mm (${(pecaSelecionada.comprimento / 1000).toFixed(1)}m), ` +
`que ULTRAPASSA o comprimento máximo suportado pelo veículo (${limiteVeiculo} mm / ${(limiteVeiculo / 1000).toFixed(1)}m)!\n\n` +
`Essa peça NÃO caberá na carroceria do transporte selecionado.\n\n` +
`Deseja realmente adicionar esta peça mesmo com essa advertência?`
);
if (!confirmar) {
toast.warning(`Inclusão da peça ${pecaSelecionada.marca} (${pecaSelecionada.comprimento}mm) cancelada por restrição dimensional.`);
return;
}
}
try { try {
const quantidade = parseInt(quantidadePeca); const quantidade = parseInt(quantidadePeca);
@@ -594,56 +633,89 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
<SelectTrigger className={isMobile ? 'h-10' : 'h-11'}> <SelectTrigger className={isMobile ? 'h-10' : 'h-11'}>
<SelectValue placeholder="Selecionar peça" /> <SelectValue placeholder="Selecionar peça" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{loading ? ( {loading ? (
<SelectItem value="loading" disabled>Carregando...</SelectItem> <SelectItem value="loading" disabled>Carregando...</SelectItem>
) : ( ) : (
<> <>
{pecasFiltradas.length === 0 ? ( {pecasFiltradas.length === 0 ? (
<SelectItem value="empty" disabled> <SelectItem value="empty" disabled>
Nenhuma peça disponível para este processo Nenhuma peça disponível para este processo
</SelectItem> </SelectItem>
) : ( ) : (
pecasFiltradas.map((peca) => ( pecasFiltradas.map((peca) => {
const limite = romaneio.comprimento_maximo_veiculo || 12000;
const naoCabe = Boolean(peca.comprimento && peca.comprimento > limite);
return (
<SelectItem key={peca.id} value={peca.id}> <SelectItem key={peca.id} value={peca.id}>
<span className="truncate"> <div className="flex items-center justify-between gap-2 w-full">
{peca.marca} - {peca.descricao} (Qtd: {peca.quantidade_disponivel}) <span className="truncate">
</span> {peca.marca} - {peca.descricao} (Qtd: {peca.quantidade_disponivel})
</span>
{peca.comprimento && (
<span className={`text-[11px] font-mono shrink-0 ml-2 px-1.5 py-0.5 rounded ${
naoCabe ? 'bg-destructive/20 text-destructive font-bold' : 'text-muted-foreground bg-muted'
}`}>
{peca.comprimento}mm {naoCabe ? '⚠️ NÃO CABE' : ''}
</span>
)}
</div>
</SelectItem> </SelectItem>
)) );
)} })
</> )}
)} </>
</SelectContent> )}
</Select> </SelectContent>
</div> </Select>
<div>
<Label className={`font-medium mb-2 block ${isMobile ? 'text-sm' : 'text-sm'}`}>
Quantidade
</Label>
<Input
type="number"
min="1"
max={pecasDisponiveis.find(p => p.id === pecaDisponivel)?.quantidade_disponivel || 1}
value={quantidadePeca}
onChange={(e) => setQuantidadePeca(e.target.value)}
placeholder="Quantidade"
className={isMobile ? 'h-10' : 'h-11'}
/>
</div>
</div> </div>
</>
)}
<Button <div>
onClick={handleAdicionarPeca} <Label className={`font-medium mb-2 block ${isMobile ? 'text-sm' : 'text-sm'}`}>
disabled={(!pecaDisponivel && !adicionarTodas) || loading} Quantidade
className={`w-full ${isMobile ? 'h-10 text-sm' : 'h-11'}`} </Label>
> <Input
<Plus className="h-4 w-4 mr-2" /> type="number"
{adicionarTodas ? 'Adicionar Todas as Peças' : 'Adicionar Peça'} min="1"
</Button> max={pecasDisponiveis.find(p => p.id === pecaDisponivel)?.quantidade_disponivel || 1}
value={quantidadePeca}
onChange={(e) => setQuantidadePeca(e.target.value)}
placeholder="Quantidade"
className={isMobile ? 'h-10' : 'h-11'}
/>
</div>
</div>
{/* Alerta de dimensão se a peça exceder a capacidade do veículo */}
{(() => {
const pSel = pecasDisponiveis.find(p => p.id === pecaDisponivel);
const limite = romaneio.comprimento_maximo_veiculo || 12000;
if (pSel?.comprimento && pSel.comprimento > limite) {
return (
<div className="p-2.5 mb-3 rounded-md bg-destructive/15 border border-destructive/40 text-destructive text-xs space-y-1">
<div className="flex items-center gap-1.5 font-bold text-xs text-destructive">
<AlertTriangle className="h-4 w-4 shrink-0" />
ALERTA DE DIMENSÃO: Peça não cabe no veículo ({limite}mm)
</div>
<p>
A peça <strong>{pSel.marca}</strong> possui <strong>{pSel.comprimento} mm ({(pSel.comprimento / 1000).toFixed(1)}m)</strong> e ULTRAPASSA o comprimento máximo de carregamento deste veículo ({(limite / 1000).toFixed(1)}m).
</p>
</div>
);
}
return null;
})()}
</>
)}
<Button
onClick={handleAdicionarPeca}
disabled={(!pecaDisponivel && !adicionarTodas) || loading}
className={`w-full ${isMobile ? 'h-10 text-sm' : 'h-11'}`}
>
<Plus className="h-4 w-4 mr-2" />
{adicionarTodas ? 'Adicionar Todas as Peças' : 'Adicionar Peça'}
</Button>
</CardContent> </CardContent>
</Card> </Card>
@@ -22,6 +22,7 @@ interface ItemRomaneioPeca {
quantidade_expedida: number; quantidade_expedida: number;
peso_unitario: number; peso_unitario: number;
peso_total: number; peso_total: number;
comprimento?: number | null;
marca: string; marca: string;
descricao: string; descricao: string;
fase: string; fase: string;
@@ -44,7 +45,7 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
try { try {
const { data, error } = await supabase const { data, error } = await supabase
.from('itens_romaneio_pecas') .from('itens_romaneio_pecas')
.select('*') .select('*, pecas(comprimento)')
.eq('romaneio_id', romaneio.id) .eq('romaneio_id', romaneio.id)
.order('marca'); .order('marca');
@@ -57,6 +58,7 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
quantidade_expedida: item.quantidade_expedida, quantidade_expedida: item.quantidade_expedida,
peso_unitario: item.peso_unitario, peso_unitario: item.peso_unitario,
peso_total: item.peso_total, peso_total: item.peso_total,
comprimento: item.pecas?.comprimento || item.comprimento || null,
marca: item.marca, marca: item.marca,
descricao: item.descricao || '', descricao: item.descricao || '',
fase: item.fase || '', fase: item.fase || '',
@@ -170,11 +172,12 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
{/* Cabeçalho da tabela - Desktop/Tablet */} {/* Cabeçalho da tabela - Desktop/Tablet */}
{!isMobile && ( {!isMobile && (
<div className={`grid gap-3 font-medium text-gray-400 bg-slate-800 p-3 rounded-t-md sticky top-0 z-10 ${ <div className={`grid gap-3 font-medium text-gray-400 bg-slate-800 p-3 rounded-t-md sticky top-0 z-10 ${
isTablet ? 'grid-cols-8 text-xs' : 'grid-cols-10 text-xs' isTablet ? 'grid-cols-9 text-xs' : 'grid-cols-11 text-xs'
}`}> }`}>
<div>Marca</div> <div>Marca</div>
<div>Fase</div> <div>Fase</div>
<div className={isTablet ? 'col-span-1' : 'col-span-2'}>Descrição</div> <div className={isTablet ? 'col-span-1' : 'col-span-2'}>Descrição</div>
<div className="text-right">Comp. (mm)</div>
<div className="text-center">Qtd</div> <div className="text-center">Qtd</div>
<div className="text-right">Peso Unit.</div> <div className="text-right">Peso Unit.</div>
<div className="text-right">Peso Total</div> <div className="text-right">Peso Total</div>
@@ -184,16 +187,29 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
)} )}
{/* Linhas de dados */} {/* Linhas de dados */}
{itensPeca.map((item, index) => ( {itensPeca.map((item, index) => {
const limite = romaneio.comprimento_maximo_veiculo || 12000;
const naoCabe = Boolean(item.comprimento && item.comprimento > limite);
return (
<div key={item.id}> <div key={item.id}>
{isMobile ? ( {isMobile ? (
// Layout mobile - card style // Layout mobile - card style
<div className={`p-3 border border-slate-700 rounded-md mb-2 ${ <div className={`p-3 border rounded-md mb-2 ${
naoCabe ? 'border-red-500/50 bg-red-950/20' : 'border-slate-700'
} ${
index % 2 === 0 ? 'bg-slate-800/20' : 'bg-slate-800/40' index % 2 === 0 ? 'bg-slate-800/20' : 'bg-slate-800/40'
}`}> }`}>
<div className="flex justify-between items-start mb-2"> <div className="flex justify-between items-start mb-2">
<div className="flex-1"> <div className="flex-1">
<div className="font-medium text-white text-sm">{item.marca}</div> <div className="font-medium text-white text-sm flex items-center gap-1.5">
{item.marca}
{naoCabe && (
<span className="text-[10px] bg-red-600 text-white font-bold px-1.5 py-0.5 rounded">
Excede Veículo
</span>
)}
</div>
<div className="text-gray-300 text-xs">{item.fase}</div> <div className="text-gray-300 text-xs">{item.fase}</div>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
@@ -218,7 +234,13 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
<div className="text-gray-300 text-xs mb-2 line-clamp-2"> <div className="text-gray-300 text-xs mb-2 line-clamp-2">
{item.descricao} {item.descricao}
</div> </div>
<div className="grid grid-cols-3 gap-2 text-xs"> <div className="grid grid-cols-4 gap-2 text-xs">
<div>
<div className="text-gray-400">Comp:</div>
<div className={`font-mono font-medium ${naoCabe ? 'text-red-400 font-bold' : 'text-gray-300'}`}>
{item.comprimento ? `${item.comprimento}mm` : '-'}
</div>
</div>
<div> <div>
<div className="text-gray-400">Qtd:</div> <div className="text-gray-400">Qtd:</div>
<div className="font-bold text-blue-400">{item.quantidade_expedida}</div> <div className="font-bold text-blue-400">{item.quantidade_expedida}</div>
@@ -237,12 +259,18 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
// Layout desktop/tablet - table style // Layout desktop/tablet - table style
<div className={`grid gap-3 text-sm p-3 border-b border-slate-700 hover:bg-slate-800/50 transition-colors ${ <div className={`grid gap-3 text-sm p-3 border-b border-slate-700 hover:bg-slate-800/50 transition-colors ${
index % 2 === 0 ? 'bg-slate-800/20' : '' index % 2 === 0 ? 'bg-slate-800/20' : ''
} ${isTablet ? 'grid-cols-8' : 'grid-cols-10'}`}> } ${isTablet ? 'grid-cols-9' : 'grid-cols-11'} ${naoCabe ? 'bg-red-950/20' : ''}`}>
<div className="font-medium text-white truncate">{item.marca}</div> <div className="font-medium text-white truncate flex items-center gap-1">
{item.marca}
{naoCabe && <span title="Não cabe no veículo" className="text-red-400 text-xs">⚠️</span>}
</div>
<div className="text-gray-300 truncate">{item.fase}</div> <div className="text-gray-300 truncate">{item.fase}</div>
<div className={`text-gray-300 truncate ${isTablet ? 'col-span-1' : 'col-span-2'}`}> <div className={`text-gray-300 truncate ${isTablet ? 'col-span-1' : 'col-span-2'}`}>
{item.descricao} {item.descricao}
</div> </div>
<div className={`text-right font-mono text-xs ${naoCabe ? 'text-red-400 font-bold' : 'text-gray-300'}`}>
{item.comprimento ? `${item.comprimento}` : '-'}
</div>
<div className="text-center font-bold text-blue-400"> <div className="text-center font-bold text-blue-400">
{item.quantidade_expedida} {item.quantidade_expedida}
</div> </div>
@@ -280,7 +308,8 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
</div> </div>
)} )}
</div> </div>
))} );
})}
</div> </div>
</ScrollArea> </ScrollArea>
)} )}
+43 -1
View File
@@ -52,6 +52,7 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
peso_total_romaneio: romaneio?.peso_total_romaneio || 0, peso_total_romaneio: romaneio?.peso_total_romaneio || 0,
previsao_kg: romaneio?.previsao_kg || 0, previsao_kg: romaneio?.previsao_kg || 0,
maior_dimensao: romaneio?.maior_dimensao || '', maior_dimensao: romaneio?.maior_dimensao || '',
comprimento_maximo_veiculo: romaneio?.comprimento_maximo_veiculo || 12000,
tipo_transporte: romaneio?.tipo_transporte || 'carro' as 'carro' | 'utilitario' | 'caminho_pequeno' | 'caminhao_trucado' | 'caminhao_munck' | 'carreta_12m' | 'carreta_15m' | 'especial', tipo_transporte: romaneio?.tipo_transporte || 'carro' as 'carro' | 'utilitario' | 'caminho_pequeno' | 'caminhao_trucado' | 'caminhao_munck' | 'carreta_12m' | 'carreta_15m' | 'especial',
frete_tipo: (romaneio?.frete_tipo as 'terceiros' | 'proprio') || 'terceiros', frete_tipo: (romaneio?.frete_tipo as 'terceiros' | 'proprio') || 'terceiros',
nome_motorista: romaneio?.nome_motorista || '' nome_motorista: romaneio?.nome_motorista || ''
@@ -74,6 +75,7 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
peso_total_romaneio: romaneio.peso_total_romaneio || 0, peso_total_romaneio: romaneio.peso_total_romaneio || 0,
previsao_kg: romaneio.previsao_kg || 0, previsao_kg: romaneio.previsao_kg || 0,
maior_dimensao: romaneio.maior_dimensao || '', maior_dimensao: romaneio.maior_dimensao || '',
comprimento_maximo_veiculo: romaneio.comprimento_maximo_veiculo || 12000,
tipo_transporte: romaneio.tipo_transporte || 'carro', tipo_transporte: romaneio.tipo_transporte || 'carro',
frete_tipo: (romaneio.frete_tipo as 'terceiros' | 'proprio') || 'terceiros', frete_tipo: (romaneio.frete_tipo as 'terceiros' | 'proprio') || 'terceiros',
nome_motorista: romaneio.nome_motorista || '' nome_motorista: romaneio.nome_motorista || ''
@@ -92,6 +94,7 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
peso_total_romaneio: 0, peso_total_romaneio: 0,
previsao_kg: 0, previsao_kg: 0,
maior_dimensao: '', maior_dimensao: '',
comprimento_maximo_veiculo: 12000,
tipo_transporte: 'carro', tipo_transporte: 'carro',
frete_tipo: 'terceiros', frete_tipo: 'terceiros',
nome_motorista: '' nome_motorista: ''
@@ -374,7 +377,23 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
<Label htmlFor="tipo_transporte">Tipo de Transporte</Label> <Label htmlFor="tipo_transporte">Tipo de Transporte</Label>
<Select <Select
value={formData.tipo_transporte} value={formData.tipo_transporte}
onValueChange={(value: any) => setFormData(prev => ({ ...prev, tipo_transporte: value }))} onValueChange={(value: any) => {
const compPadrao = {
carro: 1500,
utilitario: 2500,
caminho_pequeno: 6000,
caminhao_trucado: 8000,
caminhao_munck: 7000,
carreta_12m: 12000,
carreta_15m: 15000,
especial: 18000
}[value] || 12000;
setFormData(prev => ({
...prev,
tipo_transporte: value,
comprimento_maximo_veiculo: compPadrao
}));
}}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@@ -389,6 +408,29 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
</Select> </Select>
</div> </div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="comprimento_maximo_veiculo">Capacidade Máx. Peça (mm) *</Label>
{formData.comprimento_maximo_veiculo > 0 && (
<span className="text-xs font-semibold text-primary">
{(formData.comprimento_maximo_veiculo / 1000).toFixed(1)}m
</span>
)}
</div>
<Input
id="comprimento_maximo_veiculo"
type="number"
min="0"
step="100"
value={formData.comprimento_maximo_veiculo || ''}
onChange={(e) => setFormData(prev => ({ ...prev, comprimento_maximo_veiculo: parseFloat(e.target.value) || 0 }))}
placeholder="Ex: 10000 (10 metros)"
/>
<p className="text-[11px] text-muted-foreground">
Limite de comprimento para carregar peças neste veículo.
</p>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="frete_tipo">Tipo de Frete</Label> <Label htmlFor="frete_tipo">Tipo de Frete</Label>
<Select <Select
+29 -2
View File
@@ -30,7 +30,7 @@ interface ComponentesPopupProps {
peca: Peca; peca: Peca;
} }
type SortField = 'marca_componente' | 'descricao' | 'perfil' | 'peso_unitario' | 'quantidade_por_peca'; type SortField = 'marca_componente' | 'descricao' | 'perfil' | 'comprimento' | 'peso_unitario' | 'quantidade_por_peca';
type SortOrder = 'asc' | 'desc'; type SortOrder = 'asc' | 'desc';
export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProps) { export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProps) {
@@ -46,6 +46,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: '', marca_componente: '',
descricao: '', descricao: '',
perfil: '', perfil: '',
comprimento: 0,
peso_unitario: 0, peso_unitario: 0,
quantidade_por_peca: 1 quantidade_por_peca: 1
}); });
@@ -55,6 +56,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: '', marca_componente: '',
descricao: '', descricao: '',
perfil: '', perfil: '',
comprimento: 0,
peso_unitario: 0, peso_unitario: 0,
quantidade_por_peca: 1 quantidade_por_peca: 1
}); });
@@ -119,6 +121,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: componente.marca_componente, marca_componente: componente.marca_componente,
descricao: componente.descricao || '', descricao: componente.descricao || '',
perfil: componente.perfil || '', perfil: componente.perfil || '',
comprimento: componente.comprimento || 0,
peso_unitario: componente.peso_unitario, peso_unitario: componente.peso_unitario,
quantidade_por_peca: componente.quantidade_por_peca quantidade_por_peca: componente.quantidade_por_peca
}); });
@@ -173,7 +176,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-3"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-3">
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="marca_componente" className="text-sm">Componente *</Label> <Label htmlFor="marca_componente" className="text-sm">Componente *</Label>
<Input <Input
@@ -208,6 +211,20 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
/> />
</div> </div>
<div className="space-y-1">
<Label htmlFor="comprimento" className="text-sm">Comp. (mm)</Label>
<Input
id="comprimento"
type="number"
step="1"
min="0"
value={formData.comprimento || ''}
onChange={(e) => setFormData(prev => ({ ...prev, comprimento: parseFloat(e.target.value) || 0 }))}
placeholder="Ex: 500"
className="h-8"
/>
</div>
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="peso_unitario" className="text-sm">Peso Unitário</Label> <Label htmlFor="peso_unitario" className="text-sm">Peso Unitário</Label>
<Input <Input
@@ -331,6 +348,15 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
{getSortIcon('perfil')} {getSortIcon('perfil')}
</div> </div>
</TableHead> </TableHead>
<TableHead
className="cursor-pointer hover:bg-muted/50 select-none text-right px-2 py-1"
onClick={() => handleSort('comprimento')}
>
<div className="flex items-center justify-end gap-1 text-xs font-medium">
Comp. (mm)
{getSortIcon('comprimento')}
</div>
</TableHead>
<TableHead <TableHead
className="cursor-pointer hover:bg-muted/50 select-none text-center px-2 py-1" className="cursor-pointer hover:bg-muted/50 select-none text-center px-2 py-1"
onClick={() => handleSort('quantidade_por_peca')} onClick={() => handleSort('quantidade_por_peca')}
@@ -366,6 +392,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
<TableCell className="font-medium px-2 py-1 text-sm">{componente.marca_componente}</TableCell> <TableCell className="font-medium px-2 py-1 text-sm">{componente.marca_componente}</TableCell>
<TableCell className="px-2 py-1 text-sm">{componente.descricao || '-'}</TableCell> <TableCell className="px-2 py-1 text-sm">{componente.descricao || '-'}</TableCell>
<TableCell className="px-2 py-1 text-sm">{componente.perfil || '-'}</TableCell> <TableCell className="px-2 py-1 text-sm">{componente.perfil || '-'}</TableCell>
<TableCell className="text-right px-2 py-1 text-sm font-mono">{componente.comprimento ? `${componente.comprimento}` : '-'}</TableCell>
<TableCell className="text-center px-2 py-1 text-sm">{componente.quantidade_por_peca}</TableCell> <TableCell className="text-center px-2 py-1 text-sm">{componente.quantidade_por_peca}</TableCell>
<TableCell className="text-center font-medium px-2 py-1 text-sm"> <TableCell className="text-center font-medium px-2 py-1 text-sm">
{calculateTotalQuantity(componente.quantidade_por_peca)} {calculateTotalQuantity(componente.quantidade_por_peca)}
+17
View File
@@ -55,6 +55,7 @@ export function PecaForm({
quantidade: 1, quantidade: 1,
peso_unitario: 0, peso_unitario: 0,
peso_total: 0, peso_total: 0,
comprimento: 0,
tratamento_superficial: '', tratamento_superficial: '',
material: '', material: '',
perfil_principal: '', perfil_principal: '',
@@ -71,6 +72,7 @@ export function PecaForm({
quantidade: editingPeca.quantidade, quantidade: editingPeca.quantidade,
peso_unitario: editingPeca.peso_unitario, peso_unitario: editingPeca.peso_unitario,
peso_total: editingPeca.peso_total, peso_total: editingPeca.peso_total,
comprimento: editingPeca.comprimento || 0,
tratamento_superficial: editingPeca.tratamento_superficial || '', tratamento_superficial: editingPeca.tratamento_superficial || '',
material: editingPeca.material || '', material: editingPeca.material || '',
perfil_principal: editingPeca.perfil_principal || '', perfil_principal: editingPeca.perfil_principal || '',
@@ -85,6 +87,7 @@ export function PecaForm({
quantidade: 1, quantidade: 1,
peso_unitario: 0, peso_unitario: 0,
peso_total: 0, peso_total: 0,
comprimento: 0,
tratamento_superficial: '', tratamento_superficial: '',
material: '', material: '',
perfil_principal: '', perfil_principal: '',
@@ -148,6 +151,7 @@ export function PecaForm({
quantidade: 1, quantidade: 1,
peso_unitario: 0, peso_unitario: 0,
peso_total: 0, peso_total: 0,
comprimento: 0,
tratamento_superficial: '', tratamento_superficial: '',
material: '', material: '',
perfil_principal: '', perfil_principal: '',
@@ -246,6 +250,19 @@ export function PecaForm({
/> />
</div> </div>
<div>
<Label htmlFor="comprimento">Comprimento (mm)</Label>
<Input
id="comprimento"
type="number"
step="1"
min="0"
value={formData.comprimento || ''}
onChange={(e) => handleInputChange('comprimento', parseFloat(e.target.value) || 0)}
placeholder="Ex: 12000"
/>
</div>
<div> <div>
<Label htmlFor="tratamento_superficial">Tratamento Superficial</Label> <Label htmlFor="tratamento_superficial">Tratamento Superficial</Label>
<Input <Input
@@ -88,6 +88,10 @@ export function TableHeaderSortable({
<SortButton field="peso_total">Peso Total</SortButton> <SortButton field="peso_total">Peso Total</SortButton>
</TableHead> </TableHead>
<TableHead className="w-20 px-2 text-right">
<SortButton field="comprimento">Comp. (mm)</SortButton>
</TableHead>
<TableHead className="w-16 px-2"> <TableHead className="w-16 px-2">
<span className="text-xs font-medium">Material</span> <span className="text-xs font-medium">Material</span>
</TableHead> </TableHead>
+8
View File
@@ -144,6 +144,10 @@ export function TableRow({
{(peca.peso_total || (peca.quantidade * peca.peso_unitario)).toFixed(2)} kg {(peca.peso_total || (peca.quantidade * peca.peso_unitario)).toFixed(2)} kg
</TableCell> </TableCell>
<TableCell className="w-20 px-2 text-right font-mono font-medium text-foreground text-xs">
{peca.comprimento != null && Number(peca.comprimento) > 0 ? `${Number(peca.comprimento).toLocaleString('pt-BR')} mm` : '-'}
</TableCell>
<TableCell className="w-16 px-2 text-muted-foreground text-xs"> <TableCell className="w-16 px-2 text-muted-foreground text-xs">
{peca.material || '-'} {peca.material || '-'}
</TableCell> </TableCell>
@@ -246,6 +250,7 @@ export function TableRow({
<tr> <tr>
<th className="p-2 pl-3">Marca {labels.componenteLabel}</th> <th className="p-2 pl-3">Marca {labels.componenteLabel}</th>
<th className="p-2">Descrição / Perfil</th> <th className="p-2">Descrição / Perfil</th>
<th className="p-2 text-right">Comp. (mm)</th>
<th className="p-2 text-center">Qtd / {labels.pecaLabel}</th> <th className="p-2 text-center">Qtd / {labels.pecaLabel}</th>
<th className="p-2 text-center font-bold">Qtd Total no Lote</th> <th className="p-2 text-center font-bold">Qtd Total no Lote</th>
<th className="p-2 text-right">Peso Unit. (kg)</th> <th className="p-2 text-right">Peso Unit. (kg)</th>
@@ -269,6 +274,9 @@ export function TableRow({
<td className="p-2 text-slate-700 dark:text-slate-300 font-medium"> <td className="p-2 text-slate-700 dark:text-slate-300 font-medium">
{comp.perfil || comp.descricao || '-'} {comp.perfil || comp.descricao || '-'}
</td> </td>
<td className="p-2 text-right font-mono text-slate-700 dark:text-slate-300">
{comp.comprimento != null && Number(comp.comprimento) > 0 ? `${Number(comp.comprimento).toLocaleString('pt-BR')} mm` : '-'}
</td>
<td className="p-2 text-center font-mono text-slate-600 dark:text-slate-400"> <td className="p-2 text-center font-mono text-slate-600 dark:text-slate-400">
{qtdPorPeca} un {qtdPorPeca} un
</td> </td>
+2
View File
@@ -11,6 +11,7 @@ export interface ComponentePeca {
descricao: string | null; descricao: string | null;
perfil: string | null; perfil: string | null;
peso_unitario: number; peso_unitario: number;
comprimento?: number | null;
quantidade_por_peca: number; quantidade_por_peca: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
@@ -21,6 +22,7 @@ export interface ComponenteFormData {
descricao: string; descricao: string;
perfil: string; perfil: string;
peso_unitario: number; peso_unitario: number;
comprimento?: number | null;
quantidade_por_peca: number; quantidade_por_peca: number;
} }
+4
View File
@@ -13,6 +13,7 @@ export interface Peca {
quantidade: number; quantidade: number;
peso_unitario: number; peso_unitario: number;
peso_total: number; peso_total: number;
comprimento?: number | null;
tratamento_superficial: string; tratamento_superficial: string;
material: string; material: string;
perfil_principal: string; perfil_principal: string;
@@ -430,6 +431,7 @@ export function usePecas() {
quantidade: Number(item.quantidade) || 0, quantidade: Number(item.quantidade) || 0,
peso_unitario: Number(item.peso_unitario) || 0, peso_unitario: Number(item.peso_unitario) || 0,
peso_total: Number(item.peso_total) || 0, peso_total: Number(item.peso_total) || 0,
comprimento: item.comprimento ? Number(item.comprimento) : (item.comprimento_ref ? Number(item.comprimento_ref) : null),
tratamento_superficial: item.tratamento_superficial || '', tratamento_superficial: item.tratamento_superficial || '',
material: item.material || '', material: item.material || '',
perfil_principal: item.perfil_principal || '', perfil_principal: item.perfil_principal || '',
@@ -470,6 +472,7 @@ export function usePecas() {
descricao: item.descricao_componente || item.descricao || '', descricao: item.descricao_componente || item.descricao || '',
perfil: item.perfil_componente || item.perfil_principal || '', perfil: item.perfil_componente || item.perfil_principal || '',
peso_unitario: Number(item.peso_unitario_componente) || 0, peso_unitario: Number(item.peso_unitario_componente) || 0,
comprimento: item.comprimento_componente ? Number(item.comprimento_componente) : null,
quantidade_por_peca: Math.max(1, Math.round(Number(item.quantidade_por_peca) || 1)), quantidade_por_peca: Math.max(1, Math.round(Number(item.quantidade_por_peca) || 1)),
user_id: user.id user_id: user.id
}; };
@@ -527,6 +530,7 @@ export function usePecas() {
quantidade: peca.quantidade, quantidade: peca.quantidade,
peso_unitario: peca.peso_unitario, peso_unitario: peca.peso_unitario,
peso_total: peca.peso_total, peso_total: peca.peso_total,
comprimento: peca.comprimento,
tratamento_superficial: peca.tratamento_superficial, tratamento_superficial: peca.tratamento_superficial,
material: peca.material, material: peca.material,
perfil_principal: peca.perfil_principal, perfil_principal: peca.perfil_principal,
+1 -1
View File
@@ -4,7 +4,7 @@ import { Peca } from './usePecas';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { applyMarcaFilter } from '@/utils/rangeFilter'; import { applyMarcaFilter } from '@/utils/rangeFilter';
export type SortField = 'of_number' | 'etapa_fase' | 'marca' | 'descricao' | 'prioridade' | 'quantidade' | 'peso_unitario' | 'peso_total' | 'perfil_principal' | 'tem_componentes'; export type SortField = 'of_number' | 'etapa_fase' | 'marca' | 'descricao' | 'prioridade' | 'quantidade' | 'peso_unitario' | 'peso_total' | 'comprimento' | 'perfil_principal' | 'tem_componentes';
export type SortOrder = 'asc' | 'desc'; export type SortOrder = 'asc' | 'desc';
export const usePecasTable = (pecas: Peca[]) => { export const usePecasTable = (pecas: Peca[]) => {
+4
View File
@@ -19,6 +19,7 @@ export interface RomaneioExpedicao {
peso_total_romaneio: number; peso_total_romaneio: number;
previsao_kg?: number; previsao_kg?: number;
maior_dimensao?: string; maior_dimensao?: string;
comprimento_maximo_veiculo?: number;
tipo_transporte?: string; tipo_transporte?: string;
frete_tipo?: string; frete_tipo?: string;
nome_motorista?: string; nome_motorista?: string;
@@ -62,6 +63,7 @@ export interface PecaPintura {
marca: string; marca: string;
etapa_fase?: string; etapa_fase?: string;
descricao?: string; descricao?: string;
comprimento?: number | null;
peso_unitario: number; peso_unitario: number;
quantidade_produzida_pintura: number; quantidade_produzida_pintura: number;
quantidade_ja_expedida: number; quantidade_ja_expedida: number;
@@ -133,6 +135,7 @@ export const usePecasPintura = (ofNumber?: string) => {
etapa_fase, etapa_fase,
descricao, descricao,
peso_unitario, peso_unitario,
comprimento,
of_number of_number
), ),
processos_fabricacao ( processos_fabricacao (
@@ -171,6 +174,7 @@ export const usePecasPintura = (ofNumber?: string) => {
etapa_fase: peca.etapa_fase, etapa_fase: peca.etapa_fase,
descricao: peca.descricao, descricao: peca.descricao,
peso_unitario: peca.peso_unitario, peso_unitario: peca.peso_unitario,
comprimento: peca.comprimento,
quantidade_produzida_pintura: apontamento.quantidade_produzida, quantidade_produzida_pintura: apontamento.quantidade_produzida,
of_number: peca.of_number of_number: peca.of_number
}); });
+9
View File
@@ -445,6 +445,7 @@ export type Database = {
} }
componentes_peca: { componentes_peca: {
Row: { Row: {
comprimento: number | null
created_at: string created_at: string
descricao: string | null descricao: string | null
id: string id: string
@@ -457,6 +458,7 @@ export type Database = {
user_id: string | null user_id: string | null
} }
Insert: { Insert: {
comprimento?: number | null
created_at?: string created_at?: string
descricao?: string | null descricao?: string | null
id?: string id?: string
@@ -469,6 +471,7 @@ export type Database = {
user_id?: string | null user_id?: string | null
} }
Update: { Update: {
comprimento?: number | null
created_at?: string created_at?: string
descricao?: string | null descricao?: string | null
id?: string id?: string
@@ -2095,6 +2098,7 @@ export type Database = {
} }
pecas: { pecas: {
Row: { Row: {
comprimento: number | null
created_at: string created_at: string
descricao: string | null descricao: string | null
etapa_fase: string | null etapa_fase: string | null
@@ -2114,6 +2118,7 @@ export type Database = {
user_id: string | null user_id: string | null
} }
Insert: { Insert: {
comprimento?: number | null
created_at?: string created_at?: string
descricao?: string | null descricao?: string | null
etapa_fase?: string | null etapa_fase?: string | null
@@ -2133,6 +2138,7 @@ export type Database = {
user_id?: string | null user_id?: string | null
} }
Update: { Update: {
comprimento?: number | null
created_at?: string created_at?: string
descricao?: string | null descricao?: string | null
etapa_fase?: string | null etapa_fase?: string | null
@@ -2737,6 +2743,7 @@ export type Database = {
} }
romaneios_expedicao: { romaneios_expedicao: {
Row: { Row: {
comprimento_maximo_veiculo: number | null
created_at: string created_at: string
created_by: string | null created_by: string | null
data_criacao: string | null data_criacao: string | null
@@ -2759,6 +2766,7 @@ export type Database = {
updated_at: string updated_at: string
} }
Insert: { Insert: {
comprimento_maximo_veiculo?: number | null
created_at?: string created_at?: string
created_by?: string | null created_by?: string | null
data_criacao?: string | null data_criacao?: string | null
@@ -2781,6 +2789,7 @@ export type Database = {
updated_at?: string updated_at?: string
} }
Update: { Update: {
comprimento_maximo_veiculo?: number | null
created_at?: string created_at?: string
created_by?: string | null created_by?: string | null
data_criacao?: string | null data_criacao?: string | null