diff --git a/src/components/apontamento/SeletorItensOtimizado.tsx b/src/components/apontamento/SeletorItensOtimizado.tsx index fcac3b8..2dc0870 100644 --- a/src/components/apontamento/SeletorItensOtimizado.tsx +++ b/src/components/apontamento/SeletorItensOtimizado.tsx @@ -67,6 +67,257 @@ interface PecaWithComponents extends Peca { processo_atual_permitido?: number; } +const getPrioridadeColor = (prioridade?: string) => { + switch (prioridade?.toLowerCase()) { + case 'alta': return 'text-red-600 dark:text-red-400'; + case 'média': return 'text-amber-600 dark:text-yellow-400'; + case 'baixa': return 'text-emerald-600 dark:text-green-400'; + default: return 'text-muted-foreground'; + } +}; + +const PecaItem = React.memo(({ peca, onSelect }: { peca: PecaWithComponents; onSelect: (peca: PecaWithComponents) => void }) => { + return ( +
onSelect(peca)} + > +
+
{peca.marca}
+
+ Prioridade: {peca.prioridade || 'N/A'} +
+ {peca.descricao && ( +
+ {peca.descricao} +
+ )} +
+ Qtd Disponível: {peca.quantidadeDisponivel || 0} +
+
+
+ ); +}); +PecaItem.displayName = 'PecaItem'; + +const ComponenteItem = React.memo(({ componente, onSelect }: { componente: ComponenteItemData; onSelect: (componente: ComponenteItemData) => void }) => ( +
onSelect(componente)} + > +
+
{componente.marca_componente}
+
+ Perfil: {componente.perfil || 'N/A'} +
+
+ Peso Unitário: {componente.peso_unitario || 0}kg | Qtd por Peça: {componente.quantidade_por_peca || 1} +
+ {componente.descricao && ( +
+ {componente.descricao} +
+ )} +
+
+)); +ComponenteItem.displayName = 'ComponenteItem'; + +interface ItemDisponivelCardProps { + item: ItemDisponivel; + isSelected: boolean; + isAdmin?: boolean; + onSelect?: (item: ItemDisponivel) => void; + onAdminAction?: (item: ItemDisponivel) => void; + onApontarItemDireto?: (item: ItemDisponivel, quantidade: number) => Promise; +} + +const ItemDisponivelCard = React.memo(({ + item, + isSelected, + isAdmin, + onSelect, + onAdminAction, + onApontarItemDireto, +}: ItemDisponivelCardProps) => { + const [qtdValue, setQtdValue] = useState(''); + const [isTodas, setIsTodas] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + // Mantém sincronizado se a quantidade disponível mudar ou modo 'todas' estiver ativo + useEffect(() => { + if (isTodas) { + setQtdValue(item.quantidade_disponivel.toString()); + } else if (qtdValue) { + const parsed = parseInt(qtdValue, 10); + if (!isNaN(parsed) && parsed > item.quantidade_disponivel) { + setQtdValue(item.quantidade_disponivel.toString()); + } + } + }, [item.quantidade_disponivel, isTodas]); + + const handleToggleTodas = () => { + setIsTodas((prev) => { + const novo = !prev; + setQtdValue(novo ? item.quantidade_disponivel.toString() : ''); + return novo; + }); + }; + + const handleQtdChange = (val: string) => { + setQtdValue(val); + if (isTodas) { + setIsTodas(false); + } + }; + + const handleExecutarBaixa = async () => { + if (!onApontarItemDireto || isLoading) return; + const quantidade = isTodas ? item.quantidade_disponivel : parseInt(qtdValue, 10); + + if (isNaN(quantidade) || quantidade <= 0) { + toast.error(`Informe a quantidade para ${item.marca} ou ative 'Todas'`); + return; + } + + if (quantidade > item.quantidade_disponivel) { + toast.error(`Quantidade não pode ser maior que ${item.quantidade_disponivel} disponível`); + return; + } + + setIsLoading(true); + try { + const sucesso = await onApontarItemDireto(item, quantidade); + if (sucesso) { + setQtdValue(''); + setIsTodas(false); + } + } catch (error) { + console.error('Erro ao executar baixa direta:', error); + toast.error('Erro ao registrar apontamento'); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
onSelect?.(item)} + > +
+
+
+ {item.marca} + {item.perfil && ( + + {item.perfil} + + )} +
+
+ {item.quantidade_disponivel} disponível +
+
+ + {item.descricao && item.descricao !== item.perfil && ( +
+ {item.descricao} +
+ )} + + {/* Linha com processo e controles rápidos de apontamento */} +
+
+ Processo: {item.nome_processo || item.processo_atual_permitido} +
+ + {onApontarItemDireto && ( +
e.stopPropagation()} + > + handleQtdChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleExecutarBaixa(); + } + }} + disabled={isTodas || isLoading} + className="h-7 w-20 text-xs px-2 text-center font-medium bg-background border-input shadow-none" + /> + + + + +
+ )} +
+
+
+ + {isAdmin && onAdminAction && ( + + )} +
+ ); +}); +ItemDisponivelCard.displayName = 'ItemDisponivelCard'; + export const SeletorItensOtimizado: React.FC = ({ pecasDisponiveis = [], componentesDisponiveis = [], @@ -86,11 +337,6 @@ export const SeletorItensOtimizado: React.FC = ({ const [loadingPecas, setLoadingPecas] = useState(false); const [loadingComponentes, setLoadingComponentes] = useState(false); const [componentes, setComponentes] = useState([]); - - // Estados locais para apontamento direto por card (quantidade, modo todas e loading) - const [qtdCards, setQtdCards] = useState>({}); - const [todasCards, setTodasCards] = useState>({}); - const [loadingApontandoId, setLoadingApontandoId] = useState(null); const [showComponentes, setShowComponentes] = useState(false); const inputRef = useRef(null); @@ -184,59 +430,6 @@ export const SeletorItensOtimizado: React.FC = ({ return comps.sort((a, b) => a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' })); }, [componentesDisponiveis, filtroNumeroPeca]); - const handleQtdCardChange = useCallback((itemId: string, value: string) => { - setQtdCards(prev => ({ ...prev, [itemId]: value })); - setTodasCards(prev => ({ ...prev, [itemId]: false })); - }, []); - - const handleToggleTodas = useCallback((itemId: string, qtdDisponivel: number) => { - setTodasCards(prev => { - const novoEstado = !prev[itemId]; - if (novoEstado) { - setQtdCards(q => ({ ...q, [itemId]: qtdDisponivel.toString() })); - } else { - setQtdCards(q => ({ ...q, [itemId]: '' })); - } - return { ...prev, [itemId]: novoEstado }; - }); - }, []); - - const handleExecutarBaixa = useCallback(async (item: ItemDisponivel) => { - if (!onApontarItemDireto) return; - const isTodas = todasCards[item.id]; - const qtdDigitada = qtdCards[item.id]; - const quantidade = isTodas ? item.quantidade_disponivel : parseInt(qtdDigitada); - - if (isNaN(quantidade) || quantidade <= 0) { - toast.error(`Informe a quantidade para ${item.marca} ou ative 'Todas'`); - return; - } - - if (quantidade > item.quantidade_disponivel) { - toast.error(`Quantidade não pode ser maior que ${item.quantidade_disponivel} disponível`); - return; - } - - setLoadingApontandoId(item.id); - try { - const sucesso = await onApontarItemDireto(item, quantidade); - if (sucesso) { - setQtdCards(prev => { - const copia = { ...prev }; - delete copia[item.id]; - return copia; - }); - setTodasCards(prev => { - const copia = { ...prev }; - delete copia[item.id]; - return copia; - }); - } - } finally { - setLoadingApontandoId(null); - } - }, [onApontarItemDireto, todasCards, qtdCards]); - const fetchPecas = useCallback(async (term: string) => { if (!term.trim()) { setPecasFiltradas([]); @@ -318,174 +511,6 @@ export const SeletorItensOtimizado: React.FC = ({ } }, [onBatchSelect, pecasOrdenadas, componentesOrdenados]); - const getPrioridadeColor = (prioridade: string) => { - switch (prioridade?.toLowerCase()) { - case 'alta': return 'text-red-600 dark:text-red-400'; - case 'média': return 'text-amber-600 dark:text-yellow-400'; - case 'baixa': return 'text-emerald-600 dark:text-green-400'; - default: return 'text-muted-foreground'; - } - }; - - const PecaItem = ({ peca, onSelect }: { peca: PecaWithComponents; onSelect: (peca: PecaWithComponents) => void }) => { - return ( -
onSelect(peca)}> -
-
{peca.marca}
-
- Prioridade: {peca.prioridade || 'N/A'} -
- {peca.descricao && ( -
- {peca.descricao} -
- )} -
- Qtd Disponível: {peca.quantidadeDisponivel || 0} -
-
-
- ); - }; - - const ComponenteItem = ({ componente }: { componente: ComponenteItemData }) => ( -
handleComponenteSelect(componente)} - > -
-
{componente.marca_componente}
-
- Perfil: {componente.perfil || 'N/A'} -
-
- Peso Unitário: {componente.peso_unitario || 0}kg | Qtd por Peça: {componente.quantidade_por_peca || 1} -
- {componente.descricao && ( -
- {componente.descricao} -
- )} -
-
- ); - - const ItemDisponivelComponent = ({ item, isSelected }: { item: ItemDisponivel; isSelected: boolean }) => { - const isTodas = Boolean(todasCards[item.id]); - const qtdValue = qtdCards[item.id] ?? (isTodas ? item.quantidade_disponivel.toString() : ''); - const isLoading = loadingApontandoId === item.id; - - return ( -
-
handleItemSelect(item)} - > -
-
-
- {item.marca} - {item.perfil && ( - - {item.perfil} - - )} -
-
- {item.quantidade_disponivel} disponível -
-
- - {item.descricao && item.descricao !== item.perfil && ( -
- {item.descricao} -
- )} - - {/* Linha com processo e controles rápidos de apontamento desenhados pelo usuário */} -
-
- Processo: {item.nome_processo || item.processo_atual_permitido} -
- - {onApontarItemDireto && ( -
e.stopPropagation()} - > - handleQtdCardChange(item.id, e.target.value)} - disabled={isTodas || isLoading} - className="h-7 w-20 text-xs px-2 text-center font-medium bg-background border-input shadow-none" - /> - - - - -
- )} -
-
-
- - {isAdmin && ( - - )} -
- ); - }; - if (onSelectPeca) { return (
@@ -527,7 +552,7 @@ export const SeletorItensOtimizado: React.FC = ({
{componentes.map((componente) => ( - + ))} {componentes.length === 0 && (
@@ -592,27 +617,15 @@ export const SeletorItensOtimizado: React.FC = ({
{pecasOrdenadas.map((item) => ( -
- - - {isAdmin && ( - - )} -
+ ))} {pecasOrdenadas.length === 0 && (
@@ -645,10 +658,14 @@ export const SeletorItensOtimizado: React.FC = ({
{componentesOrdenados.map((item) => ( - ))}