Feat: Selector de Contacto Independiente y Formato de Precio
- Se divide la selección de medios de contacto entre los datos del usuario, permitiendo mostras el tipo de contacto que prefiera. - Cuando el precio es igual a 0, se muestra la palabra "Consultar" en lugar de $0 o ARS 0.
This commit is contained in:
@@ -326,6 +326,9 @@ public class AdsV2Controller : ControllerBase
|
|||||||
contactPhone = ad.ContactPhone,
|
contactPhone = ad.ContactPhone,
|
||||||
contactEmail = ad.ContactEmail,
|
contactEmail = ad.ContactEmail,
|
||||||
displayContactInfo = ad.DisplayContactInfo,
|
displayContactInfo = ad.DisplayContactInfo,
|
||||||
|
showPhone = ad.ShowPhone,
|
||||||
|
allowWhatsApp = ad.AllowWhatsApp,
|
||||||
|
showEmail = ad.ShowEmail,
|
||||||
photos = ad.Photos.Select(p => new { p.PhotoID, p.FilePath, p.IsCover, p.SortOrder }),
|
photos = ad.Photos.Select(p => new { p.PhotoID, p.FilePath, p.IsCover, p.SortOrder }),
|
||||||
features = ad.Features.Select(f => new { f.FeatureKey, f.FeatureValue }),
|
features = ad.Features.Select(f => new { f.FeatureKey, f.FeatureValue }),
|
||||||
brand = ad.Brand != null ? new { id = ad.Brand.BrandID, name = ad.Brand.Name } : null,
|
brand = ad.Brand != null ? new { id = ad.Brand.BrandID, name = ad.Brand.Name } : null,
|
||||||
@@ -432,6 +435,9 @@ public class AdsV2Controller : ControllerBase
|
|||||||
ContactPhone = request.ContactPhone,
|
ContactPhone = request.ContactPhone,
|
||||||
ContactEmail = request.ContactEmail,
|
ContactEmail = request.ContactEmail,
|
||||||
DisplayContactInfo = request.DisplayContactInfo,
|
DisplayContactInfo = request.DisplayContactInfo,
|
||||||
|
ShowPhone = request.ShowPhone,
|
||||||
|
AllowWhatsApp = request.AllowWhatsApp,
|
||||||
|
ShowEmail = request.ShowEmail,
|
||||||
|
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
@@ -655,6 +661,9 @@ public class AdsV2Controller : ControllerBase
|
|||||||
ad.ContactPhone = updatedAdDto.ContactPhone;
|
ad.ContactPhone = updatedAdDto.ContactPhone;
|
||||||
ad.ContactEmail = updatedAdDto.ContactEmail;
|
ad.ContactEmail = updatedAdDto.ContactEmail;
|
||||||
ad.DisplayContactInfo = updatedAdDto.DisplayContactInfo;
|
ad.DisplayContactInfo = updatedAdDto.DisplayContactInfo;
|
||||||
|
ad.ShowPhone = updatedAdDto.ShowPhone;
|
||||||
|
ad.AllowWhatsApp = updatedAdDto.AllowWhatsApp;
|
||||||
|
ad.ShowEmail = updatedAdDto.ShowEmail;
|
||||||
// Nota: IsFeatured y otros campos sensibles se manejan por separado (pago/admin)
|
// Nota: IsFeatured y otros campos sensibles se manejan por separado (pago/admin)
|
||||||
|
|
||||||
// LÓGICA DE ESTADO TRAS RECHAZO
|
// LÓGICA DE ESTADO TRAS RECHAZO
|
||||||
|
|||||||
@@ -95,6 +95,15 @@ public class CreateAdRequestDto
|
|||||||
[JsonPropertyName("displayContactInfo")]
|
[JsonPropertyName("displayContactInfo")]
|
||||||
public bool DisplayContactInfo { get; set; } = true;
|
public bool DisplayContactInfo { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonPropertyName("showPhone")]
|
||||||
|
public bool ShowPhone { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonPropertyName("allowWhatsApp")]
|
||||||
|
public bool AllowWhatsApp { get; set; } = true;
|
||||||
|
|
||||||
|
[JsonPropertyName("showEmail")]
|
||||||
|
public bool ShowEmail { get; set; } = true;
|
||||||
|
|
||||||
// --- Admin Only ---
|
// --- Admin Only ---
|
||||||
|
|
||||||
[JsonPropertyName("targetUserID")]
|
[JsonPropertyName("targetUserID")]
|
||||||
|
|||||||
@@ -125,6 +125,11 @@ public class Ad
|
|||||||
public string? ContactPhone { get; set; }
|
public string? ContactPhone { get; set; }
|
||||||
public string? ContactEmail { get; set; }
|
public string? ContactEmail { get; set; }
|
||||||
public bool DisplayContactInfo { get; set; } = true;
|
public bool DisplayContactInfo { get; set; } = true;
|
||||||
|
|
||||||
|
public bool ShowPhone { get; set; } = true;
|
||||||
|
public bool AllowWhatsApp { get; set; } = true;
|
||||||
|
public bool ShowEmail { get; set; } = true;
|
||||||
|
|
||||||
public bool IsFeatured { get; set; }
|
public bool IsFeatured { get; set; }
|
||||||
|
|
||||||
public int StatusID { get; set; }
|
public int StatusID { get; set; }
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from "react";
|
||||||
import { useSearchParams, Link } from 'react-router-dom';
|
import { useSearchParams, Link } from "react-router-dom";
|
||||||
import { AdsV2Service, type AdListingDto } from '../services/ads.v2.service';
|
import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
||||||
import { getImageUrl, formatCurrency } from '../utils/app.utils';
|
import { getImageUrl, formatCurrency } from "../utils/app.utils";
|
||||||
import SearchableSelect from '../components/SearchableSelect';
|
import SearchableSelect from "../components/SearchableSelect";
|
||||||
import AdStatusBadge from '../components/AdStatusBadge';
|
import AdStatusBadge from "../components/AdStatusBadge";
|
||||||
import {
|
import {
|
||||||
AUTO_SEGMENTS,
|
AUTO_SEGMENTS,
|
||||||
MOTO_SEGMENTS,
|
MOTO_SEGMENTS,
|
||||||
AUTO_TRANSMISSIONS,
|
AUTO_TRANSMISSIONS,
|
||||||
MOTO_TRANSMISSIONS,
|
MOTO_TRANSMISSIONS,
|
||||||
FUEL_TYPES,
|
FUEL_TYPES,
|
||||||
VEHICLE_CONDITIONS
|
VEHICLE_CONDITIONS,
|
||||||
} from '../constants/vehicleOptions';
|
} from "../constants/vehicleOptions";
|
||||||
|
|
||||||
export default function ExplorarPage() {
|
export default function ExplorarPage() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
@@ -19,25 +19,29 @@ export default function ExplorarPage() {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [minPrice, setMinPrice] = useState(searchParams.get('minPrice') || '');
|
const [minPrice, setMinPrice] = useState(searchParams.get("minPrice") || "");
|
||||||
const [maxPrice, setMaxPrice] = useState(searchParams.get('maxPrice') || '');
|
const [maxPrice, setMaxPrice] = useState(searchParams.get("maxPrice") || "");
|
||||||
const [currencyFilter, setCurrencyFilter] = useState(searchParams.get('currency') || '');
|
const [currencyFilter, setCurrencyFilter] = useState(
|
||||||
const [minYear, setMinYear] = useState(searchParams.get('minYear') || '');
|
searchParams.get("currency") || "",
|
||||||
const [maxYear, setMaxYear] = useState(searchParams.get('maxYear') || '');
|
);
|
||||||
const [brandId, setBrandId] = useState(searchParams.get('brandId') || '');
|
const [minYear, setMinYear] = useState(searchParams.get("minYear") || "");
|
||||||
const [modelId, setModelId] = useState(searchParams.get('modelId') || '');
|
const [maxYear, setMaxYear] = useState(searchParams.get("maxYear") || "");
|
||||||
const [fuel, setFuel] = useState(searchParams.get('fuel') || '');
|
const [brandId, setBrandId] = useState(searchParams.get("brandId") || "");
|
||||||
const [transmission, setTransmission] = useState(searchParams.get('transmission') || '');
|
const [modelId, setModelId] = useState(searchParams.get("modelId") || "");
|
||||||
|
const [fuel, setFuel] = useState(searchParams.get("fuel") || "");
|
||||||
|
const [transmission, setTransmission] = useState(
|
||||||
|
searchParams.get("transmission") || "",
|
||||||
|
);
|
||||||
|
|
||||||
const [brands, setBrands] = useState<{ id: number, name: string }[]>([]);
|
const [brands, setBrands] = useState<{ id: number; name: string }[]>([]);
|
||||||
const [models, setModels] = useState<{ id: number, name: string }[]>([]);
|
const [models, setModels] = useState<{ id: number; name: string }[]>([]);
|
||||||
|
|
||||||
const q = searchParams.get('q') || '';
|
const q = searchParams.get("q") || "";
|
||||||
const c = searchParams.get('c') || 'ALL';
|
const c = searchParams.get("c") || "ALL";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (c !== 'ALL') {
|
if (c !== "ALL") {
|
||||||
const typeId = c === 'EAUTOS' ? 1 : 2;
|
const typeId = c === "EAUTOS" ? 1 : 2;
|
||||||
AdsV2Service.getBrands(typeId).then(setBrands);
|
AdsV2Service.getBrands(typeId).then(setBrands);
|
||||||
} else {
|
} else {
|
||||||
setBrands([]);
|
setBrands([]);
|
||||||
@@ -61,7 +65,7 @@ export default function ExplorarPage() {
|
|||||||
try {
|
try {
|
||||||
const data = await AdsV2Service.getAll({
|
const data = await AdsV2Service.getAll({
|
||||||
q,
|
q,
|
||||||
c: c === 'ALL' ? undefined : c,
|
c: c === "ALL" ? undefined : c,
|
||||||
minPrice: minPrice ? Number(minPrice) : undefined,
|
minPrice: minPrice ? Number(minPrice) : undefined,
|
||||||
maxPrice: maxPrice ? Number(maxPrice) : undefined,
|
maxPrice: maxPrice ? Number(maxPrice) : undefined,
|
||||||
currency: currencyFilter || undefined,
|
currency: currencyFilter || undefined,
|
||||||
@@ -70,7 +74,7 @@ export default function ExplorarPage() {
|
|||||||
brandId: brandId ? Number(brandId) : undefined,
|
brandId: brandId ? Number(brandId) : undefined,
|
||||||
modelId: modelId ? Number(modelId) : undefined,
|
modelId: modelId ? Number(modelId) : undefined,
|
||||||
fuel: fuel || undefined,
|
fuel: fuel || undefined,
|
||||||
transmission: transmission || undefined
|
transmission: transmission || undefined,
|
||||||
});
|
});
|
||||||
setListings(data);
|
setListings(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -86,32 +90,47 @@ export default function ExplorarPage() {
|
|||||||
|
|
||||||
const applyFilters = () => {
|
const applyFilters = () => {
|
||||||
const newParams = new URLSearchParams(searchParams);
|
const newParams = new URLSearchParams(searchParams);
|
||||||
if (minPrice) newParams.set('minPrice', minPrice); else newParams.delete('minPrice');
|
if (minPrice) newParams.set("minPrice", minPrice);
|
||||||
if (maxPrice) newParams.set('maxPrice', maxPrice); else newParams.delete('maxPrice');
|
else newParams.delete("minPrice");
|
||||||
if (currencyFilter) newParams.set('currency', currencyFilter); else newParams.delete('currency');
|
if (maxPrice) newParams.set("maxPrice", maxPrice);
|
||||||
if (minYear) newParams.set('minYear', minYear); else newParams.delete('minYear');
|
else newParams.delete("maxPrice");
|
||||||
if (maxYear) newParams.set('maxYear', maxYear); else newParams.delete('maxYear');
|
if (currencyFilter) newParams.set("currency", currencyFilter);
|
||||||
if (brandId) newParams.set('brandId', brandId); else newParams.delete('brandId');
|
else newParams.delete("currency");
|
||||||
if (modelId) newParams.set('modelId', modelId); else newParams.delete('modelId');
|
if (minYear) newParams.set("minYear", minYear);
|
||||||
if (fuel) newParams.set('fuel', fuel); else newParams.delete('fuel');
|
else newParams.delete("minYear");
|
||||||
if (transmission) newParams.set('transmission', transmission); else newParams.delete('transmission');
|
if (maxYear) newParams.set("maxYear", maxYear);
|
||||||
|
else newParams.delete("maxYear");
|
||||||
|
if (brandId) newParams.set("brandId", brandId);
|
||||||
|
else newParams.delete("brandId");
|
||||||
|
if (modelId) newParams.set("modelId", modelId);
|
||||||
|
else newParams.delete("modelId");
|
||||||
|
if (fuel) newParams.set("fuel", fuel);
|
||||||
|
else newParams.delete("fuel");
|
||||||
|
if (transmission) newParams.set("transmission", transmission);
|
||||||
|
else newParams.delete("transmission");
|
||||||
setSearchParams(newParams);
|
setSearchParams(newParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
setMinPrice(''); setMaxPrice(''); setMinYear(''); setMaxYear('');
|
setMinPrice("");
|
||||||
setCurrencyFilter('');
|
setMaxPrice("");
|
||||||
setBrandId(''); setModelId(''); setFuel(''); setTransmission('');
|
setMinYear("");
|
||||||
|
setMaxYear("");
|
||||||
|
setCurrencyFilter("");
|
||||||
|
setBrandId("");
|
||||||
|
setModelId("");
|
||||||
|
setFuel("");
|
||||||
|
setTransmission("");
|
||||||
const newParams = new URLSearchParams();
|
const newParams = new URLSearchParams();
|
||||||
if (q) newParams.set('q', q);
|
if (q) newParams.set("q", q);
|
||||||
if (c !== 'ALL') newParams.set('c', c);
|
if (c !== "ALL") newParams.set("c", c);
|
||||||
setSearchParams(newParams);
|
setSearchParams(newParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCategoryFilter = (cat: string) => {
|
const handleCategoryFilter = (cat: string) => {
|
||||||
const newParams = new URLSearchParams();
|
const newParams = new URLSearchParams();
|
||||||
if (q) newParams.set('q', q);
|
if (q) newParams.set("q", q);
|
||||||
if (cat !== 'ALL') newParams.set('c', cat);
|
if (cat !== "ALL") newParams.set("c", cat);
|
||||||
setSearchParams(newParams);
|
setSearchParams(newParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,23 +138,29 @@ export default function ExplorarPage() {
|
|||||||
<div className="container mx-auto px-2 md:px-6 py-4 md:py-8 flex flex-col md:flex-row gap-6 md:gap-8 relative items-start">
|
<div className="container mx-auto px-2 md:px-6 py-4 md:py-8 flex flex-col md:flex-row gap-6 md:gap-8 relative items-start">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowMobileFilters(true)}
|
onClick={() => setShowMobileFilters(true)}
|
||||||
className={`md:hidden fixed bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 z-[110] bg-blue-600 text-white px-6 md:px-8 py-3 md:py-4 rounded-xl md:rounded-2xl font-black uppercase tracking-widest shadow-2xl shadow-blue-600/40 border border-white/20 active:scale-95 transition-all flex items-center gap-2 md:gap-3 text-sm ${showMobileFilters ? 'opacity-0 pointer-events-none translate-y-20' : 'opacity-100 translate-y-0'}`}
|
className={`md:hidden fixed bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 z-[110] bg-blue-600 text-white px-6 md:px-8 py-3 md:py-4 rounded-xl md:rounded-2xl font-black uppercase tracking-widest shadow-2xl shadow-blue-600/40 border border-white/20 active:scale-95 transition-all flex items-center gap-2 md:gap-3 text-sm ${showMobileFilters ? "opacity-0 pointer-events-none translate-y-20" : "opacity-100 translate-y-0"}`}
|
||||||
>
|
>
|
||||||
<span>🔍 FILTRAR</span>
|
<span>🔍 FILTRAR</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Sidebar Filters - NATURAL FLOW (NO STICKY, NO SCROLL INTERNO) */}
|
{/* Sidebar Filters - NATURAL FLOW (NO STICKY, NO SCROLL INTERNO) */}
|
||||||
<aside className={`
|
<aside
|
||||||
|
className={`
|
||||||
fixed inset-0 z-[105] bg-black/80 backdrop-blur-xl transition-all duration-500 overflow-y-auto md:overflow-visible
|
fixed inset-0 z-[105] bg-black/80 backdrop-blur-xl transition-all duration-500 overflow-y-auto md:overflow-visible
|
||||||
md:relative md:inset-auto md:bg-transparent md:backdrop-blur-none md:z-0 md:w-80 md:flex flex-col md:flex-shrink-0
|
md:relative md:inset-auto md:bg-transparent md:backdrop-blur-none md:z-0 md:w-80 md:flex flex-col md:flex-shrink-0
|
||||||
${showMobileFilters ? 'opacity-100 pointer-events-auto translate-y-0' : 'opacity-0 pointer-events-none translate-y-10 md:opacity-100 md:pointer-events-auto md:translate-y-0'}
|
${showMobileFilters ? "opacity-100 pointer-events-auto translate-y-0" : "opacity-0 pointer-events-none translate-y-10 md:opacity-100 md:pointer-events-auto md:translate-y-0"}
|
||||||
`}>
|
`}
|
||||||
<div className="
|
>
|
||||||
|
<div
|
||||||
|
className="
|
||||||
glass p-6 rounded-[2rem] border border-white/5 shadow-2xl
|
glass p-6 rounded-[2rem] border border-white/5 shadow-2xl
|
||||||
h-fit m-6 mt-28 md:m-0
|
h-fit m-6 mt-28 md:m-0
|
||||||
">
|
"
|
||||||
|
>
|
||||||
<div className="flex justify-between items-center mb-6 border-b border-white/5 pb-4">
|
<div className="flex justify-between items-center mb-6 border-b border-white/5 pb-4">
|
||||||
<h3 className="text-xl font-black tracking-tighter uppercase">FILTROS</h3>
|
<h3 className="text-xl font-black tracking-tighter uppercase">
|
||||||
|
FILTROS
|
||||||
|
</h3>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={clearFilters}
|
onClick={clearFilters}
|
||||||
@@ -155,22 +180,32 @@ export default function ExplorarPage() {
|
|||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
{/* Categoría */}
|
{/* Categoría */}
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Tipo de Vehículo</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
|
Tipo de Vehículo
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
value={c}
|
value={c}
|
||||||
onChange={(e) => handleCategoryFilter(e.target.value)}
|
onChange={(e) => handleCategoryFilter(e.target.value)}
|
||||||
className="w-full bg-blue-600/10 border border-blue-500/30 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none font-bold uppercase tracking-wide cursor-pointer hover:bg-blue-600/20"
|
className="w-full bg-blue-600/10 border border-blue-500/30 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none font-bold uppercase tracking-wide cursor-pointer hover:bg-blue-600/20"
|
||||||
>
|
>
|
||||||
<option value="ALL" className="bg-gray-900">Todos</option>
|
<option value="ALL" className="bg-gray-900">
|
||||||
<option value="EAUTOS" className="bg-gray-900">Automóviles</option>
|
Todos
|
||||||
<option value="EMOTOS" className="bg-gray-900">Motos</option>
|
</option>
|
||||||
|
<option value="EAUTOS" className="bg-gray-900">
|
||||||
|
Automóviles
|
||||||
|
</option>
|
||||||
|
<option value="EMOTOS" className="bg-gray-900">
|
||||||
|
Motos
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{c !== 'ALL' && (
|
{c !== "ALL" && (
|
||||||
<div className="space-y-4 animate-fade-in">
|
<div className="space-y-4 animate-fade-in">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Marca</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
|
Marca
|
||||||
|
</label>
|
||||||
<SearchableSelect
|
<SearchableSelect
|
||||||
options={brands}
|
options={brands}
|
||||||
value={brandId}
|
value={brandId}
|
||||||
@@ -181,15 +216,25 @@ export default function ExplorarPage() {
|
|||||||
|
|
||||||
{brandId && (
|
{brandId && (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Modelo</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
|
Modelo
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
value={modelId}
|
value={modelId}
|
||||||
onChange={e => setModelId(e.target.value)}
|
onChange={(e) => setModelId(e.target.value)}
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none"
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none"
|
||||||
>
|
>
|
||||||
<option value="" className="bg-gray-900 text-gray-500">Todos los modelos</option>
|
<option value="" className="bg-gray-900 text-gray-500">
|
||||||
{models.map(m => (
|
Todos los modelos
|
||||||
<option key={m.id} value={m.id} className="bg-gray-900 text-white">{m.name}</option>
|
</option>
|
||||||
|
{models.map((m) => (
|
||||||
|
<option
|
||||||
|
key={m.id}
|
||||||
|
value={m.id}
|
||||||
|
className="bg-gray-900 text-white"
|
||||||
|
>
|
||||||
|
{m.name}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -198,85 +243,231 @@ export default function ExplorarPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Moneda</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
|
Moneda
|
||||||
|
</label>
|
||||||
<select
|
<select
|
||||||
value={currencyFilter}
|
value={currencyFilter}
|
||||||
onChange={e => setCurrencyFilter(e.target.value)}
|
onChange={(e) => setCurrencyFilter(e.target.value)}
|
||||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none cursor-pointer"
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none cursor-pointer"
|
||||||
>
|
>
|
||||||
<option value="" className="bg-gray-900 text-gray-500">Indistinto</option>
|
<option value="" className="bg-gray-900 text-gray-500">
|
||||||
<option value="ARS" className="bg-gray-900 text-white">Pesos (ARS)</option>
|
Indistinto
|
||||||
<option value="USD" className="bg-gray-900 text-white">Dólares (USD)</option>
|
</option>
|
||||||
|
<option value="ARS" className="bg-gray-900 text-white">
|
||||||
|
Pesos (ARS)
|
||||||
|
</option>
|
||||||
|
<option value="USD" className="bg-gray-900 text-white">
|
||||||
|
Dólares (USD)
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Precio Máximo</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: 25000" type="number" value={maxPrice} onChange={e => setMaxPrice(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all" />
|
Precio Máximo
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: 25000"
|
||||||
|
type="number"
|
||||||
|
value={maxPrice}
|
||||||
|
onChange={(e) => setMaxPrice(e.target.value)}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Desde Año</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: 2018" type="number" value={minYear} onChange={e => setMinYear(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500" />
|
Desde Año
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: 2018"
|
||||||
|
type="number"
|
||||||
|
value={minYear}
|
||||||
|
onChange={(e) => setMinYear(e.target.value)}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Combustible</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<select value={fuel} onChange={e => setFuel(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none">
|
Combustible
|
||||||
<option value="" className="bg-gray-900 text-gray-500">Todos</option>
|
</label>
|
||||||
{FUEL_TYPES.map(f => (<option key={f} value={f} className="bg-gray-900 text-white">{f}</option>))}
|
<select
|
||||||
|
value={fuel}
|
||||||
|
onChange={(e) => setFuel(e.target.value)}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none"
|
||||||
|
>
|
||||||
|
<option value="" className="bg-gray-900 text-gray-500">
|
||||||
|
Todos
|
||||||
|
</option>
|
||||||
|
{FUEL_TYPES.map((f) => (
|
||||||
|
<option
|
||||||
|
key={f}
|
||||||
|
value={f}
|
||||||
|
className="bg-gray-900 text-white"
|
||||||
|
>
|
||||||
|
{f}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Transmisión</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<select value={transmission} onChange={e => setTransmission(e.target.value)} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none">
|
Transmisión
|
||||||
<option value="" className="bg-gray-900 text-gray-500">Todas</option>
|
</label>
|
||||||
{(c === 'EMOTOS' ? MOTO_TRANSMISSIONS : AUTO_TRANSMISSIONS).map(t => (
|
<select
|
||||||
<option key={t} value={t} className="bg-gray-900 text-white">{t}</option>
|
value={transmission}
|
||||||
|
onChange={(e) => setTransmission(e.target.value)}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 transition-all appearance-none"
|
||||||
|
>
|
||||||
|
<option value="" className="bg-gray-900 text-gray-500">
|
||||||
|
Todas
|
||||||
|
</option>
|
||||||
|
{(c === "EMOTOS"
|
||||||
|
? MOTO_TRANSMISSIONS
|
||||||
|
: AUTO_TRANSMISSIONS
|
||||||
|
).map((t) => (
|
||||||
|
<option
|
||||||
|
key={t}
|
||||||
|
value={t}
|
||||||
|
className="bg-gray-900 text-white"
|
||||||
|
>
|
||||||
|
{t}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Color</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: Blanco" type="text" value={searchParams.get('color') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('color', e.target.value); else p.delete('color'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500" />
|
Color
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: Blanco"
|
||||||
|
type="text"
|
||||||
|
value={searchParams.get("color") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("color", e.target.value);
|
||||||
|
else p.delete("color");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Ubicación</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: Buenos Aires" type="text" value={searchParams.get('location') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('location', e.target.value); else p.delete('location'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500" />
|
Ubicación
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: Buenos Aires"
|
||||||
|
type="text"
|
||||||
|
value={searchParams.get("location") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("location", e.target.value);
|
||||||
|
else p.delete("location");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={`grid ${c === 'EMOTOS' ? 'grid-cols-1' : 'grid-cols-2'} gap-2`}>
|
<div
|
||||||
|
className={`grid ${c === "EMOTOS" ? "grid-cols-1" : "grid-cols-2"} gap-2`}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Estado</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<select value={searchParams.get('condition') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('condition', e.target.value); else p.delete('condition'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 appearance-none cursor-pointer">
|
Estado
|
||||||
<option value="" className="bg-gray-900">Todos</option>
|
</label>
|
||||||
{VEHICLE_CONDITIONS.map(o => <option key={o} value={o} className="bg-gray-900">{o}</option>)}
|
<select
|
||||||
|
value={searchParams.get("condition") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("condition", e.target.value);
|
||||||
|
else p.delete("condition");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 appearance-none cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="" className="bg-gray-900">
|
||||||
|
Todos
|
||||||
|
</option>
|
||||||
|
{VEHICLE_CONDITIONS.map((o) => (
|
||||||
|
<option key={o} value={o} className="bg-gray-900">
|
||||||
|
{o}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Segmento</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<select value={searchParams.get('segment') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('segment', e.target.value); else p.delete('segment'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 appearance-none cursor-pointer">
|
Segmento
|
||||||
<option value="" className="bg-gray-900">Todos</option>
|
</label>
|
||||||
{(c === 'EMOTOS' ? MOTO_SEGMENTS : AUTO_SEGMENTS).map(o => (
|
<select
|
||||||
<option key={o} value={o} className="bg-gray-900">{o}</option>
|
value={searchParams.get("segment") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("segment", e.target.value);
|
||||||
|
else p.delete("segment");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500 appearance-none cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="" className="bg-gray-900">
|
||||||
|
Todos
|
||||||
|
</option>
|
||||||
|
{(c === "EMOTOS" ? MOTO_SEGMENTS : AUTO_SEGMENTS).map((o) => (
|
||||||
|
<option key={o} value={o} className="bg-gray-900">
|
||||||
|
{o}
|
||||||
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{c !== 'EMOTOS' && (
|
{c !== "EMOTOS" && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Puertas</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: 4" type="number" value={searchParams.get('doorCount') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('doorCount', e.target.value); else p.delete('doorCount'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500" />
|
Puertas
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: 4"
|
||||||
|
type="number"
|
||||||
|
value={searchParams.get("doorCount") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("doorCount", e.target.value);
|
||||||
|
else p.delete("doorCount");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Dirección</label>
|
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||||
<input placeholder="Ej: Hidráulica" type="text" value={searchParams.get('steering') || ''} onChange={e => { const p = new URLSearchParams(searchParams); if (e.target.value) p.set('steering', e.target.value); else p.delete('steering'); setSearchParams(p); }} className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500" />
|
Dirección
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
placeholder="Ej: Hidráulica"
|
||||||
|
type="text"
|
||||||
|
value={searchParams.get("steering") || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const p = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) p.set("steering", e.target.value);
|
||||||
|
else p.delete("steering");
|
||||||
|
setSearchParams(p);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-xs text-white outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button onClick={applyFilters} className="w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-blue-600/20 active:scale-95 mb-4 mt-6">
|
<button
|
||||||
|
onClick={applyFilters}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-500 text-white py-4 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all shadow-lg shadow-blue-600/20 active:scale-95 mb-4 mt-6"
|
||||||
|
>
|
||||||
Aplicar Filtros
|
Aplicar Filtros
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -285,10 +476,25 @@ export default function ExplorarPage() {
|
|||||||
<div className="w-full md:flex-1 md:min-w-0">
|
<div className="w-full md:flex-1 md:min-w-0">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 md:mb-8 gap-4 md:gap-6">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 md:mb-8 gap-4 md:gap-6">
|
||||||
<div className="flex-1 w-full">
|
<div className="flex-1 w-full">
|
||||||
<h2 className="text-3xl md:text-4xl font-black tracking-tighter uppercase mb-2 md:mb-0">Explorar</h2>
|
<h2 className="text-3xl md:text-4xl font-black tracking-tighter uppercase mb-2 md:mb-0">
|
||||||
|
Explorar
|
||||||
|
</h2>
|
||||||
<div className="mt-3 md:mt-4 relative max-w-xl group">
|
<div className="mt-3 md:mt-4 relative max-w-xl group">
|
||||||
<input type="text" placeholder="Buscar por marca, modelo o versión..." value={q} onChange={e => { const newParams = new URLSearchParams(searchParams); if (e.target.value) newParams.set('q', e.target.value); else newParams.delete('q'); setSearchParams(newParams); }} className="w-full bg-white/5 border border-white/10 rounded-xl md:rounded-2xl px-10 md:px-12 py-3 md:py-4 text-sm text-white outline-none focus:border-blue-500 transition-all focus:bg-white/10" />
|
<input
|
||||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-blue-500 transition-colors">🔍</span>
|
type="text"
|
||||||
|
placeholder="Buscar por marca, modelo o versión..."
|
||||||
|
value={q}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newParams = new URLSearchParams(searchParams);
|
||||||
|
if (e.target.value) newParams.set("q", e.target.value);
|
||||||
|
else newParams.delete("q");
|
||||||
|
setSearchParams(newParams);
|
||||||
|
}}
|
||||||
|
className="w-full bg-white/5 border border-white/10 rounded-xl md:rounded-2xl px-10 md:px-12 py-3 md:py-4 text-sm text-white outline-none focus:border-blue-500 transition-all focus:bg-white/10"
|
||||||
|
/>
|
||||||
|
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-blue-500 transition-colors">
|
||||||
|
🔍
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-bold bg-white/5 border border-white/10 px-6 py-3 rounded-full text-gray-400 uppercase tracking-widest self-end md:self-center whitespace-nowrap">
|
<span className="text-sm font-bold bg-white/5 border border-white/10 px-6 py-3 rounded-full text-gray-400 uppercase tracking-widest self-end md:self-center whitespace-nowrap">
|
||||||
@@ -297,22 +503,45 @@ export default function ExplorarPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex justify-center p-20"><div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div></div>
|
<div className="flex justify-center p-20">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
|
||||||
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div className="glass p-12 rounded-[2.5rem] border border-red-500/20 text-center"><p className="text-red-400 font-bold">{error}</p></div>
|
<div className="glass p-12 rounded-[2.5rem] border border-red-500/20 text-center">
|
||||||
|
<p className="text-red-400 font-bold">{error}</p>
|
||||||
|
</div>
|
||||||
) : listings.length === 0 ? (
|
) : listings.length === 0 ? (
|
||||||
<div className="glass p-20 rounded-[2.5rem] text-center border-dashed border-2 border-white/10">
|
<div className="glass p-20 rounded-[2.5rem] text-center border-dashed border-2 border-white/10">
|
||||||
<span className="text-6xl mb-6 block">🔍</span>
|
<span className="text-6xl mb-6 block">🔍</span>
|
||||||
<h3 className="text-2xl font-bold text-gray-400 uppercase tracking-tighter">Sin coincidencias</h3>
|
<h3 className="text-2xl font-bold text-gray-400 uppercase tracking-tighter">
|
||||||
<p className="text-gray-600 max-w-xs mx-auto mt-2 italic">No encontramos vehículos que coincidan con los filtros seleccionados.</p>
|
Sin coincidencias
|
||||||
<button onClick={clearFilters} className="mt-8 text-blue-400 font-black uppercase text-[10px] tracking-widest">Ver todos los avisos</button>
|
</h3>
|
||||||
|
<p className="text-gray-600 max-w-xs mx-auto mt-2 italic">
|
||||||
|
No encontramos vehículos que coincidan con los filtros
|
||||||
|
seleccionados.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={clearFilters}
|
||||||
|
className="mt-8 text-blue-400 font-black uppercase text-[10px] tracking-widest"
|
||||||
|
>
|
||||||
|
Ver todos los avisos
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-8">
|
||||||
{listings.map(car => (
|
{listings.map((car) => (
|
||||||
<Link to={`/vehiculo/${car.id}`} key={car.id} className="glass-card rounded-2xl md:rounded-[2rem] overflow-hidden group animate-fade-in-up flex flex-col">
|
<Link
|
||||||
|
to={`/vehiculo/${car.id}`}
|
||||||
|
key={car.id}
|
||||||
|
className="glass-card rounded-2xl md:rounded-[2rem] overflow-hidden group animate-fade-in-up flex flex-col"
|
||||||
|
>
|
||||||
<div className="aspect-[4/3] overflow-hidden relative bg-[#07090d] flex items-center justify-center border-b border-white/5">
|
<div className="aspect-[4/3] overflow-hidden relative bg-[#07090d] flex items-center justify-center border-b border-white/5">
|
||||||
<img src={getImageUrl(car.image)} className="max-w-full max-h-full object-contain group-hover:scale-110 transition-transform duration-700" alt={`${car.brandName} ${car.versionName}`} loading="lazy" />
|
<img
|
||||||
|
src={getImageUrl(car.image)}
|
||||||
|
className="max-w-full max-h-full object-contain group-hover:scale-110 transition-transform duration-700"
|
||||||
|
alt={`${car.brandName} ${car.versionName}`}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* --- BLOQUE PARA EL BADGE --- */}
|
{/* --- BLOQUE PARA EL BADGE --- */}
|
||||||
<div className="absolute top-4 left-4 z-10">
|
<div className="absolute top-4 left-4 z-10">
|
||||||
@@ -331,8 +560,12 @@ export default function ExplorarPage() {
|
|||||||
</h3>
|
</h3>
|
||||||
<div className="flex justify-between items-center mt-auto">
|
<div className="flex justify-between items-center mt-auto">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1">{car.year} • {car.km.toLocaleString()} KM</span>
|
<span className="text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1">
|
||||||
<span className="text-white font-black text-2xl tracking-tighter">{formatCurrency(car.price, car.currency)}</span>
|
{car.year} • {car.km.toLocaleString()} KM
|
||||||
|
</span>
|
||||||
|
<span className="text-white font-black text-2xl tracking-tighter">
|
||||||
|
{formatCurrency(car.price, car.currency)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,6 +574,6 @@ export default function ExplorarPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { AdsV2Service, type AdListingDto } from '../services/ads.v2.service';
|
import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
||||||
import { getImageUrl, formatCurrency } from '../utils/app.utils';
|
import { getImageUrl, formatCurrency } from "../utils/app.utils";
|
||||||
import AdStatusBadge from '../components/AdStatusBadge';
|
import AdStatusBadge from "../components/AdStatusBadge";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState("");
|
||||||
const [category, setCategory] = useState('ALL');
|
const [category, setCategory] = useState("ALL");
|
||||||
const [featuredAds, setFeaturedAds] = useState<AdListingDto[]>([]);
|
const [featuredAds, setFeaturedAds] = useState<AdListingDto[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
@@ -19,10 +19,10 @@ export default function HomePage() {
|
|||||||
// Cargar destacados
|
// Cargar destacados
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
AdsV2Service.getAll({ isFeatured: true })
|
AdsV2Service.getAll({ isFeatured: true })
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setFeaturedAds(data.slice(0, 3));
|
setFeaturedAds(data.slice(0, 3));
|
||||||
})
|
})
|
||||||
.catch(err => console.error("Error cargando destacados:", err))
|
.catch((err) => console.error("Error cargando destacados:", err))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -45,7 +45,10 @@ export default function HomePage() {
|
|||||||
// --- LÓGICA PARA CERRAR SUGERENCIAS AL HACER CLIC FUERA ---
|
// --- LÓGICA PARA CERRAR SUGERENCIAS AL HACER CLIC FUERA ---
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
if (searchWrapperRef.current && !searchWrapperRef.current.contains(event.target as Node)) {
|
if (
|
||||||
|
searchWrapperRef.current &&
|
||||||
|
!searchWrapperRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
setShowSuggestions(false);
|
setShowSuggestions(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,7 @@ export default function HomePage() {
|
|||||||
const handleSearch = (searchTerm: string = query) => {
|
const handleSearch = (searchTerm: string = query) => {
|
||||||
setShowSuggestions(false);
|
setShowSuggestions(false);
|
||||||
// Si la categoría es 'ALL', no enviamos el parámetro 'c'
|
// Si la categoría es 'ALL', no enviamos el parámetro 'c'
|
||||||
const categoryParam = category === 'ALL' ? '' : `&c=${category}`;
|
const categoryParam = category === "ALL" ? "" : `&c=${category}`;
|
||||||
navigate(`/explorar?q=${searchTerm}${categoryParam}`);
|
navigate(`/explorar?q=${searchTerm}${categoryParam}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -87,7 +90,8 @@ export default function HomePage() {
|
|||||||
ENCONTRÁ TU <span className="text-gradient">PRÓXIMO</span> VEHÍCULO
|
ENCONTRÁ TU <span className="text-gradient">PRÓXIMO</span> VEHÍCULO
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm sm:text-base md:text-xl text-gray-400 mb-6 md:mb-10 max-w-2xl mx-auto font-light px-2">
|
<p className="text-sm sm:text-base md:text-xl text-gray-400 mb-6 md:mb-10 max-w-2xl mx-auto font-light px-2">
|
||||||
La web más avanzada para la compra y venta de Autos y Motos en Argentina.
|
La web más avanzada para la compra y venta de Autos y Motos en
|
||||||
|
Argentina.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* --- CONTENEDOR DEL BUSCADOR CON ref y onFocus --- */}
|
{/* --- CONTENEDOR DEL BUSCADOR CON ref y onFocus --- */}
|
||||||
@@ -95,20 +99,20 @@ export default function HomePage() {
|
|||||||
{/* Botones de categoría arriba del buscador */}
|
{/* Botones de categoría arriba del buscador */}
|
||||||
<div className="flex gap-2 mb-3 justify-center">
|
<div className="flex gap-2 mb-3 justify-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setCategory('ALL')}
|
onClick={() => setCategory("ALL")}
|
||||||
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === 'ALL' ? 'bg-blue-600 text-white shadow-lg shadow-blue-600/40' : 'glass text-gray-400 hover:text-white'}`}
|
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === "ALL" ? "bg-blue-600 text-white shadow-lg shadow-blue-600/40" : "glass text-gray-400 hover:text-white"}`}
|
||||||
>
|
>
|
||||||
Todos
|
Todos
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCategory('EAUTOS')}
|
onClick={() => setCategory("EAUTOS")}
|
||||||
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === 'EAUTOS' ? 'bg-blue-600 text-white shadow-lg shadow-blue-600/40' : 'glass text-gray-400 hover:text-white'}`}
|
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === "EAUTOS" ? "bg-blue-600 text-white shadow-lg shadow-blue-600/40" : "glass text-gray-400 hover:text-white"}`}
|
||||||
>
|
>
|
||||||
🚗 Automóviles
|
🚗 Automóviles
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setCategory('EMOTOS')}
|
onClick={() => setCategory("EMOTOS")}
|
||||||
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === 'EMOTOS' ? 'bg-blue-600 text-white shadow-lg shadow-blue-600/40' : 'glass text-gray-400 hover:text-white'}`}
|
className={`px-4 md:px-6 py-2 rounded-xl font-bold text-xs md:text-sm uppercase tracking-widest transition-all ${category === "EMOTOS" ? "bg-blue-600 text-white shadow-lg shadow-blue-600/40" : "glass text-gray-400 hover:text-white"}`}
|
||||||
>
|
>
|
||||||
🏍️ Motos
|
🏍️ Motos
|
||||||
</button>
|
</button>
|
||||||
@@ -121,7 +125,7 @@ export default function HomePage() {
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
onFocus={() => setShowSuggestions(true)}
|
onFocus={() => setShowSuggestions(true)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
onKeyDown={(e) => e.key === "Enter" && handleSearch()}
|
||||||
className="bg-transparent border-none px-4 md:px-6 py-3 md:py-4 flex-1 outline-none text-white text-base md:text-lg"
|
className="bg-transparent border-none px-4 md:px-6 py-3 md:py-4 flex-1 outline-none text-white text-base md:text-lg"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -156,10 +160,19 @@ export default function HomePage() {
|
|||||||
<section className="container mx-auto px-4 md:px-6">
|
<section className="container mx-auto px-4 md:px-6">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-6 md:mb-10 gap-4">
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-6 md:mb-10 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-3xl md:text-4xl font-bold mb-2">Avisos <span className="text-gradient">Destacados</span></h2>
|
<h2 className="text-3xl md:text-4xl font-bold mb-2">
|
||||||
<p className="text-gray-400 text-base md:text-lg italic">Las mejores ofertas seleccionadas para vos.</p>
|
Avisos <span className="text-gradient">Destacados</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 text-base md:text-lg italic">
|
||||||
|
Las mejores ofertas seleccionadas para vos.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/explorar" className="text-blue-400 hover:text-white transition text-sm md:text-base">Ver todos →</Link>
|
<Link
|
||||||
|
to="/explorar"
|
||||||
|
className="text-blue-400 hover:text-white transition text-sm md:text-base"
|
||||||
|
>
|
||||||
|
Ver todos →
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -168,12 +181,18 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
) : featuredAds.length === 0 ? (
|
) : featuredAds.length === 0 ? (
|
||||||
<div className="text-center p-10 glass rounded-3xl border border-white/5">
|
<div className="text-center p-10 glass rounded-3xl border border-white/5">
|
||||||
<p className="text-gray-500 text-xl font-bold uppercase tracking-widest">No hay avisos destacados por el momento.</p>
|
<p className="text-gray-500 text-xl font-bold uppercase tracking-widest">
|
||||||
|
No hay avisos destacados por el momento.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
|
||||||
{featuredAds.map(car => (
|
{featuredAds.map((car) => (
|
||||||
<Link to={`/vehiculo/${car.id}`} key={car.id} className="glass-card rounded-2xl md:rounded-3xl overflow-hidden group">
|
<Link
|
||||||
|
to={`/vehiculo/${car.id}`}
|
||||||
|
key={car.id}
|
||||||
|
className="glass-card rounded-2xl md:rounded-3xl overflow-hidden group"
|
||||||
|
>
|
||||||
<div className="aspect-[4/3] overflow-hidden relative bg-[#07090d] flex items-center justify-center border-b border-white/5">
|
<div className="aspect-[4/3] overflow-hidden relative bg-[#07090d] flex items-center justify-center border-b border-white/5">
|
||||||
<img
|
<img
|
||||||
src={getImageUrl(car.image)}
|
src={getImageUrl(car.image)}
|
||||||
@@ -184,10 +203,14 @@ export default function HomePage() {
|
|||||||
<AdStatusBadge statusId={car.statusId || 4} />
|
<AdStatusBadge statusId={car.statusId || 4} />
|
||||||
</div>
|
</div>
|
||||||
{car.isFeatured && (
|
{car.isFeatured && (
|
||||||
<div className="absolute top-4 right-4 bg-blue-600 text-white text-xs font-bold px-3 py-1 rounded-full uppercase tracking-widest shadow-lg shadow-blue-600/40">DESTACADO</div>
|
<div className="absolute top-4 right-4 bg-blue-600 text-white text-xs font-bold px-3 py-1 rounded-full uppercase tracking-widest shadow-lg shadow-blue-600/40">
|
||||||
|
DESTACADO
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="absolute bottom-4 right-4 bg-black/60 backdrop-blur-md text-white px-4 py-2 rounded-xl border border-white/10">
|
<div className="absolute bottom-4 right-4 bg-black/60 backdrop-blur-md text-white px-4 py-2 rounded-xl border border-white/10">
|
||||||
<span className="text-xl font-bold">{formatCurrency(car.price, car.currency)}</span>
|
<span className="text-xl font-bold">
|
||||||
|
{formatCurrency(car.price, car.currency)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -197,8 +220,12 @@ export default function HomePage() {
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 text-[10px] text-gray-400 font-black tracking-widest uppercase">
|
<div className="flex gap-4 text-[10px] text-gray-400 font-black tracking-widest uppercase">
|
||||||
<span className="bg-gray-800/80 px-3 py-1.5 rounded-lg border border-white/5">{car.year}</span>
|
<span className="bg-gray-800/80 px-3 py-1.5 rounded-lg border border-white/5">
|
||||||
<span className="bg-gray-800/80 px-3 py-1.5 rounded-lg border border-white/5">{car.km.toLocaleString()} KM</span>
|
{car.year}
|
||||||
|
</span>
|
||||||
|
<span className="bg-gray-800/80 px-3 py-1.5 rounded-lg border border-white/5">
|
||||||
|
{car.km.toLocaleString()} KM
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
|||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
import { ChatService, type ChatMessage } from "../services/chat.service";
|
import { ChatService, type ChatMessage } from "../services/chat.service";
|
||||||
import ChatModal from "../components/ChatModal";
|
import ChatModal from "../components/ChatModal";
|
||||||
import { getImageUrl, parseUTCDate } from "../utils/app.utils";
|
import { formatCurrency, getImageUrl, parseUTCDate } from "../utils/app.utils";
|
||||||
import { AD_STATUSES, STATUS_CONFIG } from "../constants/adStatuses";
|
import { AD_STATUSES, STATUS_CONFIG } from "../constants/adStatuses";
|
||||||
import ConfirmationModal from "../components/ConfirmationModal";
|
import ConfirmationModal from "../components/ConfirmationModal";
|
||||||
|
|
||||||
@@ -374,7 +374,7 @@ export default function MisAvisosPage() {
|
|||||||
{av.brandName} {av.versionName}
|
{av.brandName} {av.versionName}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="text-blue-400 font-bold text-lg">
|
<span className="text-blue-400 font-bold text-lg">
|
||||||
{av.currency} {av.price.toLocaleString()}
|
{formatCurrency(av.price, av.currency)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
|
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { AdsV2Service } from '../services/ads.v2.service';
|
import { AdsV2Service } from "../services/ads.v2.service";
|
||||||
import { AuthService } from '../services/auth.service';
|
import { AuthService } from "../services/auth.service";
|
||||||
import ChatModal from '../components/ChatModal';
|
import ChatModal from "../components/ChatModal";
|
||||||
import { FaWhatsapp, FaMapMarkerAlt, FaInfoCircle, FaShareAlt } from 'react-icons/fa';
|
import {
|
||||||
import { AD_STATUSES } from '../constants/adStatuses';
|
FaWhatsapp,
|
||||||
import AdStatusBadge from '../components/AdStatusBadge';
|
FaMapMarkerAlt,
|
||||||
import PremiumGallery from '../components/PremiumGallery';
|
FaInfoCircle,
|
||||||
|
FaShareAlt,
|
||||||
|
} from "react-icons/fa";
|
||||||
|
import { AD_STATUSES } from "../constants/adStatuses";
|
||||||
|
import AdStatusBadge from "../components/AdStatusBadge";
|
||||||
|
import PremiumGallery from "../components/PremiumGallery";
|
||||||
|
|
||||||
export default function VehiculoDetailPage() {
|
export default function VehiculoDetailPage() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@@ -43,7 +48,9 @@ export default function VehiculoDetailPage() {
|
|||||||
}, [id, user?.id]);
|
}, [id, user?.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => { viewRegistered.current = false; };
|
return () => {
|
||||||
|
viewRegistered.current = false;
|
||||||
|
};
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
const handleFavoriteToggle = async () => {
|
const handleFavoriteToggle = async () => {
|
||||||
@@ -52,52 +59,102 @@ export default function VehiculoDetailPage() {
|
|||||||
if (isFavorite) await AdsV2Service.removeFavorite(user.id, Number(id));
|
if (isFavorite) await AdsV2Service.removeFavorite(user.id, Number(id));
|
||||||
else await AdsV2Service.addFavorite(user.id, Number(id)!);
|
else await AdsV2Service.addFavorite(user.id, Number(id)!);
|
||||||
setIsFavorite(!isFavorite);
|
setIsFavorite(!isFavorite);
|
||||||
} catch (err) { console.error(err); }
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getWhatsAppLink = (phone: string, title: string) => {
|
const getWhatsAppLink = (phone: string, title: string) => {
|
||||||
if (!phone) return '#';
|
if (!phone) return "#";
|
||||||
let number = phone.replace(/[^\d]/g, '');
|
let number = phone.replace(/[^\d]/g, "");
|
||||||
if (number.startsWith('0')) number = number.substring(1);
|
if (number.startsWith("0")) number = number.substring(1);
|
||||||
if (!number.startsWith('54')) number = `549${number}`;
|
if (!number.startsWith("54")) number = `549${number}`;
|
||||||
const message = `Hola, vi tu aviso "${title}" en Motores Argentinos y me interesa.`;
|
const message = `Hola, vi tu aviso "${title}" en Motores Argentinos y me interesa.`;
|
||||||
return `https://wa.me/${number}?text=${encodeURIComponent(message)}`;
|
return `https://wa.me/${number}?text=${encodeURIComponent(message)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = (platform: 'wa' | 'fb' | 'copy') => {
|
const handleShare = (platform: "wa" | "fb" | "copy") => {
|
||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
const vehicleTitle = `${vehicle.brand?.name || ''} ${vehicle.versionName}`.trim();
|
const vehicleTitle =
|
||||||
|
`${vehicle.brand?.name || ""} ${vehicle.versionName}`.trim();
|
||||||
const text = `Mira este ${vehicleTitle} en Motores Argentinos!`;
|
const text = `Mira este ${vehicleTitle} en Motores Argentinos!`;
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case 'wa': window.open(`https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`, '_blank'); break;
|
case "wa":
|
||||||
case 'fb': window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`, '_blank'); break;
|
window.open(
|
||||||
case 'copy': navigator.clipboard.writeText(url); alert('Enlace copiado al portapapeles'); break;
|
`https://wa.me/?text=${encodeURIComponent(text + " " + url)}`,
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "fb":
|
||||||
|
window.open(
|
||||||
|
`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "copy":
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
alert("Enlace copiado al portapapeles");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) return (
|
if (loading)
|
||||||
<div className="flex flex-col items-center justify-center p-40 gap-6">
|
return (
|
||||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-500"></div>
|
<div className="flex flex-col items-center justify-center p-40 gap-6">
|
||||||
<span className="text-gray-500 font-black uppercase tracking-widest text-xs animate-pulse">Cargando...</span>
|
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-500"></div>
|
||||||
</div>
|
<span className="text-gray-500 font-black uppercase tracking-widest text-xs animate-pulse">
|
||||||
);
|
Cargando...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
if (error || !vehicle) return <div className="text-white p-20 text-center">{error || "Vehículo no encontrado"}</div>;
|
if (error || !vehicle)
|
||||||
|
return (
|
||||||
|
<div className="text-white p-20 text-center">
|
||||||
|
{error || "Vehículo no encontrado"}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// HELPER: Valida que el dato exista y no sea basura ("0", vacío, etc)
|
||||||
|
const hasData = (val: string | null | undefined) => {
|
||||||
|
return val && val.trim().length > 0 && val !== "0";
|
||||||
|
};
|
||||||
|
|
||||||
const isOwnerAdmin = vehicle.ownerUserType === 3;
|
const isOwnerAdmin = vehicle.ownerUserType === 3;
|
||||||
const isAdActive = vehicle.statusID === AD_STATUSES.ACTIVE;
|
const isAdActive = vehicle.statusID === AD_STATUSES.ACTIVE;
|
||||||
const isContactable = isAdActive && vehicle.displayContactInfo;
|
|
||||||
|
// CALCULAMOS LA DISPONIBILIDAD REAL
|
||||||
|
// 1. WhatsApp: Debe estar habilitado Y tener un teléfono válido
|
||||||
|
const canShowWhatsApp =
|
||||||
|
vehicle.allowWhatsApp && hasData(vehicle.contactPhone);
|
||||||
|
|
||||||
|
// 2. Teléfono: Debe estar habilitado ("Mostrar Número") Y tener un teléfono válido
|
||||||
|
const canShowPhone = vehicle.showPhone && hasData(vehicle.contactPhone);
|
||||||
|
|
||||||
|
// 3. Email: Debe estar habilitado Y tener un email válido
|
||||||
|
const canShowEmail = vehicle.showEmail && hasData(vehicle.contactEmail);
|
||||||
|
|
||||||
|
// Es contactable si está Activo Y (Tiene WhatsApp O Tiene Teléfono O Tiene Email)
|
||||||
|
const isContactable =
|
||||||
|
isAdActive && (canShowWhatsApp || canShowPhone || canShowEmail);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 md:px-6 py-6 md:py-12 animate-fade-in-up">
|
<div className="container mx-auto px-4 md:px-6 py-6 md:py-12 animate-fade-in-up">
|
||||||
<nav className="flex gap-2 text-xs font-bold uppercase tracking-widest text-gray-500 mb-6 md:mb-8 items-center overflow-x-auto whitespace-nowrap">
|
<nav className="flex gap-2 text-xs font-bold uppercase tracking-widest text-gray-500 mb-6 md:mb-8 items-center overflow-x-auto whitespace-nowrap">
|
||||||
<Link to="/" className="hover:text-white transition-colors">Inicio</Link> /
|
<Link to="/" className="hover:text-white transition-colors">
|
||||||
<Link to="/explorar" className="hover:text-white transition-colors">Explorar</Link> /
|
Inicio
|
||||||
<span className="text-blue-400 truncate">{vehicle.brand?.name} {vehicle.versionName || 'Detalle'}</span>
|
</Link>{" "}
|
||||||
|
/
|
||||||
|
<Link to="/explorar" className="hover:text-white transition-colors">
|
||||||
|
Explorar
|
||||||
|
</Link>{" "}
|
||||||
|
/
|
||||||
|
<span className="text-blue-400 truncate">
|
||||||
|
{vehicle.brand?.name} {vehicle.versionName || "Detalle"}
|
||||||
|
</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-12 relative items-start">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 md:gap-12 relative items-start">
|
||||||
|
|
||||||
{/* COLUMNA IZQUIERDA: Galería + Descripción (Desktop) */}
|
{/* COLUMNA IZQUIERDA: Galería + Descripción (Desktop) */}
|
||||||
<div className="lg:col-span-2 space-y-8 md:space-y-12 order-1 lg:order-1">
|
<div className="lg:col-span-2 space-y-8 md:space-y-12 order-1 lg:order-1">
|
||||||
<PremiumGallery
|
<PremiumGallery
|
||||||
@@ -106,8 +163,21 @@ export default function VehiculoDetailPage() {
|
|||||||
isFavorite={isFavorite}
|
isFavorite={isFavorite}
|
||||||
onFavoriteToggle={handleFavoriteToggle}
|
onFavoriteToggle={handleFavoriteToggle}
|
||||||
statusBadge={<AdStatusBadge statusId={vehicle.statusID} />}
|
statusBadge={<AdStatusBadge statusId={vehicle.statusID} />}
|
||||||
featuredBadge={vehicle.isFeatured && <span className="bg-gradient-to-r from-blue-600 to-cyan-500 text-white px-3 md:px-4 py-1 md:py-1.5 rounded-full text-[9px] md:text-[10px] font-black uppercase tracking-widest shadow-lg animate-glow flex items-center gap-1">⭐ DESTACADO</span>}
|
featuredBadge={
|
||||||
locationBadge={vehicle.location && <span className="bg-black/60 backdrop-blur-md text-white px-3 md:px-4 py-1 md:py-1.5 rounded-full text-[9px] md:text-[10px] font-black uppercase tracking-widest border border-white/10 flex items-center gap-1.5"><FaMapMarkerAlt className="text-blue-500" /> {vehicle.location}</span>}
|
vehicle.isFeatured && (
|
||||||
|
<span className="bg-gradient-to-r from-blue-600 to-cyan-500 text-white px-3 md:px-4 py-1 md:py-1.5 rounded-full text-[9px] md:text-[10px] font-black uppercase tracking-widest shadow-lg animate-glow flex items-center gap-1">
|
||||||
|
⭐ DESTACADO
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
locationBadge={
|
||||||
|
vehicle.location && (
|
||||||
|
<span className="bg-black/60 backdrop-blur-md text-white px-3 md:px-4 py-1 md:py-1.5 rounded-full text-[9px] md:text-[10px] font-black uppercase tracking-widest border border-white/10 flex items-center gap-1.5">
|
||||||
|
<FaMapMarkerAlt className="text-blue-500" />{" "}
|
||||||
|
{vehicle.location}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* BLOQUE 3: Información General y Técnica (Acomodado debajo de la galería) */}
|
{/* BLOQUE 3: Información General y Técnica (Acomodado debajo de la galería) */}
|
||||||
@@ -118,7 +188,10 @@ export default function VehiculoDetailPage() {
|
|||||||
<div className="w-14 h-14 bg-blue-600/10 rounded-2xl flex items-center justify-center text-blue-400 text-2xl border border-blue-500/20 shadow-inner">
|
<div className="w-14 h-14 bg-blue-600/10 rounded-2xl flex items-center justify-center text-blue-400 text-2xl border border-blue-500/20 shadow-inner">
|
||||||
<span className="text-2xl">📝</span>
|
<span className="text-2xl">📝</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl md:text-3xl font-black uppercase tracking-tighter text-white">Descripción del <span className="text-blue-500">Vendedor</span></h3>
|
<h3 className="text-2xl md:text-3xl font-black uppercase tracking-tighter text-white">
|
||||||
|
Descripción del{" "}
|
||||||
|
<span className="text-blue-500">Vendedor</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-300 leading-relaxed font-light whitespace-pre-wrap text-base md:text-lg">
|
<p className="text-gray-300 leading-relaxed font-light whitespace-pre-wrap text-base md:text-lg">
|
||||||
{vehicle.description}
|
{vehicle.description}
|
||||||
@@ -130,18 +203,54 @@ export default function VehiculoDetailPage() {
|
|||||||
<div className="w-14 h-14 bg-blue-600/10 rounded-2xl flex items-center justify-center text-blue-400 text-2xl border border-blue-500/20 shadow-inner">
|
<div className="w-14 h-14 bg-blue-600/10 rounded-2xl flex items-center justify-center text-blue-400 text-2xl border border-blue-500/20 shadow-inner">
|
||||||
<FaInfoCircle />
|
<FaInfoCircle />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl md:text-3xl font-black uppercase tracking-tighter text-white">Información <span className="text-blue-500">Técnica</span></h3>
|
<h3 className="text-2xl md:text-3xl font-black uppercase tracking-tighter text-white">
|
||||||
|
Información <span className="text-blue-500">Técnica</span>
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4 md:gap-8">
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4 md:gap-8">
|
||||||
<TechnicalItem label="Kilómetros" value={`${vehicle.km?.toLocaleString()} KM`} icon="🏎️" />
|
<TechnicalItem
|
||||||
<TechnicalItem label="Combustible" value={vehicle.fuelType} icon="⛽" />
|
label="Kilómetros"
|
||||||
<TechnicalItem label="Transmisión" value={vehicle.transmission} icon="⚙️" />
|
value={`${vehicle.km?.toLocaleString()} KM`}
|
||||||
|
icon="🏎️"
|
||||||
|
/>
|
||||||
|
<TechnicalItem
|
||||||
|
label="Combustible"
|
||||||
|
value={vehicle.fuelType}
|
||||||
|
icon="⛽"
|
||||||
|
/>
|
||||||
|
<TechnicalItem
|
||||||
|
label="Transmisión"
|
||||||
|
value={vehicle.transmission}
|
||||||
|
icon="⚙️"
|
||||||
|
/>
|
||||||
<TechnicalItem label="Color" value={vehicle.color} icon="🎨" />
|
<TechnicalItem label="Color" value={vehicle.color} icon="🎨" />
|
||||||
<TechnicalItem label="Segmento" value={vehicle.segment} icon="🚗" />
|
<TechnicalItem
|
||||||
{vehicle.condition && <TechnicalItem label="Estado" value={vehicle.condition} icon="✨" />}
|
label="Segmento"
|
||||||
{vehicle.doorCount && <TechnicalItem label="Puertas" value={vehicle.doorCount} icon="🚪" />}
|
value={vehicle.segment}
|
||||||
{vehicle.engineSize && <TechnicalItem label="Motor" value={vehicle.engineSize} icon="⚡" />}
|
icon="🚗"
|
||||||
|
/>
|
||||||
|
{vehicle.condition && (
|
||||||
|
<TechnicalItem
|
||||||
|
label="Estado"
|
||||||
|
value={vehicle.condition}
|
||||||
|
icon="✨"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{vehicle.doorCount && (
|
||||||
|
<TechnicalItem
|
||||||
|
label="Puertas"
|
||||||
|
value={vehicle.doorCount}
|
||||||
|
icon="🚪"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{vehicle.engineSize && (
|
||||||
|
<TechnicalItem
|
||||||
|
label="Motor"
|
||||||
|
value={vehicle.engineSize}
|
||||||
|
icon="⚡"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,7 +267,9 @@ export default function VehiculoDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-3xl md:text-4xl font-black tracking-tighter uppercase leading-tight mb-4 text-white">
|
<h1 className="text-3xl md:text-4xl font-black tracking-tighter uppercase leading-tight mb-4 text-white">
|
||||||
<span className="text-blue-500 mr-2">{vehicle.brand?.name}</span>
|
<span className="text-blue-500 mr-2">
|
||||||
|
{vehicle.brand?.name}
|
||||||
|
</span>
|
||||||
{vehicle.versionName}
|
{vehicle.versionName}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@@ -169,57 +280,99 @@ export default function VehiculoDetailPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gradient-to-br from-white/5 to-transparent rounded-2xl md:rounded-3xl p-6 md:p-8 mb-8 border border-white/10 shadow-inner group">
|
<div className="bg-gradient-to-br from-white/5 to-transparent rounded-2xl md:rounded-3xl p-6 md:p-8 mb-8 border border-white/10 shadow-inner group">
|
||||||
<span className="text-gray-500 text-[10px] font-black tracking-widest uppercase block mb-1 opacity-60">Precio</span>
|
<span className="text-gray-500 text-[10px] font-black tracking-widest uppercase block mb-1 opacity-60">
|
||||||
|
Precio
|
||||||
|
</span>
|
||||||
<div className="flex items-baseline gap-2">
|
<div className="flex items-baseline gap-2">
|
||||||
<span className="text-blue-400 text-3xl md:text-5xl font-black tracking-tighter">{vehicle.currency} {vehicle.price?.toLocaleString()}</span>
|
<span className="text-blue-400 text-3xl md:text-5xl font-black tracking-tighter">
|
||||||
|
{vehicle.price === 0
|
||||||
|
? "CONSULTAR"
|
||||||
|
: `${vehicle.currency} ${vehicle.price?.toLocaleString()}`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isContactable ? (
|
{isContactable ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<a href={getWhatsAppLink(vehicle.contactPhone, `${vehicle.brand?.name} ${vehicle.versionName}`)} target="_blank" rel="noopener noreferrer"
|
{/* BOTÓN WHATSAPP: Usamos la nueva variable canShowWhatsApp */}
|
||||||
className="w-full glass border border-green-500/30 hover:bg-green-600 text-white py-5 rounded-2xl font-black uppercase tracking-widest transition-all shadow-lg shadow-green-600/20 flex items-center justify-center gap-3 group hover:border-green-500/50">
|
{canShowWhatsApp && (
|
||||||
<FaWhatsapp className="text-3xl group-hover:scale-110 transition-transform text-green-400 group-hover:text-white" />
|
<a
|
||||||
<span>Contactar</span>
|
href={getWhatsAppLink(
|
||||||
</a>
|
vehicle.contactPhone,
|
||||||
|
`${vehicle.brand?.name} ${vehicle.versionName}`,
|
||||||
|
)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="w-full glass border border-green-500/30 hover:bg-green-600 text-white py-5 rounded-2xl font-black uppercase tracking-widest transition-all shadow-lg shadow-green-600/20 flex items-center justify-center gap-3 group hover:border-green-500/50"
|
||||||
|
>
|
||||||
|
<FaWhatsapp className="text-3xl group-hover:scale-110 transition-transform text-green-400 group-hover:text-white" />
|
||||||
|
<span>Contactar</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
|
||||||
{vehicle.contactPhone && (
|
{/* CAJA DE TELÉFONO: Usamos canShowPhone */}
|
||||||
|
{canShowPhone && (
|
||||||
<div className="w-full bg-white/5 py-4 rounded-xl border border-white/10 flex items-center justify-center gap-3 text-gray-300 font-black uppercase tracking-[0.2em] text-[10px] shadow-sm">
|
<div className="w-full bg-white/5 py-4 rounded-xl border border-white/10 flex items-center justify-center gap-3 text-gray-300 font-black uppercase tracking-[0.2em] text-[10px] shadow-sm">
|
||||||
<span className="text-blue-400">📞</span>
|
<span className="text-blue-400">📞</span>
|
||||||
<span className="opacity-60 font-bold">Llamar:</span>
|
<span className="opacity-60 font-bold">Llamar:</span>
|
||||||
<span className="tracking-widest">{vehicle.contactPhone}</span>
|
<span className="tracking-widest select-all">
|
||||||
|
{vehicle.contactPhone}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CAJA DE EMAIL: Usamos canShowEmail */}
|
||||||
|
{canShowEmail && (
|
||||||
|
<div className="w-full bg-white/5 py-4 rounded-xl border border-white/10 flex items-center justify-center gap-3 text-gray-300 font-black uppercase tracking-[0.1em] text-[9px] shadow-sm overflow-hidden">
|
||||||
|
<span className="text-blue-400 text-base">✉️</span>
|
||||||
|
<span className="tracking-widest truncate max-w-[200px] select-all">
|
||||||
|
{vehicle.contactEmail}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white/5 border border-white/10 rounded-2xl p-6 text-center">
|
<div className="bg-white/5 border border-white/10 rounded-2xl p-6 text-center">
|
||||||
<div className="text-3xl mb-3">
|
<div className="text-3xl mb-3">
|
||||||
{isOwnerAdmin ? 'ℹ️' :
|
{isOwnerAdmin
|
||||||
vehicle.statusID === AD_STATUSES.MODERATION_PENDING ? '⏳' :
|
? "ℹ️"
|
||||||
vehicle.statusID === AD_STATUSES.PAYMENT_PENDING ? '💳' :
|
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
||||||
vehicle.statusID === AD_STATUSES.SOLD ? '🤝' : '🔒'}
|
? "⏳"
|
||||||
|
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
||||||
|
? "💳"
|
||||||
|
: vehicle.statusID === AD_STATUSES.SOLD
|
||||||
|
? "🤝"
|
||||||
|
: "🔒"}
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-lg font-bold text-white uppercase tracking-tight mb-2">
|
<h3 className="text-lg font-bold text-white uppercase tracking-tight mb-2">
|
||||||
{isOwnerAdmin ? 'Contacto en descripción' :
|
{isOwnerAdmin
|
||||||
vehicle.statusID === AD_STATUSES.MODERATION_PENDING ? 'Aviso en Revisión' :
|
? "Contacto en descripción"
|
||||||
vehicle.statusID === AD_STATUSES.PAYMENT_PENDING ? 'Pago Pendiente' :
|
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
||||||
vehicle.statusID === AD_STATUSES.SOLD ? 'Vehículo Vendido' : 'No disponible'}
|
? "Aviso en Revisión"
|
||||||
|
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
||||||
|
? "Pago Pendiente"
|
||||||
|
: vehicle.statusID === AD_STATUSES.SOLD
|
||||||
|
? "Vehículo Vendido"
|
||||||
|
: "No disponible"}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-400 leading-relaxed">
|
<p className="text-xs text-gray-400 leading-relaxed">
|
||||||
{isOwnerAdmin
|
{isOwnerAdmin
|
||||||
? 'Revisa la descripción para contactar al vendedor.'
|
? "Revisa la descripción para contactar al vendedor."
|
||||||
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
||||||
? 'Este aviso está siendo verificado por un moderador. Estará activo pronto.'
|
? "Este aviso está siendo verificado por un moderador. Estará activo pronto."
|
||||||
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
||||||
? 'El pago de este aviso aún no ha sido procesado completamente.'
|
? "El pago de este aviso aún no ha sido procesado completamente."
|
||||||
: vehicle.statusID === AD_STATUSES.SOLD
|
: vehicle.statusID === AD_STATUSES.SOLD
|
||||||
? 'Este vehículo ya ha sido vendido a otro usuario.'
|
? "Este vehículo ya ha sido vendido a otro usuario."
|
||||||
: 'Revisa la descripción para contactar al vendedor.'}
|
: "Revisa la descripción para contactar al vendedor."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button onClick={() => handleShare('copy')} className="w-full mt-6 py-4 rounded-xl border border-white/5 text-[9px] font-black uppercase tracking-[0.2em] text-gray-500 hover:text-white hover:bg-white/5 transition-all flex items-center justify-center gap-2">
|
<button
|
||||||
|
onClick={() => handleShare("copy")}
|
||||||
|
className="w-full mt-6 py-4 rounded-xl border border-white/5 text-[9px] font-black uppercase tracking-[0.2em] text-gray-500 hover:text-white hover:bg-white/5 transition-all flex items-center justify-center gap-2"
|
||||||
|
>
|
||||||
<FaShareAlt /> Compartir Aviso
|
<FaShareAlt /> Compartir Aviso
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,11 +393,22 @@ export default function VehiculoDetailPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TechnicalItem({ label, value, icon }: { label: string, value: string, icon: string }) {
|
function TechnicalItem({
|
||||||
if ((value === undefined || value === null || value === '') || value === 'N/A') return null;
|
label,
|
||||||
|
value,
|
||||||
|
icon,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
icon: string;
|
||||||
|
}) {
|
||||||
|
if (value === undefined || value === null || value === "" || value === "N/A")
|
||||||
|
return null;
|
||||||
return (
|
return (
|
||||||
<div className="bg-white/5 p-4 rounded-xl md:rounded-2xl border border-white/5 hover:bg-white/10 transition-colors">
|
<div className="bg-white/5 p-4 rounded-xl md:rounded-2xl border border-white/5 hover:bg-white/10 transition-colors">
|
||||||
<span className="text-gray-500 text-[9px] md:text-[10px] uppercase font-black tracking-widest block mb-1">{label}</span>
|
<span className="text-gray-500 text-[9px] md:text-[10px] uppercase font-black tracking-widest block mb-1">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-sm md:text-base">{icon}</span>
|
<span className="text-sm md:text-base">{icon}</span>
|
||||||
<span className="text-white font-bold text-xs md:text-sm">{value}</span>
|
<span className="text-white font-bold text-xs md:text-sm">{value}</span>
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export const getImageUrl = (path?: string): string => {
|
|||||||
* Formatea un número como moneda ARS o USD.
|
* Formatea un número como moneda ARS o USD.
|
||||||
*/
|
*/
|
||||||
export const formatCurrency = (amount: number, currency: string = 'ARS') => {
|
export const formatCurrency = (amount: number, currency: string = 'ARS') => {
|
||||||
|
// Lógica para precio 0
|
||||||
|
if (amount === 0) {
|
||||||
|
return 'CONSULTAR';
|
||||||
|
}
|
||||||
|
|
||||||
return new Intl.NumberFormat('es-AR', {
|
return new Intl.NumberFormat('es-AR', {
|
||||||
style: 'currency',
|
style: 'currency',
|
||||||
currency: currency,
|
currency: currency,
|
||||||
|
|||||||
Reference in New Issue
Block a user