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,
|
||||
contactEmail = ad.ContactEmail,
|
||||
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 }),
|
||||
features = ad.Features.Select(f => new { f.FeatureKey, f.FeatureValue }),
|
||||
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,
|
||||
ContactEmail = request.ContactEmail,
|
||||
DisplayContactInfo = request.DisplayContactInfo,
|
||||
ShowPhone = request.ShowPhone,
|
||||
AllowWhatsApp = request.AllowWhatsApp,
|
||||
ShowEmail = request.ShowEmail,
|
||||
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
@@ -655,6 +661,9 @@ public class AdsV2Controller : ControllerBase
|
||||
ad.ContactPhone = updatedAdDto.ContactPhone;
|
||||
ad.ContactEmail = updatedAdDto.ContactEmail;
|
||||
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)
|
||||
|
||||
// LÓGICA DE ESTADO TRAS RECHAZO
|
||||
|
||||
@@ -95,6 +95,15 @@ public class CreateAdRequestDto
|
||||
[JsonPropertyName("displayContactInfo")]
|
||||
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 ---
|
||||
|
||||
[JsonPropertyName("targetUserID")]
|
||||
|
||||
@@ -125,6 +125,11 @@ public class Ad
|
||||
public string? ContactPhone { get; set; }
|
||||
public string? ContactEmail { get; set; }
|
||||
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 int StatusID { get; set; }
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,17 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSearchParams, Link } from 'react-router-dom';
|
||||
import { AdsV2Service, type AdListingDto } from '../services/ads.v2.service';
|
||||
import { getImageUrl, formatCurrency } from '../utils/app.utils';
|
||||
import SearchableSelect from '../components/SearchableSelect';
|
||||
import AdStatusBadge from '../components/AdStatusBadge';
|
||||
import { useState, useEffect } from "react";
|
||||
import { useSearchParams, Link } from "react-router-dom";
|
||||
import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
||||
import { getImageUrl, formatCurrency } from "../utils/app.utils";
|
||||
import SearchableSelect from "../components/SearchableSelect";
|
||||
import AdStatusBadge from "../components/AdStatusBadge";
|
||||
import {
|
||||
AUTO_SEGMENTS,
|
||||
MOTO_SEGMENTS,
|
||||
AUTO_TRANSMISSIONS,
|
||||
MOTO_TRANSMISSIONS,
|
||||
FUEL_TYPES,
|
||||
VEHICLE_CONDITIONS
|
||||
} from '../constants/vehicleOptions';
|
||||
VEHICLE_CONDITIONS,
|
||||
} from "../constants/vehicleOptions";
|
||||
|
||||
export default function ExplorarPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@@ -19,25 +19,29 @@ export default function ExplorarPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [minPrice, setMinPrice] = useState(searchParams.get('minPrice') || '');
|
||||
const [maxPrice, setMaxPrice] = useState(searchParams.get('maxPrice') || '');
|
||||
const [currencyFilter, setCurrencyFilter] = useState(searchParams.get('currency') || '');
|
||||
const [minYear, setMinYear] = useState(searchParams.get('minYear') || '');
|
||||
const [maxYear, setMaxYear] = useState(searchParams.get('maxYear') || '');
|
||||
const [brandId, setBrandId] = useState(searchParams.get('brandId') || '');
|
||||
const [modelId, setModelId] = useState(searchParams.get('modelId') || '');
|
||||
const [fuel, setFuel] = useState(searchParams.get('fuel') || '');
|
||||
const [transmission, setTransmission] = useState(searchParams.get('transmission') || '');
|
||||
const [minPrice, setMinPrice] = useState(searchParams.get("minPrice") || "");
|
||||
const [maxPrice, setMaxPrice] = useState(searchParams.get("maxPrice") || "");
|
||||
const [currencyFilter, setCurrencyFilter] = useState(
|
||||
searchParams.get("currency") || "",
|
||||
);
|
||||
const [minYear, setMinYear] = useState(searchParams.get("minYear") || "");
|
||||
const [maxYear, setMaxYear] = useState(searchParams.get("maxYear") || "");
|
||||
const [brandId, setBrandId] = useState(searchParams.get("brandId") || "");
|
||||
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 [models, setModels] = useState<{ id: number, name: string }[]>([]);
|
||||
const [brands, setBrands] = useState<{ id: number; name: string }[]>([]);
|
||||
const [models, setModels] = useState<{ id: number; name: string }[]>([]);
|
||||
|
||||
const q = searchParams.get('q') || '';
|
||||
const c = searchParams.get('c') || 'ALL';
|
||||
const q = searchParams.get("q") || "";
|
||||
const c = searchParams.get("c") || "ALL";
|
||||
|
||||
useEffect(() => {
|
||||
if (c !== 'ALL') {
|
||||
const typeId = c === 'EAUTOS' ? 1 : 2;
|
||||
if (c !== "ALL") {
|
||||
const typeId = c === "EAUTOS" ? 1 : 2;
|
||||
AdsV2Service.getBrands(typeId).then(setBrands);
|
||||
} else {
|
||||
setBrands([]);
|
||||
@@ -61,7 +65,7 @@ export default function ExplorarPage() {
|
||||
try {
|
||||
const data = await AdsV2Service.getAll({
|
||||
q,
|
||||
c: c === 'ALL' ? undefined : c,
|
||||
c: c === "ALL" ? undefined : c,
|
||||
minPrice: minPrice ? Number(minPrice) : undefined,
|
||||
maxPrice: maxPrice ? Number(maxPrice) : undefined,
|
||||
currency: currencyFilter || undefined,
|
||||
@@ -70,7 +74,7 @@ export default function ExplorarPage() {
|
||||
brandId: brandId ? Number(brandId) : undefined,
|
||||
modelId: modelId ? Number(modelId) : undefined,
|
||||
fuel: fuel || undefined,
|
||||
transmission: transmission || undefined
|
||||
transmission: transmission || undefined,
|
||||
});
|
||||
setListings(data);
|
||||
} catch (err) {
|
||||
@@ -86,32 +90,47 @@ export default function ExplorarPage() {
|
||||
|
||||
const applyFilters = () => {
|
||||
const newParams = new URLSearchParams(searchParams);
|
||||
if (minPrice) newParams.set('minPrice', minPrice); else newParams.delete('minPrice');
|
||||
if (maxPrice) newParams.set('maxPrice', maxPrice); else newParams.delete('maxPrice');
|
||||
if (currencyFilter) newParams.set('currency', currencyFilter); else newParams.delete('currency');
|
||||
if (minYear) newParams.set('minYear', minYear); else newParams.delete('minYear');
|
||||
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');
|
||||
if (minPrice) newParams.set("minPrice", minPrice);
|
||||
else newParams.delete("minPrice");
|
||||
if (maxPrice) newParams.set("maxPrice", maxPrice);
|
||||
else newParams.delete("maxPrice");
|
||||
if (currencyFilter) newParams.set("currency", currencyFilter);
|
||||
else newParams.delete("currency");
|
||||
if (minYear) newParams.set("minYear", minYear);
|
||||
else newParams.delete("minYear");
|
||||
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);
|
||||
};
|
||||
|
||||
const clearFilters = () => {
|
||||
setMinPrice(''); setMaxPrice(''); setMinYear(''); setMaxYear('');
|
||||
setCurrencyFilter('');
|
||||
setBrandId(''); setModelId(''); setFuel(''); setTransmission('');
|
||||
setMinPrice("");
|
||||
setMaxPrice("");
|
||||
setMinYear("");
|
||||
setMaxYear("");
|
||||
setCurrencyFilter("");
|
||||
setBrandId("");
|
||||
setModelId("");
|
||||
setFuel("");
|
||||
setTransmission("");
|
||||
const newParams = new URLSearchParams();
|
||||
if (q) newParams.set('q', q);
|
||||
if (c !== 'ALL') newParams.set('c', c);
|
||||
if (q) newParams.set("q", q);
|
||||
if (c !== "ALL") newParams.set("c", c);
|
||||
setSearchParams(newParams);
|
||||
};
|
||||
|
||||
const handleCategoryFilter = (cat: string) => {
|
||||
const newParams = new URLSearchParams();
|
||||
if (q) newParams.set('q', q);
|
||||
if (cat !== 'ALL') newParams.set('c', cat);
|
||||
if (q) newParams.set("q", q);
|
||||
if (cat !== "ALL") newParams.set("c", cat);
|
||||
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">
|
||||
<button
|
||||
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>
|
||||
</button>
|
||||
|
||||
{/* 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
|
||||
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'}
|
||||
`}>
|
||||
<div className="
|
||||
${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="
|
||||
glass p-6 rounded-[2rem] border border-white/5 shadow-2xl
|
||||
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">
|
||||
<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">
|
||||
<button
|
||||
onClick={clearFilters}
|
||||
@@ -155,22 +180,32 @@ export default function ExplorarPage() {
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Categoría */}
|
||||
<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
|
||||
value={c}
|
||||
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"
|
||||
>
|
||||
<option value="ALL" className="bg-gray-900">Todos</option>
|
||||
<option value="EAUTOS" className="bg-gray-900">Automóviles</option>
|
||||
<option value="EMOTOS" className="bg-gray-900">Motos</option>
|
||||
<option value="ALL" className="bg-gray-900">
|
||||
Todos
|
||||
</option>
|
||||
<option value="EAUTOS" className="bg-gray-900">
|
||||
Automóviles
|
||||
</option>
|
||||
<option value="EMOTOS" className="bg-gray-900">
|
||||
Motos
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{c !== 'ALL' && (
|
||||
{c !== "ALL" && (
|
||||
<div className="space-y-4 animate-fade-in">
|
||||
<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
|
||||
options={brands}
|
||||
value={brandId}
|
||||
@@ -181,15 +216,25 @@ export default function ExplorarPage() {
|
||||
|
||||
{brandId && (
|
||||
<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
|
||||
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"
|
||||
>
|
||||
<option value="" className="bg-gray-900 text-gray-500">Todos los modelos</option>
|
||||
{models.map(m => (
|
||||
<option key={m.id} value={m.id} className="bg-gray-900 text-white">{m.name}</option>
|
||||
<option value="" className="bg-gray-900 text-gray-500">
|
||||
Todos los modelos
|
||||
</option>
|
||||
{models.map((m) => (
|
||||
<option
|
||||
key={m.id}
|
||||
value={m.id}
|
||||
className="bg-gray-900 text-white"
|
||||
>
|
||||
{m.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -198,85 +243,231 @@ export default function ExplorarPage() {
|
||||
)}
|
||||
|
||||
<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
|
||||
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"
|
||||
>
|
||||
<option value="" className="bg-gray-900 text-gray-500">Indistinto</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>
|
||||
<option value="" className="bg-gray-900 text-gray-500">
|
||||
Indistinto
|
||||
</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>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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 className="space-y-4">
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Combustible</label>
|
||||
<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>))}
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
Combustible
|
||||
</label>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Transmisión</label>
|
||||
<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">
|
||||
<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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
Transmisión
|
||||
</label>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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 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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Estado</label>
|
||||
<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>)}
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
Estado
|
||||
</label>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">Segmento</label>
|
||||
<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">
|
||||
<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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
Segmento
|
||||
</label>
|
||||
<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"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{c !== 'EMOTOS' && (
|
||||
{c !== "EMOTOS" && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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>
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">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" />
|
||||
<label className="text-[10px] text-gray-500 uppercase tracking-widest font-black mb-2 block">
|
||||
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>
|
||||
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
@@ -285,10 +476,25 @@ export default function ExplorarPage() {
|
||||
<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-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">
|
||||
<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" />
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-blue-500 transition-colors">🔍</span>
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
{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 ? (
|
||||
<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 ? (
|
||||
<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>
|
||||
<h3 className="text-2xl font-bold text-gray-400 uppercase tracking-tighter">Sin coincidencias</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>
|
||||
<h3 className="text-2xl font-bold text-gray-400 uppercase tracking-tighter">
|
||||
Sin coincidencias
|
||||
</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 className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 md:gap-8">
|
||||
{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">
|
||||
{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"
|
||||
>
|
||||
<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 --- */}
|
||||
<div className="absolute top-4 left-4 z-10">
|
||||
@@ -331,8 +560,12 @@ export default function ExplorarPage() {
|
||||
</h3>
|
||||
<div className="flex justify-between items-center mt-auto">
|
||||
<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-white font-black text-2xl tracking-tighter">{formatCurrency(car.price, car.currency)}</span>
|
||||
<span className="text-gray-500 text-[10px] font-black uppercase tracking-widest mb-1">
|
||||
{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>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { AdsV2Service, type AdListingDto } from '../services/ads.v2.service';
|
||||
import { getImageUrl, formatCurrency } from '../utils/app.utils';
|
||||
import AdStatusBadge from '../components/AdStatusBadge';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
||||
import { getImageUrl, formatCurrency } from "../utils/app.utils";
|
||||
import AdStatusBadge from "../components/AdStatusBadge";
|
||||
|
||||
export default function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = useState('');
|
||||
const [category, setCategory] = useState('ALL');
|
||||
const [query, setQuery] = useState("");
|
||||
const [category, setCategory] = useState("ALL");
|
||||
const [featuredAds, setFeaturedAds] = useState<AdListingDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -19,10 +19,10 @@ export default function HomePage() {
|
||||
// Cargar destacados
|
||||
useEffect(() => {
|
||||
AdsV2Service.getAll({ isFeatured: true })
|
||||
.then(data => {
|
||||
.then((data) => {
|
||||
setFeaturedAds(data.slice(0, 3));
|
||||
})
|
||||
.catch(err => console.error("Error cargando destacados:", err))
|
||||
.catch((err) => console.error("Error cargando destacados:", err))
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
@@ -45,7 +45,10 @@ export default function HomePage() {
|
||||
// --- LÓGICA PARA CERRAR SUGERENCIAS AL HACER CLIC FUERA ---
|
||||
useEffect(() => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -57,7 +60,7 @@ export default function HomePage() {
|
||||
const handleSearch = (searchTerm: string = query) => {
|
||||
setShowSuggestions(false);
|
||||
// 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}`);
|
||||
};
|
||||
|
||||
@@ -87,7 +90,8 @@ export default function HomePage() {
|
||||
ENCONTRÁ TU <span className="text-gradient">PRÓXIMO</span> VEHÍCULO
|
||||
</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">
|
||||
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>
|
||||
|
||||
{/* --- CONTENEDOR DEL BUSCADOR CON ref y onFocus --- */}
|
||||
@@ -95,20 +99,20 @@ export default function HomePage() {
|
||||
{/* Botones de categoría arriba del buscador */}
|
||||
<div className="flex gap-2 mb-3 justify-center">
|
||||
<button
|
||||
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'}`}
|
||||
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"}`}
|
||||
>
|
||||
Todos
|
||||
</button>
|
||||
<button
|
||||
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'}`}
|
||||
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"}`}
|
||||
>
|
||||
🚗 Automóviles
|
||||
</button>
|
||||
<button
|
||||
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'}`}
|
||||
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"}`}
|
||||
>
|
||||
🏍️ Motos
|
||||
</button>
|
||||
@@ -121,7 +125,7 @@ export default function HomePage() {
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
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"
|
||||
/>
|
||||
<button
|
||||
@@ -156,10 +160,19 @@ export default function HomePage() {
|
||||
<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>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2">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>
|
||||
<h2 className="text-3xl md:text-4xl font-bold mb-2">
|
||||
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>
|
||||
<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>
|
||||
|
||||
{loading ? (
|
||||
@@ -168,12 +181,18 @@ export default function HomePage() {
|
||||
</div>
|
||||
) : featuredAds.length === 0 ? (
|
||||
<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 className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-8">
|
||||
{featuredAds.map(car => (
|
||||
<Link to={`/vehiculo/${car.id}`} key={car.id} className="glass-card rounded-2xl md:rounded-3xl overflow-hidden group">
|
||||
{featuredAds.map((car) => (
|
||||
<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">
|
||||
<img
|
||||
src={getImageUrl(car.image)}
|
||||
@@ -184,10 +203,14 @@ export default function HomePage() {
|
||||
<AdStatusBadge statusId={car.statusId || 4} />
|
||||
</div>
|
||||
{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">
|
||||
<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 className="p-6">
|
||||
@@ -197,8 +220,12 @@ export default function HomePage() {
|
||||
</h3>
|
||||
</div>
|
||||
<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">{car.km.toLocaleString()} KM</span>
|
||||
<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">
|
||||
{car.km.toLocaleString()} KM
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AdsV2Service, type AdListingDto } from "../services/ads.v2.service";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { ChatService, type ChatMessage } from "../services/chat.service";
|
||||
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 ConfirmationModal from "../components/ConfirmationModal";
|
||||
|
||||
@@ -374,7 +374,7 @@ export default function MisAvisosPage() {
|
||||
{av.brandName} {av.versionName}
|
||||
</h3>
|
||||
<span className="text-blue-400 font-bold text-lg">
|
||||
{av.currency} {av.price.toLocaleString()}
|
||||
{formatCurrency(av.price, av.currency)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { AdsV2Service } from '../services/ads.v2.service';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import ChatModal from '../components/ChatModal';
|
||||
import { FaWhatsapp, FaMapMarkerAlt, FaInfoCircle, FaShareAlt } from 'react-icons/fa';
|
||||
import { AD_STATUSES } from '../constants/adStatuses';
|
||||
import AdStatusBadge from '../components/AdStatusBadge';
|
||||
import PremiumGallery from '../components/PremiumGallery';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { AdsV2Service } from "../services/ads.v2.service";
|
||||
import { AuthService } from "../services/auth.service";
|
||||
import ChatModal from "../components/ChatModal";
|
||||
import {
|
||||
FaWhatsapp,
|
||||
FaMapMarkerAlt,
|
||||
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() {
|
||||
const { id } = useParams();
|
||||
@@ -43,7 +48,9 @@ export default function VehiculoDetailPage() {
|
||||
}, [id, user?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => { viewRegistered.current = false; };
|
||||
return () => {
|
||||
viewRegistered.current = false;
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
const handleFavoriteToggle = async () => {
|
||||
@@ -52,52 +59,102 @@ export default function VehiculoDetailPage() {
|
||||
if (isFavorite) await AdsV2Service.removeFavorite(user.id, Number(id));
|
||||
else await AdsV2Service.addFavorite(user.id, Number(id)!);
|
||||
setIsFavorite(!isFavorite);
|
||||
} catch (err) { console.error(err); }
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const getWhatsAppLink = (phone: string, title: string) => {
|
||||
if (!phone) return '#';
|
||||
let number = phone.replace(/[^\d]/g, '');
|
||||
if (number.startsWith('0')) number = number.substring(1);
|
||||
if (!number.startsWith('54')) number = `549${number}`;
|
||||
if (!phone) return "#";
|
||||
let number = phone.replace(/[^\d]/g, "");
|
||||
if (number.startsWith("0")) number = number.substring(1);
|
||||
if (!number.startsWith("54")) number = `549${number}`;
|
||||
const message = `Hola, vi tu aviso "${title}" en Motores Argentinos y me interesa.`;
|
||||
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 vehicleTitle = `${vehicle.brand?.name || ''} ${vehicle.versionName}`.trim();
|
||||
const vehicleTitle =
|
||||
`${vehicle.brand?.name || ""} ${vehicle.versionName}`.trim();
|
||||
const text = `Mira este ${vehicleTitle} en Motores Argentinos!`;
|
||||
switch (platform) {
|
||||
case 'wa': window.open(`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;
|
||||
case "wa":
|
||||
window.open(
|
||||
`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)
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center p-40 gap-6">
|
||||
<div className="animate-spin rounded-full h-16 w-16 border-b-2 border-blue-500"></div>
|
||||
<span className="text-gray-500 font-black uppercase tracking-widest text-xs animate-pulse">Cargando...</span>
|
||||
<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 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 (
|
||||
<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">
|
||||
<Link to="/" className="hover:text-white transition-colors">Inicio</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>
|
||||
<Link to="/" className="hover:text-white transition-colors">
|
||||
Inicio
|
||||
</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>
|
||||
|
||||
<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) */}
|
||||
<div className="lg:col-span-2 space-y-8 md:space-y-12 order-1 lg:order-1">
|
||||
<PremiumGallery
|
||||
@@ -106,8 +163,21 @@ export default function VehiculoDetailPage() {
|
||||
isFavorite={isFavorite}
|
||||
onFavoriteToggle={handleFavoriteToggle}
|
||||
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>}
|
||||
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>}
|
||||
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>
|
||||
)
|
||||
}
|
||||
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) */}
|
||||
@@ -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">
|
||||
<span className="text-2xl">📝</span>
|
||||
</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>
|
||||
<p className="text-gray-300 leading-relaxed font-light whitespace-pre-wrap text-base md:text-lg">
|
||||
{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">
|
||||
<FaInfoCircle />
|
||||
</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 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 label="Combustible" value={vehicle.fuelType} icon="⛽" />
|
||||
<TechnicalItem label="Transmisión" value={vehicle.transmission} icon="⚙️" />
|
||||
<TechnicalItem
|
||||
label="Kilómetros"
|
||||
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="Segmento" value={vehicle.segment} 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="⚡" />}
|
||||
<TechnicalItem
|
||||
label="Segmento"
|
||||
value={vehicle.segment}
|
||||
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>
|
||||
@@ -158,7 +267,9 @@ export default function VehiculoDetailPage() {
|
||||
</div>
|
||||
|
||||
<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}
|
||||
</h1>
|
||||
|
||||
@@ -169,57 +280,99 @@ export default function VehiculoDetailPage() {
|
||||
</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">
|
||||
<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">
|
||||
<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>
|
||||
|
||||
{isContactable ? (
|
||||
<div className="space-y-4">
|
||||
<a href={getWhatsAppLink(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">
|
||||
{/* BOTÓN WHATSAPP: Usamos la nueva variable canShowWhatsApp */}
|
||||
{canShowWhatsApp && (
|
||||
<a
|
||||
href={getWhatsAppLink(
|
||||
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">
|
||||
<span className="text-blue-400">📞</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 className="bg-white/5 border border-white/10 rounded-2xl p-6 text-center">
|
||||
<div className="text-3xl mb-3">
|
||||
{isOwnerAdmin ? 'ℹ️' :
|
||||
vehicle.statusID === AD_STATUSES.MODERATION_PENDING ? '⏳' :
|
||||
vehicle.statusID === AD_STATUSES.PAYMENT_PENDING ? '💳' :
|
||||
vehicle.statusID === AD_STATUSES.SOLD ? '🤝' : '🔒'}
|
||||
{isOwnerAdmin
|
||||
? "ℹ️"
|
||||
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
||||
? "⏳"
|
||||
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
||||
? "💳"
|
||||
: vehicle.statusID === AD_STATUSES.SOLD
|
||||
? "🤝"
|
||||
: "🔒"}
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-white uppercase tracking-tight mb-2">
|
||||
{isOwnerAdmin ? 'Contacto en descripción' :
|
||||
vehicle.statusID === AD_STATUSES.MODERATION_PENDING ? 'Aviso en Revisión' :
|
||||
vehicle.statusID === AD_STATUSES.PAYMENT_PENDING ? 'Pago Pendiente' :
|
||||
vehicle.statusID === AD_STATUSES.SOLD ? 'Vehículo Vendido' : 'No disponible'}
|
||||
{isOwnerAdmin
|
||||
? "Contacto en descripción"
|
||||
: vehicle.statusID === AD_STATUSES.MODERATION_PENDING
|
||||
? "Aviso en Revisión"
|
||||
: vehicle.statusID === AD_STATUSES.PAYMENT_PENDING
|
||||
? "Pago Pendiente"
|
||||
: vehicle.statusID === AD_STATUSES.SOLD
|
||||
? "Vehículo Vendido"
|
||||
: "No disponible"}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400 leading-relaxed">
|
||||
{isOwnerAdmin
|
||||
? 'Revisa la descripción para contactar al vendedor.'
|
||||
? "Revisa la descripción para contactar al vendedor."
|
||||
: 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
|
||||
? '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
|
||||
? 'Este vehículo ya ha sido vendido a otro usuario.'
|
||||
: 'Revisa la descripción para contactar al vendedor.'}
|
||||
? "Este vehículo ya ha sido vendido a otro usuario."
|
||||
: "Revisa la descripción para contactar al vendedor."}
|
||||
</p>
|
||||
</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
|
||||
</button>
|
||||
</div>
|
||||
@@ -240,11 +393,22 @@ export default function VehiculoDetailPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function TechnicalItem({ label, value, icon }: { label: string, value: string, icon: string }) {
|
||||
if ((value === undefined || value === null || value === '') || value === 'N/A') return null;
|
||||
function TechnicalItem({
|
||||
label,
|
||||
value,
|
||||
icon,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
icon: string;
|
||||
}) {
|
||||
if (value === undefined || value === null || value === "" || value === "N/A")
|
||||
return null;
|
||||
return (
|
||||
<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">
|
||||
<span className="text-sm md:text-base">{icon}</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.
|
||||
*/
|
||||
export const formatCurrency = (amount: number, currency: string = 'ARS') => {
|
||||
// Lógica para precio 0
|
||||
if (amount === 0) {
|
||||
return 'CONSULTAR';
|
||||
}
|
||||
|
||||
return new Intl.NumberFormat('es-AR', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
|
||||
Reference in New Issue
Block a user