259 lines
8.4 KiB
TypeScript
259 lines
8.4 KiB
TypeScript
|
|
import React, { useState, useEffect } from "react";
|
||
|
|
import { Product } from "../types";
|
||
|
|
import { productService } from "../src/services/product.service";
|
||
|
|
import { authService } from "../src/services/auth.service";
|
||
|
|
import FilialSelector from "./FilialSelector";
|
||
|
|
import { X } from "lucide-react";
|
||
|
|
|
||
|
|
interface Stock {
|
||
|
|
store: string;
|
||
|
|
storeName: string;
|
||
|
|
quantity: number;
|
||
|
|
work: boolean;
|
||
|
|
blocked: string;
|
||
|
|
breakdown: number;
|
||
|
|
transfer: number;
|
||
|
|
allowDelivery: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface ProductStoreDeliveryModalProps {
|
||
|
|
isOpen: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
onConfirm: (stockStore: string, deliveryType: string) => void;
|
||
|
|
product: Product | null;
|
||
|
|
quantity?: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ProductStoreDeliveryModal: React.FC<ProductStoreDeliveryModalProps> = ({
|
||
|
|
isOpen,
|
||
|
|
onClose,
|
||
|
|
onConfirm,
|
||
|
|
product,
|
||
|
|
quantity = 1,
|
||
|
|
}) => {
|
||
|
|
const [selectedStore, setSelectedStore] = useState<string>("");
|
||
|
|
const [deliveryType, setDeliveryType] = useState<string>("");
|
||
|
|
const [stocks, setStocks] = useState<Stock[]>([]);
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [errors, setErrors] = useState<{
|
||
|
|
stockStore?: string;
|
||
|
|
deliveryType?: string;
|
||
|
|
}>({});
|
||
|
|
|
||
|
|
// Tipos de entrega - seguindo EXATAMENTE o padrão do Angular
|
||
|
|
const deliveryTypes = [
|
||
|
|
{ type: "RI", description: "Retira Imediata" },
|
||
|
|
{ type: "RP", description: "Retira Posterior" },
|
||
|
|
{ type: "EN", description: "Entrega" },
|
||
|
|
{ type: "EF", description: "Encomenda" },
|
||
|
|
];
|
||
|
|
|
||
|
|
// Carregar estoques quando o modal abrir
|
||
|
|
useEffect(() => {
|
||
|
|
if (isOpen && product?.code) {
|
||
|
|
loadStocks();
|
||
|
|
} else {
|
||
|
|
// Resetar estados quando fechar
|
||
|
|
setSelectedStore("");
|
||
|
|
setDeliveryType("");
|
||
|
|
setStocks([]);
|
||
|
|
setErrors({});
|
||
|
|
}
|
||
|
|
}, [isOpen, product]);
|
||
|
|
|
||
|
|
const loadStocks = async () => {
|
||
|
|
if (!product?.code) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
setLoading(true);
|
||
|
|
const store = authService.getStore();
|
||
|
|
if (!store) {
|
||
|
|
throw new Error("Loja não encontrada. Faça login novamente.");
|
||
|
|
}
|
||
|
|
|
||
|
|
const productId = parseInt(product.code);
|
||
|
|
if (isNaN(productId)) {
|
||
|
|
throw new Error("ID do produto inválido");
|
||
|
|
}
|
||
|
|
|
||
|
|
const stocksData = await productService.getProductStocks(store, productId);
|
||
|
|
setStocks(stocksData);
|
||
|
|
|
||
|
|
// Selecionar a primeira loja por padrão se houver apenas uma
|
||
|
|
if (stocksData.length === 1) {
|
||
|
|
setSelectedStore(stocksData[0].store);
|
||
|
|
}
|
||
|
|
} catch (err: any) {
|
||
|
|
console.error("Erro ao carregar estoques:", err);
|
||
|
|
setErrors({ stockStore: "Erro ao carregar lojas de estoque" });
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleConfirm = () => {
|
||
|
|
const newErrors: { stockStore?: string; deliveryType?: string } = {};
|
||
|
|
|
||
|
|
// Validar loja de estoque
|
||
|
|
if (!selectedStore) {
|
||
|
|
newErrors.stockStore = "Selecione uma loja de estoque";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validar tipo de entrega
|
||
|
|
if (!deliveryType) {
|
||
|
|
newErrors.deliveryType = "Selecione um tipo de entrega";
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Object.keys(newErrors).length > 0) {
|
||
|
|
setErrors(newErrors);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Se tudo estiver válido, confirmar
|
||
|
|
onConfirm(selectedStore, deliveryType);
|
||
|
|
onClose();
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!isOpen || !product) return null;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="fixed inset-0 z-[200] flex items-center justify-center">
|
||
|
|
{/* Overlay */}
|
||
|
|
<div
|
||
|
|
className="absolute inset-0 bg-[#001f3f]/60 backdrop-blur-sm"
|
||
|
|
onClick={onClose}
|
||
|
|
/>
|
||
|
|
|
||
|
|
{/* Modal */}
|
||
|
|
<div className="relative bg-white rounded-3xl shadow-2xl max-w-md w-full mx-4 max-h-[90vh] flex flex-col">
|
||
|
|
{/* Header */}
|
||
|
|
<div className="p-4 lg:p-6 bg-[#002147] text-white rounded-t-3xl relative overflow-hidden flex items-center justify-between">
|
||
|
|
<div className="flex-1 pr-4">
|
||
|
|
<h3 className="text-lg font-black leading-tight">
|
||
|
|
{product.code} - {product.name}
|
||
|
|
</h3>
|
||
|
|
</div>
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="p-2 text-white/70 hover:text-white hover:bg-white/10 rounded-lg transition-colors touch-manipulation flex-shrink-0"
|
||
|
|
title="Fechar"
|
||
|
|
>
|
||
|
|
<X className="w-5 h-5" />
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Content */}
|
||
|
|
<div className="p-4 lg:p-6 overflow-y-auto flex-1">
|
||
|
|
<div className="mb-6">
|
||
|
|
<h4 className="text-base font-black text-orange-600 mb-4">
|
||
|
|
Selecione a loja de estoque e forma de entrega
|
||
|
|
</h4>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* LOCAL DE ESTOQUE */}
|
||
|
|
<div className="mb-6">
|
||
|
|
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wide mb-2">
|
||
|
|
LOCAL DE ESTOQUE
|
||
|
|
</label>
|
||
|
|
{loading ? (
|
||
|
|
<div className="flex items-center justify-center py-8">
|
||
|
|
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-orange-500"></div>
|
||
|
|
<span className="ml-2 text-sm text-slate-500">
|
||
|
|
Carregando lojas...
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
) : stocks.length > 0 ? (
|
||
|
|
<FilialSelector
|
||
|
|
stocks={stocks}
|
||
|
|
value={selectedStore}
|
||
|
|
onValueChange={(value) => {
|
||
|
|
setSelectedStore(value);
|
||
|
|
if (errors.stockStore) {
|
||
|
|
setErrors((prev) => ({ ...prev, stockStore: undefined }));
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
placeholder="Selecione a loja..."
|
||
|
|
label=""
|
||
|
|
className="w-full"
|
||
|
|
/>
|
||
|
|
) : (
|
||
|
|
<div className="p-4 bg-slate-50 border border-slate-200 rounded-lg">
|
||
|
|
<p className="text-sm text-slate-600">
|
||
|
|
Nenhuma loja de estoque disponível
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{errors.stockStore && (
|
||
|
|
<div className="flex items-center gap-1 mt-1 text-red-600 text-xs">
|
||
|
|
<span>{errors.stockStore}</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* TIPO DE ENTREGA */}
|
||
|
|
<div className="mb-6">
|
||
|
|
<label className="block text-xs font-bold text-slate-700 uppercase tracking-wide mb-3">
|
||
|
|
TIPO DE ENTREGA
|
||
|
|
</label>
|
||
|
|
<div className="space-y-3">
|
||
|
|
{deliveryTypes.map((dt) => (
|
||
|
|
<label
|
||
|
|
key={dt.type}
|
||
|
|
className="flex items-center p-3 border border-slate-200 rounded-lg cursor-pointer hover:bg-slate-50 transition-colors group"
|
||
|
|
>
|
||
|
|
<input
|
||
|
|
type="radio"
|
||
|
|
name="deliveryType"
|
||
|
|
value={dt.type}
|
||
|
|
checked={deliveryType === dt.type}
|
||
|
|
onChange={(e) => {
|
||
|
|
setDeliveryType(e.target.value);
|
||
|
|
if (errors.deliveryType) {
|
||
|
|
setErrors((prev) => ({
|
||
|
|
...prev,
|
||
|
|
deliveryType: undefined,
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}}
|
||
|
|
className="w-4 h-4 text-orange-500 border-slate-300 focus:ring-orange-500 focus:ring-2 cursor-pointer"
|
||
|
|
/>
|
||
|
|
<span className="ml-3 text-sm font-medium text-slate-700 group-hover:text-slate-900">
|
||
|
|
{dt.description}
|
||
|
|
</span>
|
||
|
|
</label>
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
{errors.deliveryType && (
|
||
|
|
<div className="flex items-center gap-1 mt-2 text-red-600 text-xs">
|
||
|
|
<span>{errors.deliveryType}</span>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<p className="text-xs text-slate-500 mt-3">
|
||
|
|
Informe a forma de entrega do produto
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Actions */}
|
||
|
|
<div className="p-4 lg:p-6 pt-0 flex gap-3">
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="flex-1 py-3 px-4 bg-white border-2 border-slate-300 text-slate-700 font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-slate-50 transition-all"
|
||
|
|
>
|
||
|
|
Cancelar
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleConfirm}
|
||
|
|
className="flex-1 py-3 px-4 bg-blue-600 text-white font-bold uppercase text-xs tracking-wider rounded-xl hover:bg-blue-700 transition-all shadow-lg"
|
||
|
|
>
|
||
|
|
Gravar
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default ProductStoreDeliveryModal;
|
||
|
|
|