365 lines
16 KiB
HTML
365 lines
16 KiB
HTML
{% load static %}
|
|
{% load cache %}
|
|
{% load compress %}
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="description" content="Sitio web de comercio local Almeriense">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
|
<link rel="preload" href="{% static 'css/custom.css' %}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
<noscript><link rel="stylesheet" href="{% static 'css/custom.css' %}"></noscript>
|
|
<style>
|
|
.search-suggestions-container {
|
|
position: relative;
|
|
flex-grow: 1;
|
|
margin: 0 12px;
|
|
}
|
|
|
|
.search-suggestions {
|
|
display: none;
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
background: white;
|
|
border: 1px solid #dee2e6;
|
|
border-top: none;
|
|
border-radius: 0 0 4px 4px;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
z-index: 1000;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.search-suggestions.show {
|
|
display: block;
|
|
}
|
|
|
|
.search-suggestion-item {
|
|
padding: 12px 15px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
cursor: pointer;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.search-suggestion-item:hover,
|
|
.search-suggestion-item.active {
|
|
background-color: #f8f9fa;
|
|
outline: 2px solid #0d6efd;
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
.search-suggestion-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.suggestion-name {
|
|
font-weight: 500;
|
|
color: #212529;
|
|
display: block;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.suggestion-price {
|
|
font-size: 0.875rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.search-highlight {
|
|
font-weight: 600;
|
|
color: #0d6efd;
|
|
}
|
|
</style>
|
|
{% block head %}{% endblock %}
|
|
</head>
|
|
<body class="d-flex flex-column min-vh-100">
|
|
<a href="#main-content" class="skip-link">Saltar al contenido</a>
|
|
{% cache 500 sidebar request.user.username %}
|
|
<nav class="navbar navbar-expand-md header" role="banner">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="{% url 'home' %}">
|
|
<picture>
|
|
<source
|
|
type="image/webp"
|
|
srcset="{% static 'img/logo-40.webp' %} 1x, {% static 'img/logo-80.webp' %} 2x">
|
|
<img
|
|
src="{% static 'img/logo-40.png' %}"
|
|
srcset="{% static 'img/logo-40.png' %} 1x, {% static 'img/logo-80.png' %} 2x"
|
|
alt="Logo"
|
|
width="40"
|
|
height="40"
|
|
decoding="async">
|
|
</picture>
|
|
</a>
|
|
|
|
<span class="navbar-text fw-semibold site-title-mobile d-md-none">Comercialmeria</span>
|
|
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
|
|
<div class="collapse navbar-collapse" id="navbarContent">
|
|
<span class="navbar-text fw-semibold me-3 d-none d-md-inline site-title-desktop" style="color: #ffffff">Comercialmeria</span>
|
|
<!-- Barra de búsqueda con sugerencias -->
|
|
<form class="search-suggestions-container" method="GET" action="{% url 'search' %}" role="search" id="searchForm">
|
|
<div class="input-group">
|
|
<input class="form-control" type="search" name="q" id="searchInput" placeholder="Buscar productos..." aria-label="Buscar" autocomplete="off" role="combobox" aria-expanded="false" aria-autocomplete="list" aria-controls="searchSuggestions" aria-activedescendant="" aria-haspopup="listbox">
|
|
<button class="btn btn-outline-primary" type="submit" aria-label="Buscar productos">🔍 Buscar</button>
|
|
</div>
|
|
<div class="search-suggestions" id="searchSuggestions" role="listbox" aria-label="Sugerencias de búsqueda"></div>
|
|
</form>
|
|
|
|
<div class="navbar-nav ms-auto d-flex align-items-md-center gap-2 flex-wrap" role="navigation">
|
|
<a href="{% url 'view_cart' %}" class="nav-link position-relative btn btn-outline-primary btn-sm">
|
|
🛒 Carrito
|
|
{% if cart_count > 0 %}
|
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.65rem;">
|
|
{{ cart_count }}
|
|
</span>
|
|
{% endif %}
|
|
</a>
|
|
|
|
{% if user.is_authenticated %}
|
|
<a href="{% url 'mis_productos' %}" class="nav-link btn btn-outline-secondary btn-sm">Panel Vendedor</a>
|
|
<a href="{% url 'portal_usuario' %}" class="nav-link btn btn-outline-light btn-sm">{{ user.first_name|default:user.username }}</a>
|
|
<a href="{% url 'logout' %}" class="nav-link btn btn-primary btn-sm">Cerrar Sesión</a>
|
|
{% else %}
|
|
<a href="{% url 'login' %}" class="nav-link btn btn-primary btn-sm">Iniciar Sesión</a>
|
|
<a href="{% url 'register' %}" class="nav-link btn btn-outline-primary btn-sm">Registrarse</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
{% endcache %}
|
|
|
|
<div id="main-content" class="container-fluid flex-grow-1 d-flex flex-column" role="main">
|
|
<!-- Mensajes -->
|
|
{% if messages %}
|
|
<div class="row mt-3">
|
|
<div class="col-12">
|
|
{% for message in messages %}
|
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
|
{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Contenido-->
|
|
{% block content %}{% endblock %}
|
|
|
|
{% cache 500 footer %}
|
|
<!-- Footer-->
|
|
<footer id="footer" class="row pt-4 pb-3 mt-auto" role="contentinfo">
|
|
<div class="col-12">
|
|
<div class="row text-center gy-3">
|
|
<div class="col-12 col-md-4">
|
|
<h6 class="fw-semibold text-uppercase mb-2">Información Legal</h6>
|
|
<ul class="list-unstyled mb-0">
|
|
<li><a href="{% url 'privacidad' %}" class="footer-link">Política de Privacidad</a></li>
|
|
<li><a href="{% url 'aviso_legal' %}" class="footer-link">Aviso Legal</a></li>
|
|
<li><a href="{% url 'terminos' %}" class="footer-link">Términos y Condiciones</a></li>
|
|
<li><a href="{% url 'cookies' %}" class="footer-link">Política de Cookies</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<h6 class="fw-semibold text-uppercase mb-2">Compras</h6>
|
|
<ul class="list-unstyled mb-0">
|
|
<li><a href="{% url 'devoluciones' %}" class="footer-link">Política de Devoluciones</a></li>
|
|
<li><a href="{% url 'ayuda' %}" class="footer-link">Centro de Ayuda</a></li>
|
|
<li><a href="{% url 'ayuda' %}#envios" class="footer-link">Información de Envíos</a></li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<h6 class="fw-semibold text-uppercase mb-2">Comercialmeria</h6>
|
|
<ul class="list-unstyled mb-0">
|
|
<li><a href="{% url 'sobre_nosotros' %}" class="footer-link">Sobre Nosotros</a></li>
|
|
<li><a href="{% url 'mis_productos' %}" class="footer-link">Vende con Nosotros</a></li>
|
|
<li><a href="mailto:example@example.com" class="footer-link">Contacto</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<hr class="mt-3 mb-2">
|
|
<p class="text-center mb-0" style="font-size: 0.85rem;">© 2026 Comercialmeria — Comercio local en Almería, España</p>
|
|
</div>
|
|
</footer>
|
|
{% endcache %}
|
|
</div>
|
|
{% cache 500 scripts %}
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
|
|
|
<script>
|
|
// Sistema de sugerencias de búsqueda en tiempo real
|
|
const searchInput = document.getElementById('searchInput');
|
|
const searchSuggestions = document.getElementById('searchSuggestions');
|
|
const searchForm = document.getElementById('searchForm');
|
|
let searchTimeout;
|
|
let currentFocusIndex = -1;
|
|
|
|
// Helpers para gestionar el estado ARIA del combobox
|
|
function openSuggestions() {
|
|
searchSuggestions.classList.add('show');
|
|
searchInput.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
function closeSuggestions() {
|
|
searchSuggestions.classList.remove('show');
|
|
searchInput.setAttribute('aria-expanded', 'false');
|
|
searchInput.setAttribute('aria-activedescendant', '');
|
|
currentFocusIndex = -1;
|
|
}
|
|
|
|
function updateFocus(options) {
|
|
options.forEach((option, index) => {
|
|
const active = index === currentFocusIndex;
|
|
option.classList.toggle('active', active);
|
|
option.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
});
|
|
if (currentFocusIndex >= 0) {
|
|
const activeOption = options[currentFocusIndex];
|
|
searchInput.setAttribute('aria-activedescendant', activeOption.id);
|
|
activeOption.scrollIntoView({ block: 'nearest' });
|
|
} else {
|
|
searchInput.setAttribute('aria-activedescendant', '');
|
|
}
|
|
}
|
|
|
|
// Escuchar cambios en el input
|
|
searchInput.addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
const query = this.value.trim();
|
|
|
|
if (query.length < 2) {
|
|
closeSuggestions();
|
|
return;
|
|
}
|
|
|
|
// Debounce: esperar 300ms después de dejar de escribir
|
|
searchTimeout = setTimeout(() => {
|
|
fetchSuggestions(query);
|
|
}, 300);
|
|
});
|
|
|
|
// Navegación por teclado (ArrowDown/ArrowUp/Enter/Escape)
|
|
searchInput.addEventListener('keydown', function(event) {
|
|
const options = searchSuggestions.querySelectorAll('[role="option"]');
|
|
if (!options.length || !searchSuggestions.classList.contains('show')) {
|
|
return;
|
|
}
|
|
|
|
if (event.key === 'ArrowDown') {
|
|
event.preventDefault();
|
|
currentFocusIndex = Math.min(currentFocusIndex + 1, options.length - 1);
|
|
updateFocus(options);
|
|
} else if (event.key === 'ArrowUp') {
|
|
event.preventDefault();
|
|
currentFocusIndex = Math.max(currentFocusIndex - 1, -1);
|
|
updateFocus(options);
|
|
} else if (event.key === 'Enter' && currentFocusIndex >= 0) {
|
|
event.preventDefault();
|
|
const selected = options[currentFocusIndex];
|
|
window.location.href = selected.dataset.href;
|
|
} else if (event.key === 'Escape') {
|
|
closeSuggestions();
|
|
searchInput.focus();
|
|
}
|
|
});
|
|
|
|
// Función para obtener sugerencias del servidor
|
|
function fetchSuggestions(query) {
|
|
fetch(`{% url 'search_suggestions' %}?q=${encodeURIComponent(query)}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
displaySuggestions(data.suggestions, query);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching suggestions:', error);
|
|
closeSuggestions();
|
|
});
|
|
}
|
|
|
|
// Función para mostrar las sugerencias
|
|
function displaySuggestions(suggestions, query) {
|
|
currentFocusIndex = -1;
|
|
if (suggestions.length === 0) {
|
|
searchSuggestions.innerHTML = '<div class="search-suggestion-item text-muted">No se encontraron productos</div>';
|
|
openSuggestions();
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
suggestions.forEach((suggestion, index) => {
|
|
// Resaltar la coincidencia en el nombre
|
|
const highlightedName = highlightMatch(suggestion.name, query);
|
|
const priceWithVAT = (suggestion.price * 1.21).toFixed(2);
|
|
html += `
|
|
<div class="search-suggestion-item" role="option" id="search-option-${index}"
|
|
aria-selected="false" tabindex="-1"
|
|
data-href="/tienda/producto/${suggestion.id}"
|
|
onclick="window.location.href=this.dataset.href">
|
|
<span class="suggestion-name">${highlightedName}</span>
|
|
<span class="suggestion-price">€${priceWithVAT}</span>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
searchSuggestions.innerHTML = html;
|
|
openSuggestions();
|
|
}
|
|
|
|
// Función para resaltar el texto que coincide
|
|
function highlightMatch(text, query) {
|
|
const regex = new RegExp(`(${query})`, 'gi');
|
|
return text.replace(regex, '<span class="search-highlight">$1</span>');
|
|
}
|
|
|
|
// Cerrar sugerencias cuando se hace clic fuera
|
|
document.addEventListener('click', function(event) {
|
|
if (!searchForm.contains(event.target)) {
|
|
closeSuggestions();
|
|
}
|
|
});
|
|
|
|
// Cerrar sugerencias al enviar el formulario
|
|
searchForm.addEventListener('submit', function() {
|
|
closeSuggestions();
|
|
});
|
|
|
|
// Mostrar sugerencias al hacer clic en el input (si hay texto)
|
|
searchInput.addEventListener('focus', function() {
|
|
if (this.value.trim().length >= 2 && searchSuggestions.innerHTML) {
|
|
openSuggestions();
|
|
}
|
|
});
|
|
</script>
|
|
{% endcache %}
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css" rel="stylesheet" integrity="sha384-b7166c239e461f42296ad7248c04ef6768e9340a51aef45fad197acf8f4c16f119f36376b19516548885a9ecabdccc10" />
|
|
<script type="module">
|
|
import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js';
|
|
|
|
createChat({
|
|
webhookUrl: 'https://n8n.elordenador.org/webhook/0e2cbe42-39d2-4e86-be62-c12542e246d4/chat',
|
|
initialMessages: [
|
|
'¡Hola! 👋',
|
|
'Soy el asistente virtual de Comercialmeria. ¿En qué puedo ayudarte?'
|
|
],
|
|
i18n: {
|
|
en: {
|
|
title: 'Chat de Soporte',
|
|
subtitle: 'Estamos aquí para ayudarte 24/7.',
|
|
footer: '',
|
|
getStarted: 'Nueva conversación',
|
|
inputPlaceholder: 'Escribe tu mensaje...',
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|