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 */}
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-2">
<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">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 h-3 w-3 text-muted-foreground" />
<Input
placeholder="OF, Marca..."
placeholder="Marca da peça..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
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
// Aplicar filtro de busca textual APENAS se preenchido
// Aplicar filtro de busca por MARCA DA PEÇA apenas se preenchido
if (searchTerm && searchTerm.trim() !== '') {
const searchLower = searchTerm.toLowerCase();
const searchLower = searchTerm.toLowerCase().trim();
const beforeSearch = resultado.length;
resultado = resultado.filter(apt =>
(apt.of_number && apt.of_number.toLowerCase().includes(searchLower)) ||
(apt.peca?.marca && apt.peca.marca.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
+120 -48
View File
@@ -10,7 +10,7 @@ import { ScrollArea } from '@/components/ui/scroll-area';
import { Card, CardContent } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
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 { useProcessosFabricacao } from '@/hooks/useProcessosFabricacao';
import { usePecas } from '@/hooks/usePecas';
@@ -31,6 +31,7 @@ interface PecaDisponivel {
etapa_fase: string;
quantidade_disponivel: number;
peso_unitario: number;
comprimento?: number | null;
prioridade: string;
}
@@ -40,6 +41,7 @@ interface ItemRomaneioPeca {
quantidade: number;
peso_unitario: number;
peso_total: number;
comprimento?: number | null;
marca: string;
descricao: string;
etapa_fase: string;
@@ -111,6 +113,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
descricao,
etapa_fase,
peso_unitario,
comprimento,
prioridade,
of_number
)
@@ -165,6 +168,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
etapa_fase: apontamento.peca.etapa_fase || '',
quantidade_disponivel: quantidade, // Bruto temporário
peso_unitario: apontamento.peca.peso_unitario || 0,
comprimento: apontamento.peca.comprimento || null,
prioridade: apontamento.peca.prioridade || 'P4'
});
}
@@ -210,7 +214,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
.from('itens_romaneio_pecas')
.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);
@@ -224,6 +228,7 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
quantidade: item.quantidade_expedida,
peso_unitario: item.peso_unitario,
peso_total: item.peso_total,
comprimento: item.pecas?.comprimento || item.comprimento || null,
marca: item.marca,
descricao: item.descricao || '',
etapa_fase: item.fase || '',
@@ -308,7 +313,26 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
}, [pecasDisponiveis, faseFilter, marcaFilter]);
const handleAdicionarPeca = async () => {
const limiteVeiculo = romaneio.comprimento_maximo_veiculo || 12000;
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
for (const peca of pecasFiltradas) {
try {
@@ -345,6 +369,21 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
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 {
const quantidade = parseInt(quantidadePeca);
@@ -594,56 +633,89 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
<SelectTrigger className={isMobile ? 'h-10' : 'h-11'}>
<SelectValue placeholder="Selecionar peça" />
</SelectTrigger>
<SelectContent>
{loading ? (
<SelectItem value="loading" disabled>Carregando...</SelectItem>
) : (
<>
{pecasFiltradas.length === 0 ? (
<SelectItem value="empty" disabled>
Nenhuma peça disponível para este processo
</SelectItem>
) : (
pecasFiltradas.map((peca) => (
<SelectContent>
{loading ? (
<SelectItem value="loading" disabled>Carregando...</SelectItem>
) : (
<>
{pecasFiltradas.length === 0 ? (
<SelectItem value="empty" disabled>
Nenhuma peça disponível para este processo
</SelectItem>
) : (
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}>
<span className="truncate">
{peca.marca} - {peca.descricao} (Qtd: {peca.quantidade_disponivel})
</span>
<div className="flex items-center justify-between gap-2 w-full">
<span className="truncate">
{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>
))
)}
</>
)}
</SelectContent>
</Select>
</div>
<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>
);
})
)}
</>
)}
</SelectContent>
</Select>
</div>
</>
)}
<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>
<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>
{/* 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>
</Card>
@@ -22,6 +22,7 @@ interface ItemRomaneioPeca {
quantidade_expedida: number;
peso_unitario: number;
peso_total: number;
comprimento?: number | null;
marca: string;
descricao: string;
fase: string;
@@ -44,7 +45,7 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
try {
const { data, error } = await supabase
.from('itens_romaneio_pecas')
.select('*')
.select('*, pecas(comprimento)')
.eq('romaneio_id', romaneio.id)
.order('marca');
@@ -57,6 +58,7 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
quantidade_expedida: item.quantidade_expedida,
peso_unitario: item.peso_unitario,
peso_total: item.peso_total,
comprimento: item.pecas?.comprimento || item.comprimento || null,
marca: item.marca,
descricao: item.descricao || '',
fase: item.fase || '',
@@ -170,11 +172,12 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
{/* Cabeçalho da tabela - Desktop/Tablet */}
{!isMobile && (
<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>Fase</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-right">Peso Unit.</div>
<div className="text-right">Peso Total</div>
@@ -184,16 +187,29 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
)}
{/* 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}>
{isMobile ? (
// 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'
}`}>
<div className="flex justify-between items-start mb-2">
<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>
<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">
{item.descricao}
</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 className="text-gray-400">Qtd:</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
<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' : ''
} ${isTablet ? 'grid-cols-8' : 'grid-cols-10'}`}>
<div className="font-medium text-white truncate">{item.marca}</div>
} ${isTablet ? 'grid-cols-9' : 'grid-cols-11'} ${naoCabe ? 'bg-red-950/20' : ''}`}>
<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 ${isTablet ? 'col-span-1' : 'col-span-2'}`}>
{item.descricao}
</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">
{item.quantidade_expedida}
</div>
@@ -280,7 +308,8 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
</div>
)}
</div>
))}
);
})}
</div>
</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,
previsao_kg: romaneio?.previsao_kg || 0,
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',
frete_tipo: (romaneio?.frete_tipo as 'terceiros' | 'proprio') || 'terceiros',
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,
previsao_kg: romaneio.previsao_kg || 0,
maior_dimensao: romaneio.maior_dimensao || '',
comprimento_maximo_veiculo: romaneio.comprimento_maximo_veiculo || 12000,
tipo_transporte: romaneio.tipo_transporte || 'carro',
frete_tipo: (romaneio.frete_tipo as 'terceiros' | 'proprio') || 'terceiros',
nome_motorista: romaneio.nome_motorista || ''
@@ -92,6 +94,7 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
peso_total_romaneio: 0,
previsao_kg: 0,
maior_dimensao: '',
comprimento_maximo_veiculo: 12000,
tipo_transporte: 'carro',
frete_tipo: 'terceiros',
nome_motorista: ''
@@ -374,7 +377,23 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
<Label htmlFor="tipo_transporte">Tipo de Transporte</Label>
<Select
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>
<SelectValue />
@@ -389,6 +408,29 @@ export function RomaneioForm({ romaneio, ofNumbers, selectedOF, onSave, onCancel
</Select>
</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">
<Label htmlFor="frete_tipo">Tipo de Frete</Label>
<Select
+29 -2
View File
@@ -30,7 +30,7 @@ interface ComponentesPopupProps {
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';
export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProps) {
@@ -46,6 +46,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: '',
descricao: '',
perfil: '',
comprimento: 0,
peso_unitario: 0,
quantidade_por_peca: 1
});
@@ -55,6 +56,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: '',
descricao: '',
perfil: '',
comprimento: 0,
peso_unitario: 0,
quantidade_por_peca: 1
});
@@ -119,6 +121,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
marca_componente: componente.marca_componente,
descricao: componente.descricao || '',
perfil: componente.perfil || '',
comprimento: componente.comprimento || 0,
peso_unitario: componente.peso_unitario,
quantidade_por_peca: componente.quantidade_por_peca
});
@@ -173,7 +176,7 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
</CardTitle>
</CardHeader>
<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">
<Label htmlFor="marca_componente" className="text-sm">Componente *</Label>
<Input
@@ -208,6 +211,20 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
/>
</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">
<Label htmlFor="peso_unitario" className="text-sm">Peso Unitário</Label>
<Input
@@ -331,6 +348,15 @@ export function ComponentesPopup({ isOpen, onClose, peca }: ComponentesPopupProp
{getSortIcon('perfil')}
</div>
</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
className="cursor-pointer hover:bg-muted/50 select-none text-center px-2 py-1"
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="px-2 py-1 text-sm">{componente.descricao || '-'}</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 font-medium px-2 py-1 text-sm">
{calculateTotalQuantity(componente.quantidade_por_peca)}
+17
View File
@@ -55,6 +55,7 @@ export function PecaForm({
quantidade: 1,
peso_unitario: 0,
peso_total: 0,
comprimento: 0,
tratamento_superficial: '',
material: '',
perfil_principal: '',
@@ -71,6 +72,7 @@ export function PecaForm({
quantidade: editingPeca.quantidade,
peso_unitario: editingPeca.peso_unitario,
peso_total: editingPeca.peso_total,
comprimento: editingPeca.comprimento || 0,
tratamento_superficial: editingPeca.tratamento_superficial || '',
material: editingPeca.material || '',
perfil_principal: editingPeca.perfil_principal || '',
@@ -85,6 +87,7 @@ export function PecaForm({
quantidade: 1,
peso_unitario: 0,
peso_total: 0,
comprimento: 0,
tratamento_superficial: '',
material: '',
perfil_principal: '',
@@ -148,6 +151,7 @@ export function PecaForm({
quantidade: 1,
peso_unitario: 0,
peso_total: 0,
comprimento: 0,
tratamento_superficial: '',
material: '',
perfil_principal: '',
@@ -246,6 +250,19 @@ export function PecaForm({
/>
</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>
<Label htmlFor="tratamento_superficial">Tratamento Superficial</Label>
<Input
@@ -88,6 +88,10 @@ export function TableHeaderSortable({
<SortButton field="peso_total">Peso Total</SortButton>
</TableHead>
<TableHead className="w-20 px-2 text-right">
<SortButton field="comprimento">Comp. (mm)</SortButton>
</TableHead>
<TableHead className="w-16 px-2">
<span className="text-xs font-medium">Material</span>
</TableHead>
+8
View File
@@ -144,6 +144,10 @@ export function TableRow({
{(peca.peso_total || (peca.quantidade * peca.peso_unitario)).toFixed(2)} kg
</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">
{peca.material || '-'}
</TableCell>
@@ -246,6 +250,7 @@ export function TableRow({
<tr>
<th className="p-2 pl-3">Marca {labels.componenteLabel}</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 font-bold">Qtd Total no Lote</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">
{comp.perfil || comp.descricao || '-'}
</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">
{qtdPorPeca} un
</td>
+2
View File
@@ -11,6 +11,7 @@ export interface ComponentePeca {
descricao: string | null;
perfil: string | null;
peso_unitario: number;
comprimento?: number | null;
quantidade_por_peca: number;
created_at: string;
updated_at: string;
@@ -21,6 +22,7 @@ export interface ComponenteFormData {
descricao: string;
perfil: string;
peso_unitario: number;
comprimento?: number | null;
quantidade_por_peca: number;
}
+4
View File
@@ -13,6 +13,7 @@ export interface Peca {
quantidade: number;
peso_unitario: number;
peso_total: number;
comprimento?: number | null;
tratamento_superficial: string;
material: string;
perfil_principal: string;
@@ -430,6 +431,7 @@ export function usePecas() {
quantidade: Number(item.quantidade) || 0,
peso_unitario: Number(item.peso_unitario) || 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 || '',
material: item.material || '',
perfil_principal: item.perfil_principal || '',
@@ -470,6 +472,7 @@ export function usePecas() {
descricao: item.descricao_componente || item.descricao || '',
perfil: item.perfil_componente || item.perfil_principal || '',
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)),
user_id: user.id
};
@@ -527,6 +530,7 @@ export function usePecas() {
quantidade: peca.quantidade,
peso_unitario: peca.peso_unitario,
peso_total: peca.peso_total,
comprimento: peca.comprimento,
tratamento_superficial: peca.tratamento_superficial,
material: peca.material,
perfil_principal: peca.perfil_principal,
+1 -1
View File
@@ -4,7 +4,7 @@ import { Peca } from './usePecas';
import { supabase } from '@/integrations/supabase/client';
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 const usePecasTable = (pecas: Peca[]) => {
+4
View File
@@ -19,6 +19,7 @@ export interface RomaneioExpedicao {
peso_total_romaneio: number;
previsao_kg?: number;
maior_dimensao?: string;
comprimento_maximo_veiculo?: number;
tipo_transporte?: string;
frete_tipo?: string;
nome_motorista?: string;
@@ -62,6 +63,7 @@ export interface PecaPintura {
marca: string;
etapa_fase?: string;
descricao?: string;
comprimento?: number | null;
peso_unitario: number;
quantidade_produzida_pintura: number;
quantidade_ja_expedida: number;
@@ -133,6 +135,7 @@ export const usePecasPintura = (ofNumber?: string) => {
etapa_fase,
descricao,
peso_unitario,
comprimento,
of_number
),
processos_fabricacao (
@@ -171,6 +174,7 @@ export const usePecasPintura = (ofNumber?: string) => {
etapa_fase: peca.etapa_fase,
descricao: peca.descricao,
peso_unitario: peca.peso_unitario,
comprimento: peca.comprimento,
quantidade_produzida_pintura: apontamento.quantidade_produzida,
of_number: peca.of_number
});
+9
View File
@@ -445,6 +445,7 @@ export type Database = {
}
componentes_peca: {
Row: {
comprimento: number | null
created_at: string
descricao: string | null
id: string
@@ -457,6 +458,7 @@ export type Database = {
user_id: string | null
}
Insert: {
comprimento?: number | null
created_at?: string
descricao?: string | null
id?: string
@@ -469,6 +471,7 @@ export type Database = {
user_id?: string | null
}
Update: {
comprimento?: number | null
created_at?: string
descricao?: string | null
id?: string
@@ -2095,6 +2098,7 @@ export type Database = {
}
pecas: {
Row: {
comprimento: number | null
created_at: string
descricao: string | null
etapa_fase: string | null
@@ -2114,6 +2118,7 @@ export type Database = {
user_id: string | null
}
Insert: {
comprimento?: number | null
created_at?: string
descricao?: string | null
etapa_fase?: string | null
@@ -2133,6 +2138,7 @@ export type Database = {
user_id?: string | null
}
Update: {
comprimento?: number | null
created_at?: string
descricao?: string | null
etapa_fase?: string | null
@@ -2737,6 +2743,7 @@ export type Database = {
}
romaneios_expedicao: {
Row: {
comprimento_maximo_veiculo: number | null
created_at: string
created_by: string | null
data_criacao: string | null
@@ -2759,6 +2766,7 @@ export type Database = {
updated_at: string
}
Insert: {
comprimento_maximo_veiculo?: number | null
created_at?: string
created_by?: string | null
data_criacao?: string | null
@@ -2781,6 +2789,7 @@ export type Database = {
updated_at?: string
}
Update: {
comprimento_maximo_veiculo?: number | null
created_at?: string
created_by?: string | null
data_criacao?: string | null