Files
yogibook_aury_new/public/assets/js/yb-shop.js
T
2026-10-14 10:21:55 +02:00

1187 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* YogiBook - negozio della scuola.
* Catalogo, scheda prodotto e scelte (classe fissa, data, destinatario del buono).
* Pensato per essere riusato anche dal plugin WordPress:
* YBShop.mount(elemento, dati, { onCheckout: function (scelta) { ... } })
* "dati" è l'oggetto prodotto da ShopService::catalog().
* Indirizzi: #/slug (scheda prodotto), #/slug/scegli (passo delle scelte).
*/
(function () {
"use strict";
var MESI = [
"gennaio",
"febbraio",
"marzo",
"aprile",
"maggio",
"giugno",
"luglio",
"agosto",
"settembre",
"ottobre",
"novembre",
"dicembre",
];
var TONES = {
single: "#DBF6F9",
fixed: "#DBF6F9",
free: "#F8EFE4",
membership: "#F8EFE4",
voucher: "#E2CEB1",
};
var GROUPS = [
["single", "Per iniziare"],
["fixed", "Classe fissa"],
["free", "Flessibili"],
["membership", "Tessera"],
["voucher", "Regali"],
];
function esc(s) {
return String(s === null || s === undefined ? "" : s).replace(
/[&<>"']/g,
function (c) {
return {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
}[c];
},
);
}
function euro(n) {
return (
Number(n).toLocaleString("it-IT", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}) + " €"
);
}
function fmtDate(iso) {
var p = String(iso).split("-");
return (
parseInt(p[2], 10) + " " + MESI[parseInt(p[1], 10) - 1] + " " + p[0]
);
}
function addDays(iso, n) {
var p = iso.split("-"),
d = new Date(+p[0], +p[1] - 1, +p[2] + n);
return (
d.getFullYear() +
"-" +
String(d.getMonth() + 1).padStart(2, "0") +
"-" +
String(d.getDate()).padStart(2, "0")
);
}
function unitLabel(n, unit) {
var l = {
days: ["giorno", "giorni"],
weeks: ["settimana", "settimane"],
months: ["mese", "mesi"],
}[unit] || ["", ""];
return n + " " + (n === 1 ? l[0] : l[1]);
}
function mount(root, DATA, opts) {
opts = opts || {};
// cls = classe scelta nell'orario; prefSvc = classe da preselezionare nella scheda (classe fissa)
var S = {
filter: "",
cat: 0,
pid: null,
vid: null,
picked: [],
occ: null,
rName: "",
rEmail: "",
rMsg: "",
img: 0,
cls: 0,
prefSvc: 0,
from: "",
};
var lastView = null;
/* ---------------- dati ---------------- */
function byId(id) {
for (var i = 0; i < DATA.products.length; i++)
if (DATA.products[i].id === id) return DATA.products[i];
return null;
}
function bySlug(slug) {
for (var i = 0; i < DATA.products.length; i++)
if (DATA.products[i].slug === slug) return DATA.products[i];
return null;
}
function prod() {
return byId(S.pid);
}
function variant(p) {
p = p || prod();
if (!p) return null;
for (var i = 0; i < p.variants.length; i++)
if (p.variants[i].id === S.vid) return p.variants[i];
return null;
}
function svc(id) {
return DATA.services[id] || null;
}
function needsChoose(p) {
return (
!!p &&
(p.mode === "fixed" ||
p.mode === "single" ||
p.mode === "voucher")
);
}
function mark(p) {
if (p.mode === "fixed") return p.slots === 2 ? "2×" : "1×";
if (p.mode === "single") return p.once ? "Prova" : "1";
if (p.mode === "membership") return "Tessera";
if (p.mode === "voucher") return "Regalo";
var v = p.variants[0];
return v.lessons ? String(v.lessons) : "∞";
}
function ruleLine(p) {
var lessons = p.variants
.map(function (v) {
return v.lessons;
})
.filter(Boolean);
var range = lessons.length
? (Math.min.apply(null, lessons) ===
Math.max.apply(null, lessons)
? Math.min.apply(null, lessons)
: Math.min.apply(null, lessons) +
"–" +
Math.max.apply(null, lessons)) + " lezioni"
: "";
if (p.mode === "fixed")
return (
(p.slots === 2
? "2 classi fisse a settimana"
: "1 classe fissa a settimana") +
(range ? " · " + range : "")
);
if (p.mode === "free")
return (
"Prenoti tu" +
(p.weekly ? " · max " + p.weekly + "/sett." : "") +
(range ? " · " + range : "")
);
if (p.mode === "single") return "Scegli giorno e ora";
if (p.mode === "membership") return "Quota associativa";
return "Da regalare con un codice";
}
function validityText(p, v) {
if (p.mode === "membership") {
if (v.vmode === "calendar_year")
return "Valida fino al " + fmtDate(DATA.yearEnd);
if (v.vmode === "season")
return "Valida fino al " + fmtDate(DATA.seasonEnd);
if (v.vmode === "duration" && v.vvalue)
return (
"Valida " +
unitLabel(v.vvalue, v.vunit) +
" dall'acquisto"
);
return "Senza scadenza";
}
if (p.mode === "voucher")
return v.vvalue
? "Il buono scade dopo " + unitLabel(v.vvalue, v.vunit)
: "Il buono non scade";
if (p.mode === "single") return "Valida per la lezione che scegli";
if (v.vmode === "none" || !v.vvalue) return "Senza scadenza";
return (
"Da usare entro " +
unitLabel(v.vvalue, v.vunit) +
(v.vstart === "purchase"
? " dall'acquisto"
: " dalla prima lezione")
);
}
/* ---------------- posti ---------------- */
// Classe fissa: minimo dei posti liberi sulle prossime N lezioni coperte dal pacchetto
function fixedSeats(sid, p, v) {
var s = svc(sid);
var n = v && v.lessons ? Math.ceil(v.lessons / (p.slots || 1)) : 8;
var occ = s ? s.occ.slice(0, n) : [];
if (!occ.length)
return {
cls: "none",
text: "Nessuna lezione in calendario",
ok: false,
};
if (s.cap === null)
return { cls: "", text: "Posti disponibili", ok: true };
var min = Math.min.apply(
null,
occ.map(function (o) {
return o.free;
}),
);
if (min <= 0)
return { cls: "full", text: "Al completo", ok: false };
return {
cls: min <= 2 ? "low" : "",
text: min === 1 ? "Ultimo posto" : min + " posti liberi",
ok: true,
};
}
function seatsBadge(o) {
if (o.mine)
return { cls: "mine", text: "Sei già iscritta", ok: false };
if (o.free === null)
return { cls: "", text: "Posti disponibili", ok: true };
if (o.free <= 0)
return { cls: "full", text: "Al completo", ok: false };
return {
cls: o.free <= 2 ? "low" : "",
text: o.free === 1 ? "Ultimo posto" : o.free + " posti",
ok: true,
};
}
function singleOccurrences(p) {
var limit = addDays(DATA.today, DATA.singleDays || 21),
list = [];
p.services.forEach(function (sid) {
var s = svc(sid);
if (!s) return;
s.occ.forEach(function (o) {
if (o.date <= limit) list.push({ sid: sid, o: o, s: s });
});
});
list.sort(function (a, b) {
return (a.o.date + a.o.time).localeCompare(b.o.date + b.o.time);
});
return list.slice(0, 40);
}
function chooseValid(p) {
if (!p) return false;
if (p.mode === "fixed") return S.picked.length === (p.slots || 1);
if (p.mode === "single") return !!S.occ;
if (p.mode === "voucher")
return (
S.rName.trim() !== "" &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(S.rEmail.trim())
);
return true;
}
/* ---------------- render ---------------- */
function cover(p, cls) {
var img = p.cover || (p.gallery && p.gallery[0]) || "";
return img
? '<div class="' +
cls +
'" style="background-image:url(\'' +
esc(img) +
"');\">"
: '<div class="' +
cls +
'" style="background-color:' +
TONES[p.mode] +
';"><span class="mark">' +
esc(mark(p)) +
"</span>";
}
var DAYS = [
"Domenica",
"Lunedì",
"Martedì",
"Mercoledì",
"Giovedì",
"Venerdì",
"Sabato",
];
var DAY_ORDER = [1, 2, 3, 4, 5, 6, 0];
var CAT_COLORS = [
"#6FBABE",
"#E2A86B",
"#9C8FD6",
"#7FB77E",
"#E58F9E",
"#5A9BD5",
"#C9A876",
];
function catColor(s) {
if (s.color) return s.color;
var i = 0;
(DATA.categories || []).forEach(function (c, idx) {
if (c.id === s.cat) i = idx;
});
return CAT_COLORS[i % CAT_COLORS.length];
}
function endTime(start, minutes) {
if (!minutes) return "";
var p = start.split(":"),
t = +p[0] * 60 + +p[1] + minutes;
return (
String(Math.floor(t / 60) % 24).padStart(2, "0") +
":" +
String(t % 60).padStart(2, "0")
);
}
function productsWith(sid) {
return DATA.products.filter(function (p) {
return p.kind === "lessons" && p.services.indexOf(sid) !== -1;
});
}
function renderTop(active) {
var h =
'<div class="ys-head"><div><h1 class="ys-h1">Scegli come praticare</h1><p class="ys-lead">' +
(active === "calendar"
? "Parti dall'orario: scegli una classe e vedi con quali pacchetti puoi frequentarla."
: "Pacchetti con la tua classe fissa, ingressi flessibili, una lezione singola o un regalo.") +
"</p></div>";
var u = DATA.user || {};
if (u.packages && u.packages.length) {
h +=
'<div class="ys-mine"><b>I tuoi pacchetti attivi</b>' +
u.packages
.slice(0, 3)
.map(function (k) {
return (
"<div><strong>" +
esc(k.title) +
"</strong><span>" +
(k.pending
? "in attesa di pagamento"
: (k.left !== null
? k.left + " lezioni"
: "") +
(k.expire
? " · fino al " + fmtDate(k.expire)
: "")) +
"</span></div>"
);
})
.join("") +
"</div>";
}
h += "</div>";
if (u.membershipRequired && !u.hasMembership) {
h +=
'<div class="ys-alert"><i class="bx bx-id-card"></i><div>Per frequentare le lezioni serve la <strong>tessera associativa</strong> valida.' +
(u.membershipProduct
? ' <a href="#/' +
esc(byId(u.membershipProduct).slug) +
'">Acquista la tessera</a>'
: " Chiedi in segreteria.") +
"</div></div>";
}
var hasCal = Object.keys(DATA.services).some(function (k) {
return DATA.services[k].occ.length > 0;
});
if (hasCal) {
h +=
'<div class="ys-tabs" role="tablist">' +
'<button type="button" role="tab" aria-selected="' +
(active === "products") +
'" class="ys-tab' +
(active === "products" ? " on" : "") +
'" data-act="tab" data-id="products"><i class="bx bx-grid-alt"></i> Prodotti</button>' +
'<button type="button" role="tab" aria-selected="' +
(active === "calendar") +
'" class="ys-tab' +
(active === "calendar" ? " on" : "") +
'" data-act="tab" data-id="calendar"><i class="bx bx-calendar-week"></i> Orario settimanale</button></div>';
}
return h;
}
function catSelect() {
if (!DATA.categories || DATA.categories.length < 2) return "";
return (
'<select class="ys-select" data-act="cat" aria-label="Disciplina"><option value="0">Tutte le discipline</option>' +
DATA.categories
.map(function (c) {
return (
'<option value="' +
c.id +
'"' +
(S.cat === c.id ? " selected" : "") +
">" +
esc(c.name) +
"</option>"
);
})
.join("") +
"</select>"
);
}
function productCard(p, fromSvc) {
var min = Math.min.apply(
null,
p.variants.map(function (v) {
return v.price;
}),
);
return (
'<button type="button" class="ys-card" data-act="open" data-id="' +
p.id +
'"' +
(fromSvc ? ' data-svc="' + fromSvc + '"' : "") +
">" +
cover(p, "ys-card-img") +
(p.blocked === "used"
? '<span class="ys-badge muted">Già utilizzata</span>'
: p.badge
? '<span class="ys-badge">' + esc(p.badge) + "</span>"
: "") +
"</div>" +
'<span class="ys-card-body"><span class="ys-card-title">' +
esc(p.title) +
"</span>" +
(p.subtitle
? '<span class="ys-card-sub">' + esc(p.subtitle) + "</span>"
: "") +
'<span class="ys-card-rule">' +
esc(ruleLine(p)) +
"</span>" +
'<span class="ys-card-foot"><span class="ys-price-from">' +
(p.variants.length > 1 ? "<small>da </small>" : "") +
euro(min) +
"</span></span></span></button>"
);
}
function renderCatalog() {
var h = renderTop("products");
var present = GROUPS.filter(function (g) {
return DATA.products.some(function (p) {
return p.group === g[0];
});
});
h +=
'<div class="ys-filters" role="toolbar" aria-label="Filtra i prodotti"><button type="button" class="ys-pill' +
(S.filter === "" ? " on" : "") +
'" data-act="filter" data-id="">Tutti</button>' +
present
.map(function (g) {
return (
'<button type="button" class="ys-pill' +
(S.filter === g[0] ? " on" : "") +
'" data-act="filter" data-id="' +
g[0] +
'">' +
esc(g[1]) +
"</button>"
);
})
.join("") +
catSelect() +
"</div>";
var list = DATA.products.filter(function (p) {
if (S.filter && p.group !== S.filter) return false;
if (
S.cat &&
p.kind === "lessons" &&
p.cats.indexOf(S.cat) === -1
)
return false;
if (S.cat && p.kind !== "lessons" && S.filter === "")
return false;
return true;
});
if (!DATA.products.length)
return (
h +
'<div class="ys-noresult">Al momento non ci sono prodotti in vendita.</div>'
);
if (!list.length)
return (
h +
'<div class="ys-noresult">Nessun prodotto con questi filtri.</div>'
);
return (
h +
'<div class="ys-grid">' +
list
.map(function (p) {
return productCard(p);
})
.join("") +
"</div>"
);
}
/* Orario settimanale: le classi ricavate dalla prossima lezione in calendario */
function renderCalendar() {
var h = renderTop("calendar");
h +=
'<div class="ys-filters">' +
catSelect() +
'<span class="ys-muted" style="margin-left:auto;">Tocca una classe per vedere come frequentarla</span></div>';
var cols = {};
DAY_ORDER.forEach(function (d) {
cols[d] = [];
});
Object.keys(DATA.services).forEach(function (k) {
var s = DATA.services[k],
sid = +k;
if (
!s.occ.length ||
(S.cat && s.cat !== S.cat) ||
!productsWith(sid).length
)
return;
var o = s.occ[0],
p = o.date.split("-");
var wd = new Date(+p[0], +p[1] - 1, +p[2]).getDay();
cols[wd].push({ sid: sid, s: s, o: o });
});
var days = DAY_ORDER.filter(function (d) {
return cols[d].length || (d >= 1 && d <= 5);
});
if (
!days.some(function (d) {
return cols[d].length;
})
) {
return (
h +
'<div class="ys-noresult">Nessuna classe in calendario' +
(S.cat ? " per questa disciplina" : "") +
".</div>"
);
}
h += '<div class="ys-week" style="--ys-cols:' + days.length + ';">';
days.forEach(function (d) {
cols[d].sort(function (a, b) {
return a.o.time.localeCompare(b.o.time);
});
h +=
'<div class="ys-day"><div class="ys-day-name">' +
DAYS[d] +
"</div>";
if (!cols[d].length) h += '<div class="ys-day-empty">—</div>';
cols[d].forEach(function (it) {
var b = seatsBadge(it.o),
sel = S.cls === it.sid,
col = catColor(it.s);
var end = endTime(it.o.time, it.s.minutes);
h +=
'<button type="button" class="ys-slot' +
(sel ? " sel" : "") +
'" data-act="cls" data-id="' +
it.sid +
'" aria-pressed="' +
sel +
'" style="--ys-c:' +
esc(col) +
';">' +
'<span class="t">' +
esc(it.o.time) +
(end ? "–" + esc(end) : "") +
"</span>" +
'<span class="n">' +
esc(it.s.name) +
"</span>" +
(it.s.teacher
? '<span class="m">' + esc(it.s.teacher) + "</span>"
: "") +
'<span class="ys-seats ' +
b.cls +
'">' +
esc(b.text) +
"</span></button>";
});
h += "</div>";
});
h += "</div>";
if (S.cls && DATA.services[S.cls]) {
var s = DATA.services[S.cls],
list = productsWith(S.cls);
h +=
'<div class="ys-calpanel" id="ys-calpanel"><div class="ys-calpanel-head" style="--ys-c:' +
esc(catColor(s)) +
';"><div>' +
'<div class="ys-h3" style="margin:0;">' +
esc(s.name) +
" · " +
esc(s.occ[0].label.split(" · ")[0].split(" ")[0]) +
" " +
esc(s.occ[0].time) +
"</div>" +
'<div class="ys-muted">Prossime lezioni: ' +
s.occ
.slice(0, 3)
.map(function (o) {
return esc(o.label.replace(/^\S+\s/, ""));
})
.join(" · ") +
"</div></div>" +
'<button type="button" class="ys-back" data-act="cls" data-id="0" style="min-height:auto;">Chiudi <i class="bx bx-x"></i></button></div>' +
'<p class="ys-muted" style="margin:0 0 12px;">' +
(list.length === 1
? "Puoi frequentarla con questo prodotto:"
: "Puoi frequentarla con questi prodotti:") +
"</p>" +
'<div class="ys-grid">' +
list
.map(function (p) {
return productCard(p, S.cls);
})
.join("") +
"</div></div>";
}
return h;
}
function renderProduct(p, v) {
var imgs = [];
if (p.cover) imgs.push(p.cover);
(p.gallery || []).forEach(function (g) {
if (imgs.indexOf(g) === -1) imgs.push(g);
});
var cur = imgs[S.img] || imgs[0];
var h =
'<button type="button" class="ys-back" data-act="home"><i class="bx bx-chevron-left"></i> ' +
(S.from === "calendar"
? "Torna all'orario"
: "Tutti i prodotti") +
'</button><div class="ys-product"><div>';
h += cur
? '<div class="ys-gallery-main" style="background-image:url(\'' +
esc(cur) +
'\');" role="img" aria-label="' +
esc(p.title) +
'"></div>'
: '<div class="ys-gallery-main" style="background-color:' +
TONES[p.mode] +
';"><span class="mark">' +
esc(mark(p)) +
"</span></div>";
if (imgs.length > 1) {
h +=
'<div class="ys-thumbs">' +
imgs
.map(function (src, i) {
return (
'<button type="button" class="ys-thumb' +
(src === cur ? " on" : "") +
'" data-act="img" data-id="' +
i +
'" style="background-image:url(\'' +
esc(src) +
'\');" aria-label="Foto ' +
(i + 1) +
'"></button>'
);
})
.join("") +
"</div>";
}
if (p.pros.length) {
h +=
'<div style="margin-top:18px;"><h3 class="ys-h3">Vantaggi</h3>' +
p.pros
.map(function (t) {
return (
'<div class="ys-li"><span class="ys-check"><i class="bx bx-check"></i></span><span>' +
esc(t) +
"</span></div>"
);
})
.join("") +
"</div>";
}
if (p.cons.length) {
h +=
'<div class="ys-cons" style="margin-top:12px;"><h3 class="ys-h3" style="color:#7A5A2E;">Da considerare</h3>' +
p.cons
.map(function (t) {
return (
'<div class="ys-li"><span class="ys-dot"></span><span>' +
esc(t) +
"</span></div>"
);
})
.join("") +
"</div>";
}
h += '</div><div class="ys-info">';
if (p.badge)
h +=
'<span class="ys-badge" style="align-self:flex-start;">' +
esc(p.badge) +
"</span>";
h += '<h1 class="ys-title">' + esc(p.title) + "</h1>";
if (p.subtitle)
h += '<p class="ys-subtitle">' + esc(p.subtitle) + "</p>";
if (p.description)
h += '<div class="ys-desc">' + p.description + "</div>";
if (p.kind === "lessons" && p.services.length) {
h +=
'<div><h3 class="ys-h3">' +
(p.mode === "fixed"
? "Classi tra cui scegliere"
: "Vale per queste classi") +
'</h3><div class="ys-chips">' +
p.services
.map(function (sid) {
var s = svc(sid);
return s
? '<span class="ys-chip">' +
esc(
s.name + (s.day ? " · " + s.day : ""),
) +
"</span>"
: "";
})
.join("") +
"</div>" +
(p.weekly
? '<div class="ys-muted" style="margin-top:6px;">Puoi prenotare al massimo ' +
p.weekly +
" lezioni a settimana.</div>"
: "") +
(p.maxres !== null && p.mode !== "single"
? '<div class="ys-muted">Recuperi inclusi: ' +
p.maxres +
".</div>"
: "") +
"</div>";
}
if (p.suitable || p.requirements) {
h +=
'<div class="ys-info2">' +
(p.suitable
? "<div><b>Per chi è</b>" + esc(p.suitable) + "</div>"
: "") +
(p.requirements
? "<div><b>Cosa serve</b>" +
esc(p.requirements) +
"</div>"
: "") +
"</div>";
}
h +=
'<div><h3 class="ys-h3" style="font-size:17px;">' +
(p.variants.length > 1 ? "Scegli l'opzione" : "Prezzo") +
'</h3><div class="ys-options">';
p.variants.forEach(function (x) {
var sel = v && x.id === v.id;
h +=
'<button type="button" class="ys-opt' +
(sel ? " sel" : "") +
'" data-act="variant" data-id="' +
x.id +
'" aria-pressed="' +
sel +
'"><span class="ys-radio"></span>' +
'<span class="ys-opt-main"><span class="ys-opt-title">' +
esc(x.label) +
'</span><span class="ys-muted">' +
esc(validityText(p, x)) +
"</span>" +
(p.mode === "voucher" && x.targetLabel
? '<span class="ys-muted">Regala: ' +
esc(x.targetLabel) +
"</span>"
: "") +
(x.note
? '<span class="ys-opt-note">' + esc(x.note) + "</span>"
: "") +
"</span>" +
'<span class="ys-opt-price">' +
(x.compare ? "<s>" + euro(x.compare) + "</s>" : "") +
"<b>" +
euro(x.price) +
"</b></span></button>";
});
h += "</div></div></div></div>";
var blocked = p.blocked === "used";
h += buyBar(
v,
blocked
? '<button type="button" class="ys-btn" disabled>Già utilizzata</button>'
: '<button type="button" class="ys-btn" data-act="next">' +
(needsChoose(p) ? "Continua" : "Acquista") +
' <i class="bx bx-right-arrow-alt"></i></button>',
);
if (blocked)
h +=
'<p class="ys-muted" style="margin-top:8px;">Questo prodotto si può acquistare una sola volta e l\'hai già utilizzato.</p>';
return h;
}
function buyBar(v, button) {
return (
'<div class="ys-buy"><div style="display:flex;flex-direction:column;flex:1;min-width:0;"><span class="lbl">' +
esc(v ? v.label : "") +
'</span><span class="amt">' +
(v ? euro(v.price) : "") +
"</span></div>" +
button +
"</div>"
);
}
function renderChoose(p, v) {
var h =
'<button type="button" class="ys-back" data-act="toProduct"><i class="bx bx-chevron-left"></i> ' +
esc(p.title) +
'</button><div class="ys-choose">';
if (p.mode === "fixed") {
var slots = p.slots || 1;
h +=
'<div><h1 class="ys-title">' +
(slots === 2
? "Scegli le tue 2 classi"
: "Scegli la tua classe") +
'</h1><p class="ys-subtitle" style="font-size:15px;margin-top:6px;">' +
(slots === 1
? "Sarà il tuo posto fisso ogni settimana."
: "Saranno i tuoi due posti fissi ogni settimana.") +
"</p></div>";
h +=
'<span class="ys-pillcount">' +
S.picked.length +
" di " +
slots +
(slots === 1 ? " scelta" : " scelte") +
"</span>";
p.services.forEach(function (sid) {
var s = svc(sid);
if (!s) return;
var seat = fixedSeats(sid, p, v),
sel = S.picked.indexOf(sid) !== -1;
h +=
'<button type="button" class="ys-opt' +
(sel ? " sel" : "") +
'" data-act="pickClass" data-id="' +
sid +
'" aria-pressed="' +
sel +
'"' +
(!seat.ok && !sel ? " disabled" : "") +
">" +
'<span class="ys-box">' +
(sel ? '<i class="bx bx-check"></i>' : "") +
'</span><span class="ys-opt-main"><span class="ys-opt-title">' +
esc(s.day || s.name) +
"</span>" +
'<span class="ys-muted">' +
esc(s.name + (s.teacher ? " · " + s.teacher : "")) +
"</span>" +
(s.occ.length
? '<span class="ys-muted">Prossima lezione: ' +
esc(s.occ[0].label) +
"</span>"
: "") +
"</span>" +
'<span class="ys-seats ' +
seat.cls +
'">' +
esc(seat.text) +
"</span></button>";
});
h +=
'<div class="ys-note"><i class="bx bx-info-circle" style="color:#2F7F84;font-size:18px;"></i><span>Dopo l\'acquisto la scuola ti conferma la data della prima lezione e prenota per te tutte le altre, ogni settimana.</span></div>';
}
if (p.mode === "single") {
var list = singleOccurrences(p);
h +=
'<div><h1 class="ys-title">Scegli la lezione</h1><p class="ys-subtitle" style="font-size:15px;margin-top:6px;">Lezioni delle prossime settimane. Il posto si prenota quando completi l\'acquisto.</p></div>';
if (!list.length)
h +=
'<div class="ys-note">Al momento non ci sono lezioni prenotabili per questo prodotto.</div>';
list.forEach(function (it) {
var b = seatsBadge(it.o),
sel = S.occ === it.o.id;
h +=
'<button type="button" class="ys-opt' +
(sel ? " sel" : "") +
'" data-act="pickOcc" data-id="' +
it.o.id +
'" aria-pressed="' +
sel +
'"' +
(!b.ok ? " disabled" : "") +
'><span class="ys-radio"></span>' +
'<span class="ys-opt-main"><span class="ys-opt-title" style="font-size:15px;">' +
esc(it.o.label) +
'</span><span class="ys-muted">' +
esc(
it.s.name +
(it.s.teacher ? " · " + it.s.teacher : ""),
) +
"</span></span>" +
'<span class="ys-seats ' +
b.cls +
'">' +
esc(b.text) +
"</span></button>";
});
}
if (p.mode === "voucher") {
h +=
'<div><h1 class="ys-title">A chi lo regali?</h1><p class="ys-subtitle" style="font-size:15px;margin-top:6px;">Il buono arriva via email con un codice da usare nel negozio della scuola.</p></div>' +
'<div class="ys-panel">' +
'<div><label class="ys-label" for="ys-rname">Nome di chi lo riceve</label><input class="ys-input" id="ys-rname" data-field="rName" value="' +
esc(S.rName) +
'" placeholder="Es. Giulia" autocomplete="off"></div>' +
'<div><label class="ys-label" for="ys-remail">Email a cui inviarlo</label><input class="ys-input" id="ys-remail" type="email" data-field="rEmail" value="' +
esc(S.rEmail) +
'" placeholder="nome@email.it" autocomplete="off"><div class="ys-muted" style="margin-top:4px;">Metti la tua se preferisci consegnarlo di persona.</div></div>' +
'<div><label class="ys-label" for="ys-rmsg">Messaggio (facoltativo)</label><textarea class="ys-input" id="ys-rmsg" data-field="rMsg" rows="3" maxlength="500" placeholder="Buon compleanno!">' +
esc(S.rMsg) +
"</textarea></div></div>";
}
h += "</div>";
h += buyBar(
v,
'<button type="button" class="ys-btn" data-act="checkout" id="ys-go"' +
(chooseValid(p) ? "" : " disabled") +
'>Vai al pagamento <i class="bx bx-right-arrow-alt"></i></button>',
);
return h;
}
function view() {
var hash = decodeURIComponent(
(location.hash || "").replace(/^#\/?/, ""),
);
var parts = hash.split("/");
if (parts[0] === "orario") return { name: "calendar" };
var p = parts[0] ? bySlug(parts[0]) : null;
if (!p) return { name: "catalog" };
return {
name:
parts[1] === "scegli" &&
needsChoose(p) &&
p.blocked !== "used"
? "choose"
: "product",
p: p,
};
}
function render() {
var vw = view(),
html;
if (vw.name === "catalog") {
S.from = "";
html = renderCatalog();
} else if (vw.name === "calendar") {
S.from = "calendar";
html = renderCalendar();
} else {
if (S.pid !== vw.p.id) {
S.pid = vw.p.id;
S.vid = vw.p.variants[0].id;
S.picked = [];
S.occ = null;
S.img = 0;
// Arrivo dall'orario: preseleziona la classe scelta
if (S.prefSvc && vw.p.services.indexOf(S.prefSvc) !== -1) {
if (
vw.p.mode === "fixed" &&
fixedSeats(S.prefSvc, vw.p, variant(vw.p)).ok
)
S.picked = [S.prefSvc];
if (vw.p.mode === "single" && svc(S.prefSvc)) {
var free = svc(S.prefSvc).occ.filter(function (o) {
return seatsBadge(o).ok;
})[0];
if (free) S.occ = free.id;
}
}
S.prefSvc = 0;
}
var v = variant(vw.p);
html =
vw.name === "choose"
? renderChoose(vw.p, v)
: renderProduct(vw.p, v);
}
root.innerHTML = html;
var key = vw.name + (vw.p ? vw.p.id : "");
if (lastView !== key) {
lastView = key;
var top =
root.getBoundingClientRect().top + window.pageYOffset - 90;
window.scrollTo({ top: Math.max(0, top) });
}
}
function go(hash) {
if (location.hash === hash) render();
else location.hash = hash;
}
/* ---------------- azioni ---------------- */
var actions = {
filter: function (id) {
S.filter = id || "";
render();
},
open: function (id, el) {
var p = byId(+id);
if (!p) return;
S.prefSvc =
el && el.getAttribute("data-svc")
? +el.getAttribute("data-svc")
: 0;
S.pid = null; // nuova apertura: riparte dalle scelte predefinite
go("#/" + encodeURIComponent(p.slug));
},
home: function () {
go(S.from === "calendar" ? "#/orario" : "#/");
},
tab: function (id) {
go(id === "calendar" ? "#/orario" : "#/");
},
cls: function (id) {
S.cls = S.cls === +id ? 0 : +id;
render();
var panel = document.getElementById("ys-calpanel");
if (panel && panel.scrollIntoView)
panel.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
},
toProduct: function () {
var p = prod();
if (p) go("#/" + encodeURIComponent(p.slug));
},
img: function (i) {
S.img = +i;
render();
},
variant: function (id) {
S.vid = +id;
var p = prod(),
v = variant(p);
if (p && p.mode === "fixed")
S.picked = S.picked.filter(function (sid) {
return fixedSeats(sid, p, v).ok;
});
render();
},
next: function () {
var p = prod();
if (!p || p.blocked === "used") return;
if (needsChoose(p))
go("#/" + encodeURIComponent(p.slug) + "/scegli");
else actions.checkout();
},
pickClass: function (id) {
var p = prod(),
sid = +id,
slots = p.slots || 1,
arr = S.picked.slice(),
i = arr.indexOf(sid);
if (i !== -1) arr.splice(i, 1);
else if (slots === 1) arr = [sid];
else if (arr.length < slots) arr.push(sid);
else arr = [arr[1], sid];
S.picked = arr;
render();
},
pickOcc: function (id) {
S.occ = +id;
render();
},
checkout: function () {
var p = prod(),
v = variant(p);
if (!p || !v || !chooseValid(p) || p.blocked === "used") return;
var sel = {
product: p,
variant: v,
idvariant: v.id,
services: p.mode === "fixed" ? S.picked.slice() : [],
idschedule: p.mode === "single" ? S.occ : null,
recipient:
p.mode === "voucher"
? {
name: S.rName.trim(),
email: S.rEmail.trim(),
message: S.rMsg.trim(),
}
: null,
};
if (typeof opts.onCheckout === "function") opts.onCheckout(sel);
},
};
root.addEventListener("click", function (ev) {
var el = ev.target.closest("[data-act]");
if (
!el ||
el.disabled ||
!root.contains(el) ||
el.tagName === "SELECT"
)
return;
var fn = actions[el.getAttribute("data-act")];
if (fn) fn(el.getAttribute("data-id"), el);
});
root.addEventListener("change", function (ev) {
if (ev.target.getAttribute("data-act") === "cat") {
S.cat = +ev.target.value;
S.cls = 0;
render();
}
});
root.addEventListener("input", function (ev) {
var f = ev.target.getAttribute("data-field");
if (!f) return;
S[f] = ev.target.value;
var btn = document.getElementById("ys-go");
if (btn) btn.disabled = !chooseValid(prod());
});
window.addEventListener("hashchange", render);
render();
}
window.YBShop = { mount: mount };
// Webapp: monta il negozio e invia le scelte a shop_checkout.php
document.addEventListener("DOMContentLoaded", function () {
var root = document.getElementById("shop");
if (!root || !window.YB_SHOP) return;
mount(root, window.YB_SHOP, {
onCheckout: function (sel) {
var f = document.getElementById("checkout-form");
document.getElementById("f-variant").value = sel.idvariant;
document.getElementById("f-services").value =
sel.services.join(",");
document.getElementById("f-schedule").value =
sel.idschedule || "";
document.getElementById("f-rname").value = sel.recipient
? sel.recipient.name
: "";
document.getElementById("f-remail").value = sel.recipient
? sel.recipient.email
: "";
document.getElementById("f-rmsg").value = sel.recipient
? sel.recipient.message
: "";
f.submit();
},
});
});
})();