cambios profundos
This commit is contained in:
+125
-111
@@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>May HairCare — Peluquería en Barcelona</title>
|
||||
<meta name="description" content="May HairCare, peluquería en Horta, Barcelona. Cortes, color, peinados y tratamientos para señoras, caballeros y niños.">
|
||||
<title>MAY Studio — Peluquería en Barcelona</title>
|
||||
<meta name="description" content="MAY Studio, peluquería en Horta, Barcelona. Cortes, color, peinados y tratamientos para señoras, caballeros y niños.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
@@ -14,12 +14,10 @@
|
||||
<!-- NAV -->
|
||||
<nav class="nav">
|
||||
<div class="container nav-inner">
|
||||
<a href="#" class="logo">May <span>HairCare</span></a>
|
||||
<a href="#" class="logo">MAY <span>Studio</span></a>
|
||||
<ul class="nav-links">
|
||||
<li><a href="#servicios">Servicios</a></li>
|
||||
<li><a href="#galeria">Galería</a></li>
|
||||
<li><a href="#profesional">La profesional</a></li>
|
||||
<li><a href="#horario">Horario</a></li>
|
||||
<li><a href="#galeria">Nuestro trabajo</a></li>
|
||||
<li><a href="#reservar">Reservar</a></li>
|
||||
<li><a href="#contacto">Contacto</a></li>
|
||||
</ul>
|
||||
@@ -30,34 +28,42 @@
|
||||
<!-- HERO -->
|
||||
<header class="hero">
|
||||
<div class="hero-bg">
|
||||
<img src="/assets/hero.jpg" alt="Interior del salón May HairCare">
|
||||
<img src="/assets/hero.jpg" alt="Interior del salón MAY Studio">
|
||||
</div>
|
||||
<div class="container hero-content">
|
||||
<h1>May HairCare<br>Tu peluquería en Horta</h1>
|
||||
<h1>MAY Studio<br>Tu peluquería en Horta</h1>
|
||||
<p class="hero-sub">Cortes, color, peinados y tratamientos capilares para toda la familia. Un espacio pensado para ti.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="#reservar" class="btn btn-primary">Reservar cita</a>
|
||||
<a href="#servicios" class="btn btn-secondary">Ver servicios</a>
|
||||
<a href="#galeria" class="btn btn-secondary">Ver nuestro trabajo</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- SERVICIOS -->
|
||||
<section id="servicios" class="section">
|
||||
<!-- PROFESIONAL (Mayra) -->
|
||||
<section id="profesional" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Nuestros servicios</h2>
|
||||
<p class="section-subtitle">Cuidamos de tu pelo con los mejores productos y técnicas</p>
|
||||
<div class="services-grid" id="services-grid">
|
||||
<p class="services-loading">Cargando servicios…</p>
|
||||
<div class="profesional-grid">
|
||||
<div class="profesional-img">
|
||||
<img src="/assets/may.jpg" alt="Mayra, peluquera profesional en MAY Studio">
|
||||
</div>
|
||||
<div class="profesional-info">
|
||||
<h3>Mayra</h3>
|
||||
<p class="profesional-role">Peluquera & fundadora</p>
|
||||
<p>Soy Mayra, fundadora de MAY Studio, un espacio dedicado al cuidado del cabello con un enfoque personalizado.</p>
|
||||
<p>Estoy especializada en color, mechas y cortes, buscando siempre resultados naturales, equilibrados y elegantes.</p>
|
||||
<p>Cada cliente recibe una atención individualizada, analizando su cabello y su estilo para crear un resultado que sea bonito, favorecedor y fácil de mantener día a día.</p>
|
||||
<p>MAY Studio nace con la idea de ofrecer un ambiente tranquilo donde el cuidado del cabello y la atención al detalle son lo más importante.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- GALERÍA -->
|
||||
<!-- GALERÍA / Nuestro trabajo -->
|
||||
<section id="galeria" class="section section-alt">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Nuestro trabajo</h2>
|
||||
<p class="section-subtitle">Algunos de los looks que hemos creado para nuestras clientas</p>
|
||||
<p class="section-subtitle">Explora algunos de los estilos y transformaciones que creamos en MAY Studio. Haz clic en cualquier imagen para ampliar.</p>
|
||||
<div class="gallery-grid">
|
||||
<div class="gallery-item"><img src="/assets/galeria-1.jpg" alt="Trabajo de peluquería"></div>
|
||||
<div class="gallery-item"><img src="/assets/galeria-2.jpg" alt="Trabajo de peluquería"></div>
|
||||
@@ -69,50 +75,32 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROFESIONAL -->
|
||||
<section id="profesional" class="section">
|
||||
<div class="container">
|
||||
<div class="profesional-grid">
|
||||
<div class="profesional-img">
|
||||
<img src="/assets/may.jpg" alt="May, peluquera profesional en May HairCare">
|
||||
</div>
|
||||
<div class="profesional-info">
|
||||
<h3>May</h3>
|
||||
<p class="profesional-role">Peluquera & fundadora</p>
|
||||
<p>Con más de 15 años de experiencia en peluquería profesional, May ha hecho del cuidado del cabello su vocación. Especializada en coloración, corte y tratamientos capilares, trabaja con técnicas actuales y productos de alta calidad para ofrecer resultados duraderos. Su trato cercano y la atención personalizada a cada cliente son la base de May HairCare.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HORARIO -->
|
||||
<section id="horario" class="section section-alt">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Horario</h2>
|
||||
<div class="schedule">
|
||||
<div class="schedule-row">
|
||||
<span>Lunes a Viernes</span>
|
||||
<span>9:30 — 13:30 / 16:30 — 20:30</span>
|
||||
</div>
|
||||
<div class="schedule-row">
|
||||
<span>Sábados</span>
|
||||
<span>9:00 — 14:00</span>
|
||||
</div>
|
||||
<div class="schedule-row closed">
|
||||
<span>Domingos y festivos</span>
|
||||
<span>Cerrado</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="schedule-note">Llámanos o reserva online. Te atendemos con o sin cita.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- RESERVAR -->
|
||||
<section id="reservar" class="section">
|
||||
<div class="container">
|
||||
<h2 class="section-title">Reservar cita</h2>
|
||||
<p class="section-subtitle">Elige uno o varios servicios, día y hora para tu cita</p>
|
||||
|
||||
<!-- Horario integrado -->
|
||||
<div style="max-width: 500px; margin: 0 auto 32px;">
|
||||
<h3 style="font-family: 'DM Serif Display', serif; font-size: 1.1rem; text-align: center; margin-bottom: 12px;">Horario</h3>
|
||||
<div class="schedule">
|
||||
<div class="schedule-row">
|
||||
<span>Lunes a Viernes</span>
|
||||
<span>9:30 — 13:30 / 16:30 — 20:30</span>
|
||||
</div>
|
||||
<div class="schedule-row">
|
||||
<span>Sábados</span>
|
||||
<span>9:00 — 14:00</span>
|
||||
</div>
|
||||
<div class="schedule-row closed">
|
||||
<span>Domingos y festivos</span>
|
||||
<span>Cerrado</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="schedule-note">Llámanos o reserva online. Te atendemos con o sin cita.</p>
|
||||
</div>
|
||||
|
||||
<!-- Service selector -->
|
||||
<div class="service-selector" id="service-selector">
|
||||
<p class="services-loading">Cargando servicios…</p>
|
||||
@@ -138,7 +126,7 @@
|
||||
</div>
|
||||
<!-- Right: slots + contact form -->
|
||||
<div class="cal-right">
|
||||
<div id="slots-placeholder" class="slots-placeholder">Selecciona primero el servicio que deseas</div>
|
||||
<div id="slots-placeholder" class="slots-placeholder">Selecciona primero el/los servicio(s) que deseas</div>
|
||||
<div id="slots-container" style="display:none">
|
||||
<h3 class="slots-title" id="slots-title"></h3>
|
||||
<div class="slots-grid" id="slots-grid"></div>
|
||||
@@ -195,7 +183,7 @@
|
||||
<iframe
|
||||
src="https://www.openstreetmap.org/export/embed.html?bbox=2.1550%2C41.4380%2C2.1650%2C41.4430&layer=mapnik&marker=41.4405%2C2.1600"
|
||||
width="100%" height="300" style="border:0; border-radius: 12px;"
|
||||
loading="lazy" title="Mapa May HairCare">
|
||||
loading="lazy" title="Mapa MAY Studio">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,8 +193,8 @@
|
||||
<!-- FOOTER -->
|
||||
<footer class="footer">
|
||||
<div class="container footer-inner">
|
||||
<span class="logo">May <span>HairCare</span></span>
|
||||
<p>© 2026 May HairCare. Tu peluquería en Horta.</p>
|
||||
<span class="logo">MAY <span>Studio</span></span>
|
||||
<p>© 2026 MAY Studio. Tu peluquería en Horta.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -221,59 +209,94 @@
|
||||
const PROVIDER_ID = 1;
|
||||
|
||||
// ---- Service selector state ----
|
||||
let selectedCategory = null;
|
||||
let selectedServices = []; // [{id, nombre, duracion}]
|
||||
let totalDuration = 0;
|
||||
let allServices = []; // cache de servicios EA
|
||||
|
||||
// ---- Services (ahora desde EasyAppointments) ----
|
||||
async function loadServices() {
|
||||
const grid = document.getElementById('services-grid');
|
||||
// ---- Categories and Services from EasyAppointments ----
|
||||
async function loadCategories() {
|
||||
try {
|
||||
const res = await fetch('/ea-api/v1/services');
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
const services = await res.json();
|
||||
let res = await fetch('/ea-api/v1/categories');
|
||||
if (!res.ok) {
|
||||
res = await fetch('/ea-api/v1/service_categories');
|
||||
}
|
||||
if (!res.ok) return [];
|
||||
return await res.json();
|
||||
} catch (e) {
|
||||
console.warn('No se pudieron cargar las categorías de EA');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadServices() {
|
||||
const selectorContainer = document.getElementById('service-selector');
|
||||
try {
|
||||
const [servicesRes, categories] = await Promise.all([
|
||||
fetch('/ea-api/v1/services'),
|
||||
loadCategories()
|
||||
]);
|
||||
if (!servicesRes.ok) throw new Error('HTTP ' + servicesRes.status);
|
||||
const services = await servicesRes.json();
|
||||
allServices = services;
|
||||
|
||||
// Agrupamos en una categoría simple (puedes crear categorías en EA y extender esto)
|
||||
const fakeCategorias = [{
|
||||
nombre: "Servicios",
|
||||
imagen: "",
|
||||
servicios: services.map(s => ({
|
||||
// Filter services to those assigned to this provider
|
||||
let displayServices = services;
|
||||
try {
|
||||
const provRes = await fetch(`/ea-api/v1/providers/${PROVIDER_ID}`);
|
||||
if (provRes.ok) {
|
||||
const prov = await provRes.json();
|
||||
const allowed = (prov.services || prov.service_ids || []).map(id => parseInt(id));
|
||||
if (allowed.length > 0) {
|
||||
displayServices = services.filter(s => allowed.includes(parseInt(s.id)));
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
const catMap = {};
|
||||
(categories || []).forEach(cat => {
|
||||
catMap[cat.id] = cat;
|
||||
});
|
||||
|
||||
const grouped = {};
|
||||
displayServices.forEach(s => {
|
||||
const catId = s.categoryId ?? s.serviceCategoryId ?? 'uncat';
|
||||
if (!grouped[catId]) {
|
||||
const catInfo = catMap[catId] || {};
|
||||
grouped[catId] = {
|
||||
nombre: catInfo.name || catInfo.title || 'Otros servicios',
|
||||
imagen: '',
|
||||
servicios: []
|
||||
};
|
||||
}
|
||||
grouped[catId].servicios.push({
|
||||
id: s.id,
|
||||
nombre: s.name,
|
||||
precio: (parseFloat(s.price) || 0) + " €",
|
||||
duracion: s.duration || 30
|
||||
}))
|
||||
}];
|
||||
});
|
||||
});
|
||||
|
||||
renderServices(fakeCategorias, grid);
|
||||
let categorias = Object.values(grouped);
|
||||
// Sort categories alphabetically for consistent order
|
||||
categorias.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es'));
|
||||
|
||||
// Sort services inside each category
|
||||
categorias.forEach(cat => {
|
||||
cat.servicios.sort((a, b) => a.nombre.localeCompare(b.nombre, 'es'));
|
||||
});
|
||||
|
||||
if (categorias.length === 0 || displayServices.length === 0) {
|
||||
selectorContainer.innerHTML = '<p class="services-loading">No hay servicios asignados para este proveedor o categorías.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
renderServiceSelector(categorias);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
grid.innerHTML = '<p class="services-loading">No se pudieron cargar los servicios desde EasyAppointments.</p>';
|
||||
if (selectorContainer) selectorContainer.innerHTML = '<p class="services-loading">No se pudieron cargar los servicios desde EasyAppointments.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderServices(categorias, grid) {
|
||||
grid.innerHTML = categorias.map(cat => `
|
||||
<div class="service-card">
|
||||
<div class="service-img">
|
||||
<img src="${esc(cat.imagen)}" alt="${esc(cat.nombre)}">
|
||||
</div>
|
||||
<div class="service-body">
|
||||
<h3>${esc(cat.nombre)}</h3>
|
||||
<ul class="service-list">
|
||||
${cat.servicios.map(s => `
|
||||
<li><span>${esc(s.nombre)}</span><span class="price">${esc(s.precio)}</span></li>
|
||||
`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
renderServiceSelector(categorias);
|
||||
}
|
||||
|
||||
function renderServiceSelector(categorias) {
|
||||
const container = document.getElementById('service-selector');
|
||||
container.innerHTML = categorias.map(cat => `
|
||||
@@ -296,7 +319,10 @@
|
||||
`).join('');
|
||||
|
||||
// Listener se añade una vez desde loadServices
|
||||
container.addEventListener('change', handleServiceChange);
|
||||
if (!container.dataset.listenerAttached) {
|
||||
container.addEventListener('change', handleServiceChange);
|
||||
container.dataset.listenerAttached = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
function handleServiceChange(e) {
|
||||
@@ -315,11 +341,7 @@
|
||||
totalDuration += parseInt(cb.dataset.duracion) || 30;
|
||||
});
|
||||
|
||||
// Lock to one category
|
||||
const first = document.querySelector('#service-selector input[type=checkbox]:checked');
|
||||
selectedCategory = first ? first.dataset.cat : null;
|
||||
|
||||
updateServiceDisabledState();
|
||||
// No lock to category anymore - services from different categories can be selected
|
||||
updateServiceSummary();
|
||||
|
||||
if (selectedDate) {
|
||||
@@ -331,19 +353,11 @@
|
||||
} else {
|
||||
const ph = document.getElementById('slots-placeholder');
|
||||
ph.textContent = selectedServices.length === 0
|
||||
? 'Selecciona primero el servicio que deseas'
|
||||
? 'Selecciona primero el/los servicio(s) que deseas'
|
||||
: 'Selecciona un día en el calendario';
|
||||
}
|
||||
}
|
||||
|
||||
function updateServiceDisabledState() {
|
||||
document.querySelectorAll('#service-selector .svc-cat-card').forEach(card => {
|
||||
const locked = selectedCategory !== null && card.dataset.cat !== selectedCategory;
|
||||
card.classList.toggle('svc-cat-locked', locked);
|
||||
card.querySelectorAll('input[type=checkbox]').forEach(cb => { cb.disabled = locked; });
|
||||
});
|
||||
}
|
||||
|
||||
function updateServiceSummary() {
|
||||
const bar = document.getElementById('service-summary');
|
||||
if (selectedServices.length === 0) { bar.style.display = 'none'; return; }
|
||||
@@ -582,7 +596,7 @@
|
||||
lastName: lastName,
|
||||
email: email,
|
||||
phone: telefono || '',
|
||||
notes: 'Creado desde web May HairCare'
|
||||
notes: 'Creado desde web MAY Studio'
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user