418 lines
16 KiB
TypeScript
418 lines
16 KiB
TypeScript
import React, { useState, useEffect } from "react";
|
|
import { CustomerAddress } from "../../src/services/customer.service";
|
|
import {
|
|
MapPin,
|
|
Plus,
|
|
Pencil,
|
|
Trash2,
|
|
ArrowLeft,
|
|
ArrowRight,
|
|
CheckCircle,
|
|
} from "lucide-react";
|
|
import AddressSelectionModal from "./AddressSelectionModal";
|
|
import AddressFormModal from "./AddressFormModal";
|
|
import ConfirmDialog from "../ConfirmDialog";
|
|
|
|
interface AddressStepProps {
|
|
addressForm: {
|
|
zipCode: string;
|
|
address: string;
|
|
number: string;
|
|
city: string;
|
|
state: string;
|
|
complement: string;
|
|
referencePoint: string;
|
|
note: string;
|
|
};
|
|
addressErrors: Record<string, string>;
|
|
selectedAddress: CustomerAddress | null;
|
|
customerId: number | null;
|
|
onFormChange: (field: string, value: string) => void;
|
|
onShowAddressModal: () => void;
|
|
onRemoveAddress: () => void;
|
|
onSelectAddress: (address: CustomerAddress) => void;
|
|
onPrevious: () => void;
|
|
onNext: () => void;
|
|
}
|
|
|
|
const AddressStep: React.FC<AddressStepProps> = ({
|
|
addressForm,
|
|
addressErrors,
|
|
selectedAddress,
|
|
customerId,
|
|
onFormChange,
|
|
onShowAddressModal,
|
|
onRemoveAddress,
|
|
onSelectAddress,
|
|
onPrevious,
|
|
onNext,
|
|
}) => {
|
|
const [showSelectionModal, setShowSelectionModal] = useState(false);
|
|
const [showFormModal, setShowFormModal] = useState(false);
|
|
const [showRemoveDialog, setShowRemoveDialog] = useState(false);
|
|
const [editingAddress, setEditingAddress] = useState<CustomerAddress | null>(
|
|
null
|
|
);
|
|
|
|
const formatAddress = (address: CustomerAddress) => {
|
|
const parts = [];
|
|
if (address.address) parts.push(address.address);
|
|
if (address.number) parts.push(address.number);
|
|
if (address.complement) parts.push(address.complement);
|
|
return parts.join(", ");
|
|
};
|
|
|
|
const handleSelectFromList = () => {
|
|
if (!customerId) {
|
|
console.warn(
|
|
"AddressStep: customerId não fornecido. Não é possível abrir o modal de seleção de endereços."
|
|
);
|
|
return;
|
|
}
|
|
console.log(
|
|
"AddressStep: Abrindo modal de seleção de endereços para customerId:",
|
|
customerId
|
|
);
|
|
setShowSelectionModal(true);
|
|
};
|
|
|
|
const handleCreateNew = () => {
|
|
setEditingAddress(null);
|
|
setShowFormModal(true);
|
|
};
|
|
|
|
const handleEdit = () => {
|
|
if (selectedAddress) {
|
|
setEditingAddress(selectedAddress);
|
|
setShowFormModal(true);
|
|
}
|
|
};
|
|
|
|
const handleAddressSelected = (address: CustomerAddress) => {
|
|
onSelectAddress(address);
|
|
setShowSelectionModal(false);
|
|
};
|
|
|
|
const handleAddressSaved = (address: CustomerAddress) => {
|
|
onSelectAddress(address);
|
|
setShowFormModal(false);
|
|
setEditingAddress(null);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="animate-fade-in">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-slate-400">
|
|
Endereço de entrega
|
|
</h3>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={handleSelectFromList}
|
|
className="flex items-center gap-2 bg-slate-100 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest text-[#002147] hover:bg-slate-200 transition-colors"
|
|
>
|
|
<MapPin className="w-4 h-4" />
|
|
Selecionar
|
|
</button>
|
|
<button
|
|
onClick={handleCreateNew}
|
|
className="flex items-center gap-2 bg-slate-100 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest text-[#002147] hover:bg-slate-200 transition-colors"
|
|
>
|
|
<Plus className="w-4 h-4" />
|
|
Novo
|
|
</button>
|
|
{selectedAddress && (
|
|
<>
|
|
<button
|
|
onClick={handleEdit}
|
|
className="flex items-center gap-2 bg-[#002147] text-white px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest hover:bg-[#003366] transition-colors"
|
|
>
|
|
<Pencil className="w-4 h-4" />
|
|
Editar
|
|
</button>
|
|
<button
|
|
onClick={() => setShowRemoveDialog(true)}
|
|
className="flex items-center gap-2 bg-red-100 text-red-600 px-4 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest hover:bg-red-200 transition-colors"
|
|
>
|
|
<Trash2 className="w-4 h-4" />
|
|
Remover
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{selectedAddress ? (
|
|
/* Endereço Selecionado */
|
|
<div className="bg-gradient-to-br from-orange-50 to-orange-100/50 rounded-2xl p-6 border-2 border-orange-200 mb-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<CheckCircle className="w-5 h-5 text-green-500" />
|
|
<h4 className="font-black text-slate-800 text-lg">
|
|
{selectedAddress.addressType || "Endereço"}
|
|
</h4>
|
|
{selectedAddress.isPrimary && (
|
|
<span className="px-2 py-0.5 bg-green-100 text-green-700 rounded-full text-[10px] font-black uppercase">
|
|
Principal
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm font-bold text-slate-700 mb-2">
|
|
{formatAddress(selectedAddress)}
|
|
</p>
|
|
<p className="text-xs text-slate-600 mb-1">
|
|
{selectedAddress.neighborhood &&
|
|
`${selectedAddress.neighborhood} - `}
|
|
{selectedAddress.city && `${selectedAddress.city}`}
|
|
{selectedAddress.state && `/${selectedAddress.state}`}
|
|
</p>
|
|
{selectedAddress.zipCode && (
|
|
<p className="text-xs text-slate-600">
|
|
CEP: {selectedAddress.zipCode}
|
|
</p>
|
|
)}
|
|
{selectedAddress.referencePoint && (
|
|
<p className="text-xs text-slate-500 mt-2">
|
|
<strong>Referência:</strong>{" "}
|
|
{selectedAddress.referencePoint}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
/* Sem Endereço Selecionado */
|
|
<div className="bg-slate-50 rounded-2xl p-8 border-2 border-dashed border-slate-300 mb-6 text-center">
|
|
<div className="w-20 h-20 bg-slate-200 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<MapPin className="w-10 h-10 text-slate-400" />
|
|
</div>
|
|
<p className="text-lg font-bold text-slate-600 mb-2">
|
|
Nenhum endereço selecionado
|
|
</p>
|
|
<p className="text-sm text-slate-500 mb-6">
|
|
Selecione um endereço existente ou cadastre um novo endereço de
|
|
entrega
|
|
</p>
|
|
<div className="flex gap-3 justify-center">
|
|
<button
|
|
onClick={handleSelectFromList}
|
|
className="flex items-center gap-2 bg-[#002147] text-white px-6 py-3 rounded-xl font-black uppercase text-xs tracking-widest hover:bg-[#001a36] transition-all"
|
|
>
|
|
<MapPin className="w-4 h-4" />
|
|
Selecionar Endereço
|
|
</button>
|
|
<button
|
|
onClick={handleCreateNew}
|
|
className="flex items-center gap-2 bg-white border-2 border-[#002147] text-[#002147] px-6 py-3 rounded-xl font-black uppercase text-xs tracking-widest hover:bg-[#002147] hover:text-white transition-all"
|
|
>
|
|
<Plus className="w-4 h-4" />
|
|
Cadastrar Novo
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Formulário de Endereço (apenas quando há endereço selecionado) */}
|
|
{selectedAddress && (
|
|
<form className="space-y-4 mt-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
CEP
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.zipCode}
|
|
onChange={(e) => onFormChange("zipCode", e.target.value)}
|
|
className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${
|
|
addressErrors.zipCode
|
|
? "border-red-500"
|
|
: "border-slate-200"
|
|
} focus:outline-none focus:ring-2 focus:ring-orange-500/20`}
|
|
/>
|
|
{addressErrors.zipCode && (
|
|
<p className="text-red-500 text-xs mt-1">
|
|
{addressErrors.zipCode}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
endereço
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.address}
|
|
onChange={(e) => onFormChange("address", e.target.value)}
|
|
className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${
|
|
addressErrors.address
|
|
? "border-red-500"
|
|
: "border-slate-200"
|
|
} focus:outline-none focus:ring-2 focus:ring-orange-500/20`}
|
|
/>
|
|
{addressErrors.address && (
|
|
<p className="text-red-500 text-xs mt-1">
|
|
{addressErrors.address}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
número
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.number}
|
|
onChange={(e) => onFormChange("number", e.target.value)}
|
|
className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${
|
|
addressErrors.number ? "border-red-500" : "border-slate-200"
|
|
} focus:outline-none focus:ring-2 focus:ring-orange-500/20`}
|
|
/>
|
|
{addressErrors.number && (
|
|
<p className="text-red-500 text-xs mt-1">
|
|
{addressErrors.number}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
cidade
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.city}
|
|
onChange={(e) => onFormChange("city", e.target.value)}
|
|
className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${
|
|
addressErrors.city ? "border-red-500" : "border-slate-200"
|
|
} focus:outline-none focus:ring-2 focus:ring-orange-500/20`}
|
|
/>
|
|
{addressErrors.city && (
|
|
<p className="text-red-500 text-xs mt-1">
|
|
{addressErrors.city}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
estado
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.state}
|
|
onChange={(e) => onFormChange("state", e.target.value)}
|
|
className={`w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border ${
|
|
addressErrors.state ? "border-red-500" : "border-slate-200"
|
|
} focus:outline-none focus:ring-2 focus:ring-orange-500/20`}
|
|
/>
|
|
{addressErrors.state && (
|
|
<p className="text-red-500 text-xs mt-1">
|
|
{addressErrors.state}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
complemento
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.complement}
|
|
onChange={(e) => onFormChange("complement", e.target.value)}
|
|
className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
Ponto de referência
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.referencePoint}
|
|
onChange={(e) => onFormChange("referencePoint", e.target.value)}
|
|
className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-[10px] font-black uppercase text-slate-400 mb-2">
|
|
observações
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={addressForm.note}
|
|
onChange={(e) => onFormChange("note", e.target.value)}
|
|
className="w-full px-4 py-3 bg-slate-50 rounded-xl font-bold text-[#002147] border border-slate-200 focus:outline-none focus:ring-2 focus:ring-orange-500/20"
|
|
/>
|
|
</div>
|
|
</form>
|
|
)}
|
|
|
|
<div className="flex justify-between gap-2 mt-6">
|
|
<button
|
|
type="button"
|
|
onClick={onPrevious}
|
|
className="flex items-center gap-2 bg-slate-100 px-6 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest text-[#002147] hover:bg-slate-200 transition-colors"
|
|
>
|
|
<ArrowLeft className="w-4 h-4" />
|
|
Retornar
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={onNext}
|
|
className="flex items-center gap-2 bg-[#002147] text-white px-6 py-2 rounded-lg text-[10px] font-black uppercase tracking-widest hover:bg-[#003366] transition-colors"
|
|
>
|
|
Avançar
|
|
<ArrowRight className="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modais */}
|
|
<AddressSelectionModal
|
|
isOpen={showSelectionModal}
|
|
customerId={customerId}
|
|
onClose={() => setShowSelectionModal(false)}
|
|
onSelect={handleAddressSelected}
|
|
onCreateNew={() => {
|
|
setShowSelectionModal(false);
|
|
handleCreateNew();
|
|
}}
|
|
/>
|
|
|
|
<AddressFormModal
|
|
isOpen={showFormModal}
|
|
customerId={customerId}
|
|
address={editingAddress}
|
|
onClose={() => {
|
|
setShowFormModal(false);
|
|
setEditingAddress(null);
|
|
}}
|
|
onSave={handleAddressSaved}
|
|
/>
|
|
|
|
<ConfirmDialog
|
|
isOpen={showRemoveDialog}
|
|
onClose={() => setShowRemoveDialog(false)}
|
|
onConfirm={() => {
|
|
onRemoveAddress();
|
|
setShowRemoveDialog(false);
|
|
}}
|
|
type="delete"
|
|
title="Remover Endereço"
|
|
message="Tem certeza que deseja remover este endereço de entrega? Esta ação não pode ser desfeita."
|
|
confirmText="Remover"
|
|
cancelText="Cancelar"
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default AddressStep;
|