feat(ui): notificaciones toast y mapa que sobrevive al cambio de pestaña
- Añade sistema de avisos tipo snackbar (Toast.jsx + useToast.js) con auto-cierre, animación de entrada y estilos info/éxito/error. - LocalCard: feedback visual al añadir/quitar un local de favoritos. - AdminPanel: sustituye los alert() por toasts al aprobar, rechazar, eliminar locales, descartar avisos y guardar el filtro de palabras; además saluda con un toast tras iniciar sesión o crear cuenta. - App: el contenedor del mapa Leaflet queda siempre montado (oculto con CSS) y se llama a invalidateSize() al volver a la pestaña de mapa; así el mapa ya no se rompe al navegar entre vistas (antes obligaba a recargar la página).
This commit is contained in:
@@ -2,7 +2,9 @@ import { useState } from "react";
|
||||
import CategoriaBadge from "./CategoriaBadge.jsx";
|
||||
import StarRating from "./StarRating.jsx";
|
||||
import ReportarLocal from "./ReportarLocal.jsx";
|
||||
import Toast from "./Toast.jsx";
|
||||
import useFavoritos from "../hooks/useFavoritos.js";
|
||||
import useToast from "../hooks/useToast.js";
|
||||
import { confirmarLocal } from "../firebase.js";
|
||||
import { enlaceGoogleMapsDesdeLocal, enlaceWazeDesdeLocal, formatearDistancia } from "../utils/geo.js";
|
||||
|
||||
@@ -24,6 +26,7 @@ function mesesDesde(fecha) {
|
||||
|
||||
export default function LocalCard({ local, distanciaKm }) {
|
||||
const { esFavorito, alternarFavorito } = useFavoritos();
|
||||
const { toast, mostrarToast } = useToast();
|
||||
const [confirmando, setConfirmando] = useState(false);
|
||||
const [confirmado, setConfirmado] = useState(false);
|
||||
const [fechaConfirmacion, setFechaConfirmacion] = useState(local.fechaConfirmacion || local.fecha);
|
||||
@@ -47,6 +50,12 @@ export default function LocalCard({ local, distanciaKm }) {
|
||||
setConfirmando(false);
|
||||
};
|
||||
|
||||
const handleAlternarFavorito = () => {
|
||||
const nuevoEstado = !favorito;
|
||||
alternarFavorito(local.id);
|
||||
mostrarToast(nuevoEstado ? "❤️ Añadido a favoritos" : "Quitado de favoritos", nuevoEstado ? "exito" : "info");
|
||||
};
|
||||
|
||||
const compartir = async () => {
|
||||
const url = `${window.location.origin}${window.location.pathname}?local=${local.id}`;
|
||||
const texto = `${local.nombre}${local.provincia ? ` · ${local.provincia}` : ""}`;
|
||||
@@ -82,7 +91,7 @@ export default function LocalCard({ local, distanciaKm }) {
|
||||
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 16, color: "var(--color-text-tertiary)", padding: 4 }}>
|
||||
<i className="ti ti-share-2" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" onClick={() => alternarFavorito(local.id)} title={favorito ? "Quitar de favoritos" : "Guardar en favoritos"}
|
||||
<button type="button" onClick={handleAlternarFavorito} title={favorito ? "Quitar de favoritos" : "Guardar en favoritos"}
|
||||
aria-pressed={favorito}
|
||||
style={{ background: "none", border: "none", cursor: "pointer", fontSize: 16, color: favorito ? "#8B0000" : "var(--color-text-tertiary)", padding: 4 }}>
|
||||
<i className={favorito ? "ti ti-heart-filled" : "ti ti-heart"} aria-hidden="true"></i>
|
||||
@@ -152,6 +161,8 @@ export default function LocalCard({ local, distanciaKm }) {
|
||||
)}
|
||||
<ReportarLocal localId={local.id} />
|
||||
</div>
|
||||
|
||||
<Toast toast={toast} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
const ESTILOS = {
|
||||
info: { bg: "#2C3E50", color: "white", icono: "ti-info-circle" },
|
||||
exito: { bg: "#1E8449", color: "white", icono: "ti-check" },
|
||||
error: { bg: "#C0392B", color: "white", icono: "ti-x" },
|
||||
};
|
||||
|
||||
// Aviso flotante breve, de solo lectura: se controla desde el hook useToast.
|
||||
export default function Toast({ toast }) {
|
||||
if (!toast) return null;
|
||||
const { bg, color, icono } = ESTILOS[toast.tipo] || ESTILOS.info;
|
||||
return (
|
||||
<div key={toast.key} role="status" aria-live="polite" style={{
|
||||
position: "fixed", left: "50%", bottom: 24, transform: "translateX(-50%)",
|
||||
background: bg, color, padding: "10px 18px", borderRadius: 999, fontSize: 13,
|
||||
fontWeight: 500, boxShadow: "0 6px 20px rgba(0,0,0,0.18)", zIndex: 2000,
|
||||
display: "flex", alignItems: "center", gap: 8, maxWidth: "90vw",
|
||||
animation: "localesp-toast-in 0.25s ease-out",
|
||||
}}>
|
||||
<i className={`ti ${icono}`} aria-hidden="true" style={{ fontSize: 15 }}></i>
|
||||
{toast.mensaje}
|
||||
<style>{"@keyframes localesp-toast-in{from{opacity:0;transform:translate(-50%,10px)}to{opacity:1;transform:translate(-50%,0)}}"}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user