fix(expedicao): suporte a modo claro e escuro no modal PecasRomaneioExpandido

This commit is contained in:
2026-10-28 10:49:45 -03:00
parent 2bf0ccd8ce
commit 7f98ae9803
@@ -1,9 +1,10 @@
import React, { useState, useEffect } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Edit2, Trash2, X } from 'lucide-react';
import { Edit2, Trash2, Check, X, Package } from 'lucide-react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { RomaneioExpedicao } from '@/hooks/useRomaneios';
@@ -20,6 +21,7 @@ interface ItemRomaneioPeca {
id: string;
peca_id: string;
quantidade_expedida: number;
quantidade_max_peca?: number;
peso_unitario: number;
peso_total: number;
comprimento?: number | null;
@@ -39,13 +41,16 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
const [itensPeca, setItensPeca] = useState<ItemRomaneioPeca[]>([]);
const [loading, setLoading] = useState(false);
const [editingItemId, setEditingItemId] = useState<string | null>(null);
const [editQuantidade, setEditQuantidade] = useState<string>('');
const [savingEdit, setSavingEdit] = useState(false);
const carregarItensPecas = async () => {
setLoading(true);
try {
const { data, error } = await supabase
.from('itens_romaneio_pecas')
.select('*, pecas(comprimento)')
.select('*, pecas(comprimento, prioridade, quantidade)')
.eq('romaneio_id', romaneio.id)
.order('marca');
@@ -56,13 +61,14 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
id: item.id,
peca_id: item.peca_id,
quantidade_expedida: item.quantidade_expedida,
quantidade_max_peca: item.pecas?.quantidade || undefined,
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 || '',
prioridade: 'P4'
prioridade: item.pecas?.prioridade || 'P4'
}));
setItensPeca(itensFormatados);
}
@@ -76,8 +82,10 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
useEffect(() => {
if (isOpen) {
setEditingItemId(null);
carregarItensPecas();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen, romaneio.id]);
const handleRemoverItem = async (itemId: string) => {
@@ -99,16 +107,59 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
};
const handleEditarItem = (item: ItemRomaneioPeca) => {
// Funcionalidade de edição pode ser implementada aqui
toast.info('Funcionalidade de edição em desenvolvimento');
setEditingItemId(item.id);
setEditQuantidade(String(item.quantidade_expedida));
};
const handleCancelarEdicao = () => {
setEditingItemId(null);
setEditQuantidade('');
};
const handleSalvarEdicao = async (item: ItemRomaneioPeca) => {
const novaQtd = parseInt(editQuantidade, 10);
if (isNaN(novaQtd) || novaQtd <= 0) {
toast.error('Informe uma quantidade válida maior que zero');
return;
}
if (item.quantidade_max_peca && novaQtd > item.quantidade_max_peca) {
toast.error(`A quantidade não pode exceder o total da peça na OF (${item.quantidade_max_peca} un)`);
return;
}
try {
setSavingEdit(true);
const novoPesoTotal = novaQtd * item.peso_unitario;
const { error } = await supabase
.from('itens_romaneio_pecas')
.update({
quantidade_expedida: novaQtd,
peso_total: novoPesoTotal
})
.eq('id', item.id);
if (error) throw error;
toast.success(`Quantidade da peça ${item.marca} atualizada para ${novaQtd} un`);
setEditingItemId(null);
setEditQuantidade('');
carregarItensPecas();
onRefresh();
} catch (error) {
console.error('Erro ao atualizar item:', error);
toast.error('Erro ao atualizar quantidade da peça');
} finally {
setSavingEdit(false);
}
};
const getPrioridadeColor = (prioridade: string) => {
switch (prioridade) {
case 'P1': return 'bg-red-100 text-red-800';
case 'P2': return 'bg-orange-100 text-orange-800';
case 'P3': return 'bg-blue-100 text-blue-800';
default: return 'bg-gray-100 text-gray-800';
case 'P1': return 'bg-red-100 text-red-800 border-red-200 dark:bg-red-950/60 dark:text-red-300 dark:border-red-800';
case 'P2': return 'bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950/60 dark:text-orange-300 dark:border-orange-800';
case 'P3': return 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950/60 dark:text-blue-300 dark:border-blue-800';
default: return 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700';
}
};
@@ -122,32 +173,35 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
? 'w-[95vw] max-w-none h-[95vh] m-2'
: isTablet
? 'w-[90vw] max-w-5xl h-[90vh]'
: 'max-w-6xl h-[95vh]'
: 'max-w-6xl h-[90vh]'
}
bg-slate-900 text-white border-slate-700 overflow-hidden flex flex-col
bg-background text-foreground border-border shadow-xl overflow-hidden flex flex-col
`}>
<DialogHeader className="border-b border-slate-700 pb-3 shrink-0">
<DialogHeader className="border-b border-border pb-3 shrink-0 pr-6">
<div className="flex justify-between items-center">
<DialogTitle className={`font-bold text-white ${isMobile ? 'text-lg' : 'text-xl'}`}>
<DialogTitle className={`font-bold text-foreground flex items-center gap-2 ${isMobile ? 'text-lg' : 'text-xl'}`}>
<Package className="h-5 w-5 text-primary shrink-0" />
<span className="truncate">
Peças do Romaneio {romaneio.numero_romaneio} - OF: {romaneio.of_number}
</span>
</DialogTitle>
<Button
onClick={onClose}
variant="ghost"
size="sm"
className={`text-gray-400 hover:text-white p-0 ${isMobile ? 'h-6 w-6' : 'h-8 w-8'}`}
>
<X className={isMobile ? 'h-3 w-3' : 'h-4 w-4'} />
</Button>
</div>
<div className={`flex gap-2 mt-3 ${isMobile ? 'flex-wrap' : ''}`}>
<Badge variant="outline" className={`border-blue-500 text-blue-400 ${isMobile ? 'text-xs px-2 py-1' : ''}`}>
<Badge
variant="outline"
className={`border-blue-500/40 bg-blue-500/10 text-blue-700 dark:text-blue-400 dark:border-blue-500/50 font-semibold ${
isMobile ? 'text-xs px-2.5 py-1' : 'px-3 py-1'
}`}
>
Qtd Total: {qtdTotal}
</Badge>
<Badge variant="outline" className={`border-green-500 text-green-400 ${isMobile ? 'text-xs px-2 py-1' : ''}`}>
<Badge
variant="outline"
className={`border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 dark:border-emerald-500/50 font-semibold ${
isMobile ? 'text-xs px-2.5 py-1' : 'px-3 py-1'
}`}
>
Peso Total: {pesoTotal.toFixed(2)} kg
</Badge>
</div>
@@ -156,13 +210,13 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
<div className="flex-1 min-h-0">
{loading ? (
<div className="flex justify-center items-center h-64">
<div className={`text-gray-400 ${isMobile ? 'text-sm' : 'text-base'}`}>
<div className={`text-muted-foreground ${isMobile ? 'text-sm' : 'text-base'}`}>
Carregando...
</div>
</div>
) : itensPeca.length === 0 ? (
<div className="flex justify-center items-center h-64">
<div className={`text-gray-400 ${isMobile ? 'text-sm' : 'text-base'}`}>
<div className={`text-muted-foreground ${isMobile ? 'text-sm' : 'text-base'}`}>
Nenhuma peça encontrada no romaneio
</div>
</div>
@@ -171,7 +225,7 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
<div className="space-y-1 p-1 pb-4">
{/* 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 ${
<div className={`grid gap-3 font-semibold text-muted-foreground bg-muted/80 dark:bg-slate-800/90 border border-border p-3 rounded-t-md sticky top-0 z-10 backdrop-blur-sm ${
isTablet ? 'grid-cols-9 text-xs' : 'grid-cols-11 text-xs'
}`}>
<div>Marca</div>
@@ -190,120 +244,209 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
{itensPeca.map((item, index) => {
const limite = romaneio.comprimento_maximo_veiculo || 12000;
const naoCabe = Boolean(item.comprimento && item.comprimento > limite);
const isEditing = editingItemId === item.id;
return (
<div key={item.id}>
{isMobile ? (
// Layout mobile - card style
<div className={`p-3 border rounded-md mb-2 ${
naoCabe ? 'border-red-500/50 bg-red-950/20' : 'border-slate-700'
<div className={`p-3 border rounded-md mb-2 transition-colors ${
naoCabe
? 'border-destructive/50 bg-destructive/10'
: 'border-border'
} ${
index % 2 === 0 ? 'bg-slate-800/20' : 'bg-slate-800/40'
index % 2 === 0 ? 'bg-card' : 'bg-muted/30'
}`}>
<div className="flex justify-between items-start mb-2">
<div className="flex-1">
<div className="font-medium text-white text-sm flex items-center gap-1.5">
<div className="font-semibold text-foreground 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">
<span className="text-[10px] bg-destructive text-destructive-foreground 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-muted-foreground text-xs">Fase {item.fase}</div>
</div>
<div className="flex gap-1">
<Button
size="sm"
variant="ghost"
onClick={() => handleEditarItem(item)}
className="h-6 w-6 p-0 text-blue-400 hover:text-blue-300 hover:bg-blue-500/20"
>
<Edit2 className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleRemoverItem(item.id)}
className="h-6 w-6 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/20"
>
<Trash2 className="h-3 w-3" />
</Button>
{isEditing ? (
<>
<Button
size="sm"
variant="ghost"
disabled={savingEdit}
onClick={() => handleSalvarEdicao(item)}
className="h-7 w-7 p-0 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/15"
title="Salvar quantidade"
>
<Check className="h-3.5 w-3.5" />
</Button>
<Button
size="sm"
variant="ghost"
disabled={savingEdit}
onClick={handleCancelarEdicao}
className="h-7 w-7 p-0 text-muted-foreground hover:bg-muted"
title="Cancelar"
>
<X className="h-3.5 w-3.5" />
</Button>
</>
) : (
<>
<Button
size="sm"
variant="ghost"
onClick={() => handleEditarItem(item)}
className="h-7 w-7 p-0 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:bg-blue-500/15"
title="Editar quantidade"
>
<Edit2 className="h-3.5 w-3.5" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleRemoverItem(item.id)}
className="h-7 w-7 p-0 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-500/15"
title="Remover peça"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</>
)}
</div>
</div>
<div className="text-gray-300 text-xs mb-2 line-clamp-2">
<div className="text-muted-foreground text-xs mb-2 line-clamp-2">
{item.descricao}
</div>
<div className="grid grid-cols-4 gap-2 text-xs">
<div className="grid grid-cols-4 gap-2 text-xs items-center">
<div>
<div className="text-gray-400">Comp:</div>
<div className={`font-mono font-medium ${naoCabe ? 'text-red-400 font-bold' : 'text-gray-300'}`}>
<div className="text-muted-foreground">Comp:</div>
<div className={`font-mono font-medium ${naoCabe ? 'text-destructive font-bold' : 'text-foreground'}`}>
{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>
<div className="text-muted-foreground">Qtd:</div>
{isEditing ? (
<Input
type="number"
min="1"
max={item.quantidade_max_peca}
value={editQuantidade}
onChange={(e) => setEditQuantidade(e.target.value)}
className="h-7 w-16 text-center text-xs px-1"
autoFocus
/>
) : (
<div className="font-bold text-blue-600 dark:text-blue-400">{item.quantidade_expedida}</div>
)}
</div>
<div>
<div className="text-gray-400">Peso Unit:</div>
<div className="text-gray-300">{item.peso_unitario.toFixed(2)} kg</div>
<div className="text-muted-foreground">Peso Unit:</div>
<div className="text-foreground">{item.peso_unitario.toFixed(2)} kg</div>
</div>
<div>
<div className="text-gray-400">Peso Total:</div>
<div className="font-medium text-white">{item.peso_total.toFixed(2)} kg</div>
<div className="text-muted-foreground">Peso Total:</div>
<div className="font-semibold text-foreground">{item.peso_total.toFixed(2)} kg</div>
</div>
</div>
</div>
) : (
// 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-9' : 'grid-cols-11'} ${naoCabe ? 'bg-red-950/20' : ''}`}>
<div className="font-medium text-white truncate flex items-center gap-1">
<div className={`grid gap-3 items-center text-sm p-3 border-b border-x border-border hover:bg-muted/50 transition-colors ${
index % 2 === 0 ? 'bg-card' : 'bg-muted/20'
} ${isTablet ? 'grid-cols-9' : 'grid-cols-11'} ${naoCabe ? 'bg-destructive/10 border-destructive/40' : ''}`}>
<div className="font-semibold text-foreground truncate flex items-center gap-1">
{item.marca}
{naoCabe && <span title="Não cabe no veículo" className="text-red-400 text-xs">⚠️</span>}
{naoCabe && <span title="Não cabe no veículo" className="text-destructive 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'}`}>
<div className="text-muted-foreground truncate">{item.fase}</div>
<div className={`text-foreground 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'}`}>
<div className={`text-right font-mono text-xs ${naoCabe ? 'text-destructive font-bold' : 'text-muted-foreground'}`}>
{item.comprimento ? `${item.comprimento}` : '-'}
</div>
<div className="text-center font-bold text-blue-400">
{item.quantidade_expedida}
<div className="text-center font-bold text-blue-600 dark:text-blue-400 flex justify-center">
{isEditing ? (
<Input
type="number"
min="1"
max={item.quantidade_max_peca}
value={editQuantidade}
onChange={(e) => setEditQuantidade(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSalvarEdicao(item);
if (e.key === 'Escape') handleCancelarEdicao();
}}
className="h-7 w-20 text-center text-xs px-1"
autoFocus
/>
) : (
item.quantidade_expedida
)}
</div>
<div className="text-right text-gray-300">
<div className="text-right text-muted-foreground">
{item.peso_unitario.toFixed(2)} kg
</div>
<div className="text-right font-medium text-white">
<div className="text-right font-semibold text-foreground">
{item.peso_total.toFixed(2)} kg
</div>
{!isTablet && (
<div className="flex justify-center">
<Badge className={`text-xs ${getPrioridadeColor(item.prioridade)}`}>
<Badge variant="outline" className={`text-xs ${getPrioridadeColor(item.prioridade)}`}>
{item.prioridade}
</Badge>
</div>
)}
<div className={`flex items-center justify-center gap-2 ${isTablet ? 'col-span-2' : 'col-span-2'}`}>
<Button
size="sm"
variant="ghost"
onClick={() => handleEditarItem(item)}
className="h-7 w-7 p-0 text-blue-400 hover:text-blue-300 hover:bg-blue-500/20"
>
<Edit2 className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleRemoverItem(item.id)}
className="h-7 w-7 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/20"
>
<Trash2 className="h-3 w-3" />
</Button>
{isEditing ? (
<>
<Button
size="sm"
variant="ghost"
disabled={savingEdit}
onClick={() => handleSalvarEdicao(item)}
className="h-7 w-7 p-0 text-emerald-600 dark:text-emerald-400 hover:bg-emerald-500/15"
title="Salvar quantidade"
>
<Check className="h-3.5 w-3.5" />
</Button>
<Button
size="sm"
variant="ghost"
disabled={savingEdit}
onClick={handleCancelarEdicao}
className="h-7 w-7 p-0 text-muted-foreground hover:bg-muted"
title="Cancelar edição"
>
<X className="h-3.5 w-3.5" />
</Button>
</>
) : (
<>
<Button
size="sm"
variant="ghost"
onClick={() => handleEditarItem(item)}
className="h-7 w-7 p-0 text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:bg-blue-500/15"
title="Editar quantidade"
>
<Edit2 className="h-3.5 w-3.5" />
</Button>
<Button
size="sm"
variant="ghost"
onClick={() => handleRemoverItem(item.id)}
className="h-7 w-7 p-0 text-red-600 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 hover:bg-red-500/15"
title="Remover item"
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</>
)}
</div>
</div>
)}
@@ -315,11 +458,11 @@ export const PecasRomaneioExpandido: React.FC<PecasRomaneioExpandidoProps> = ({
)}
</div>
<div className={`flex justify-end pt-3 border-t border-slate-700 shrink-0 ${isMobile ? 'px-1' : ''}`}>
<div className={`flex justify-end pt-3 border-t border-border shrink-0 ${isMobile ? 'px-1' : ''}`}>
<Button
variant="outline"
onClick={onClose}
className={`border-slate-600 text-gray-300 hover:bg-slate-800 ${isMobile ? 'h-10 px-4 text-sm' : ''}`}
className={`font-medium text-foreground border-border hover:bg-accent hover:text-accent-foreground ${isMobile ? 'h-10 px-4 text-sm' : 'px-6'}`}
>
Fechar
</Button>