| Server IP : 193.86.120.172 / Your IP : 216.73.216.67 Web Server : Apache/2.4.63 (Unix) System : Linux JServices 3.10.108 #86003 SMP Wed Oct 22 13:20:46 CST 2025 x86_64 User : kubec ( 1026) PHP Version : 8.2.28 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /volume1/web/kadernictvi_jservices_cz/ |
Upload File : |
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rezervace – Kadeřnictví DEMO</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{max-width:100%;overflow-x:hidden}
:root{
--p50:#faf5ff;--p100:#f3e8ff;--p200:#e9d5ff;--p400:#c084fc;
--p500:#a855f7;--p600:#9333ea;--p700:#7c3aed;--p800:#6b21a8;
--g50:#f9fafb;--g100:#f3f4f6;--g200:#e5e7eb;--g300:#d1d5db;
--g400:#9ca3af;--g500:#6b7280;--g700:#374151;--g900:#111827;
--red:#991b1b;--redbg:#fee2e2;--green:#166534;--greenbg:#dcfce7;
}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--g50);color:var(--g900);min-height:100vh}
.page{max-width:600px;margin:0 auto;padding:1.5rem 1rem 3rem}
.header{text-align:center;margin-bottom:2rem}
.header-badge{display:inline-flex;align-items:center;gap:6px;background:var(--p100);color:var(--p800);font-size:13px;font-weight:500;padding:5px 14px;border-radius:999px;margin-bottom:12px}
.header h1{font-size:26px;font-weight:600;color:var(--g900);margin-bottom:6px}
.header p{font-size:15px;color:var(--g500);line-height:1.5}
.stepper{display:flex;align-items:center;justify-content:center;margin-bottom:2rem;gap:0}
.step-item{display:flex;flex-direction:column;align-items:center;gap:5px}
.step-circle{width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;border:2px solid var(--p200);background:#fff;color:var(--p600);transition:all .2s}
.step-circle.active{background:var(--p600);color:#fff;border-color:var(--p600)}
.step-circle.done{background:var(--p100);color:var(--p700);border-color:var(--p600)}
.step-label{font-size:11px;color:var(--g400);white-space:nowrap}
.step-label.active{color:var(--p700);font-weight:500}
.step-line{width:52px;height:2px;background:var(--p200);margin-bottom:18px;transition:background .2s}
.step-line.done{background:var(--p600)}
.card{background:#fff;border:1px solid var(--g200);border-radius:16px;padding:1.25rem;margin-bottom:1rem}
.card-title{font-size:17px;font-weight:600;color:var(--g900);margin-bottom:4px}
.card-sub{font-size:13px;color:var(--g400);margin-bottom:1.25rem}
.services-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
@media(max-width:400px){.services-grid{grid-template-columns:1fr}}
.service-tile{border:2px solid var(--p200);border-radius:12px;padding:14px 12px;text-align:center;cursor:pointer;transition:all .15s;background:#fff}
.service-tile:hover{border-color:var(--p500);background:var(--p50)}
.service-tile.selected{border-color:var(--p600);background:var(--p100)}
.doplnek-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:0.5px solid var(--g100)}
.doplnek-row:last-child{border-bottom:none}
.doplnek-check{width:18px;height:18px;accent-color:var(--p600);cursor:pointer;flex-shrink:0}
.doplnek-name{font-size:13px;font-weight:600;color:var(--g900)}
.doplnek-meta{font-size:11px;color:var(--g400);margin-top:2px}
.doplnek-price{font-size:13px;font-weight:700;color:var(--p700);white-space:nowrap}
.service-tile-icon{font-size:24px;margin-bottom:6px;color:var(--p600)}
.service-tile-name{font-size:13px;font-weight:600;color:var(--g900);margin-bottom:2px}
.service-tile-detail{font-size:11px;color:var(--g400)}
.service-tile-price{font-size:13px;font-weight:600;color:var(--p700);margin-top:5px}
.cal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
.cal-month{font-size:15px;font-weight:600;color:var(--g900)}
.cal-nav{width:32px;height:32px;border:1px solid var(--p200);border-radius:8px;background:none;cursor:pointer;color:var(--p600);font-size:16px;display:flex;align-items:center;justify-content:center}
.cal-nav:hover{background:var(--p50)}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}
.cal-day-name{text-align:center;font-size:11px;color:var(--g400);padding:4px 0;font-weight:500}
.cal-day{text-align:center;padding:7px 2px;border-radius:8px;font-size:13px;cursor:pointer;border:2px solid transparent;transition:all .15s}
.cal-day:hover:not(.disabled):not(.empty){background:var(--p100);border-color:var(--p200)}
.cal-day.today{border-color:var(--p300,#d8b4fe);color:var(--p700);font-weight:600}
.cal-day.selected{background:var(--p600);color:#fff;border-color:var(--p600)}
.cal-day.disabled{color:var(--g300);cursor:default}
.cal-day.empty{cursor:default}
.cal-day.blokovano{color:var(--g300);cursor:default;text-decoration:line-through}
.times-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:1rem}
@media(max-width:400px){.times-grid{grid-template-columns:repeat(3,1fr)}}
.time-slot{border:2px solid var(--p200);border-radius:8px;padding:9px 4px;text-align:center;font-size:13px;cursor:pointer;transition:all .15s;color:var(--g900)}
.time-slot:hover:not(.taken){background:var(--p100);border-color:var(--p500)}
.time-slot.selected{background:var(--p600);color:#fff;border-color:var(--p600)}
.time-slot.taken{color:var(--g300);cursor:default;text-decoration:line-through;background:var(--g50)}
.form-row{margin-bottom:14px}
.form-row-2{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:14px}
@media(max-width:400px){.form-row-2{grid-template-columns:1fr}}
label{font-size:13px;color:var(--g500);margin-bottom:5px;display:block}
input,textarea{width:100%;border:2px solid var(--p200);border-radius:8px;padding:10px 12px;font-size:14px;color:var(--g900);outline:none;transition:border-color .15s;font-family:inherit;background:#fff}
input:focus,textarea:focus{border-color:var(--p500)}
textarea{height:72px;resize:none;line-height:1.5}
.checkbox-row{display:flex;align-items:flex-start;gap:10px;margin-top:4px}
.checkbox-row input[type=checkbox]{width:16px;height:16px;margin-top:2px;accent-color:var(--p600);flex-shrink:0}
.checkbox-label{font-size:12px;color:var(--g500);line-height:1.5}
.summary-box{background:var(--p50);border:1px solid var(--p200);border-radius:12px;padding:14px;margin-bottom:1rem}
.summary-row{display:flex;justify-content:space-between;font-size:13px;padding:4px 0}
.summary-label{color:var(--g500)}
.summary-value{font-weight:600;color:var(--g900)}
.summary-divider{border:none;border-top:1px solid var(--p200);margin:8px 0}
.summary-total .summary-value{color:var(--p700);font-size:15px}
.btn-primary{width:100%;background:var(--p600);color:#fff;border:none;border-radius:999px;padding:14px;font-size:15px;font-weight:600;cursor:pointer;transition:background .15s}
.btn-primary:hover:not(:disabled){background:var(--p700)}
.btn-primary:disabled{background:var(--p300,#d8b4fe);cursor:default}
.btn-back{background:none;border:2px solid var(--p200);border-radius:999px;padding:10px 20px;font-size:14px;color:var(--p600);cursor:pointer;margin-bottom:12px;display:inline-flex;align-items:center;gap:6px}
.btn-back:hover{background:var(--p50)}
.step-view{display:none}
.step-view.active{display:block}
.success-view{display:none;text-align:center;padding:2rem 0}
.success-view.active{display:block}
.success-icon{width:72px;height:72px;background:var(--p100);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1.25rem;font-size:32px}
.success-title{font-size:22px;font-weight:700;color:var(--g900);margin-bottom:10px}
.success-sub{font-size:14px;color:var(--g500);line-height:1.7;margin-bottom:1.5rem}
.success-links{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.link-btn{border:2px solid var(--p200);border-radius:999px;padding:10px 20px;font-size:13px;color:var(--p700);background:none;cursor:pointer;display:inline-flex;align-items:center;gap:6px}
.link-btn:hover{background:var(--p50)}
.loading{text-align:center;padding:2rem;color:var(--g400);font-size:14px}
.error-inline{font-size:12px;color:var(--red);margin-top:4px;display:none}
.alert{border-radius:10px;padding:12px 14px;font-size:13px;margin-bottom:1rem;display:none}
.alert.error{background:var(--redbg);color:var(--red);border:1px solid #fca5a5}
.alert.success{background:var(--greenbg);color:var(--green);border:1px solid #86efac}
.alert.show{display:block}
.note{font-size:12px;color:var(--g400);margin-top:8px;line-height:1.5}
</style>
</head>
<body>
<div class="page">
<div class="header">
<h1 id="salon-nazev">Kadeřnictví DEMO</h1>
<p id="salon-uvod">Jednoduchá rezervace ve 3 krocích</p>
</div>
<div class="stepper" id="stepper">
<div class="step-item">
<div class="step-circle active" id="sc1">✂</div>
<div class="step-label active" id="sl1">Služba</div>
</div>
<div class="step-line" id="line1"></div>
<div class="step-item">
<div class="step-circle" id="sc2">📅</div>
<div class="step-label" id="sl2">Termín</div>
</div>
<div class="step-line" id="line2"></div>
<div class="step-item">
<div class="step-circle" id="sc3">👤</div>
<div class="step-label" id="sl3">Kontakt</div>
</div>
</div>
<div id="alert-global" class="alert error"></div>
<!-- KROK 1 -->
<div class="step-view active" id="step1">
<div class="card">
<div class="card-title">Co pro vás uděláme?</div>
<div class="card-sub">Vyberte jednu službu</div>
<div class="services-grid" id="servicesGrid">
<div class="loading">Načítám služby…</div>
</div>
</div>
<!-- Doplňkové služby - zobrazí se po výběru hlavní služby -->
<div id="doplnky-sekce" style="display:none">
<div class="card" style="margin-top:12px">
<div class="card-title">Doplňkové služby</div>
<div class="card-sub">Volitelné — přidejte co chcete</div>
<div id="doplnky-grid"></div>
</div>
</div>
<button class="btn-primary" id="btn1" disabled onclick="goStep(2)">Vybrat termín →</button>
</div>
<!-- KROK 2 -->
<div class="step-view" id="step2">
<button class="btn-back" onclick="goStep(1)">← Zpět</button>
<div class="card">
<div class="card-title">Vyberte datum</div>
<div class="card-sub" id="step2sub">Vyberte prosím nejprve službu</div>
<div class="cal-header">
<button class="cal-nav" onclick="changeMonth(-1)">‹</button>
<div class="cal-month" id="calMonth"></div>
<button class="cal-nav" onclick="changeMonth(1)">›</button>
</div>
<div class="cal-grid" id="calGrid"></div>
</div>
<div class="card" id="timesCard" style="display:none">
<div class="card-title">Vyberte čas</div>
<div class="card-sub" id="timesLabel"></div>
<div id="timesLoading" class="loading" style="display:none">Načítám dostupné časy…</div>
<div class="times-grid" id="timesGrid"></div>
<p class="note">V tento den není možné rezervaci provést. Vyberte jiný den.</p>
</div>
<button class="btn-primary" id="btn2" disabled onclick="goStep(3)">Zadat kontakt →</button>
</div>
<!-- KROK 3 -->
<div class="step-view" id="step3">
<button class="btn-back" onclick="goStep(2)">← Zpět</button>
<div class="card">
<div class="card-title">Vaše kontaktní údaje</div>
<div class="card-sub">Potvrzení zašleme e-mailem</div>
<div class="form-row-2">
<div>
<label>Jméno *</label>
<input type="text" id="f-jmeno" placeholder="Jana" oninput="validateForm()">
<div class="error-inline" id="err-jmeno">Vyplňte jméno</div>
</div>
<div>
<label>Příjmení *</label>
<input type="text" id="f-prijmeni" placeholder="Nováková" oninput="validateForm()">
<div class="error-inline" id="err-prijmeni">Vyplňte příjmení</div>
</div>
</div>
<div class="form-row">
<label>E-mail *</label>
<input type="email" id="f-email" placeholder="jana@email.cz" oninput="validateForm()">
<div class="error-inline" id="err-email">Zadejte platný e-mail</div>
</div>
<div class="form-row">
<label>Telefon *</label>
<input type="tel" id="f-telefon" placeholder="+420 123 456 789" oninput="validateForm()" required>
</div>
<div class="form-row">
<label>Poznámka</label>
<textarea id="f-poznamka" placeholder="Např. alergie na určité přípravky…"></textarea>
</div>
<div class="checkbox-row">
<input type="checkbox" id="f-souhlas" onchange="validateForm()">
<label class="checkbox-label" for="f-souhlas">
Souhlasím se zpracováním osobních údajů. Rezervaci lze zrušit nebo změnit prostřednictvím odkazu v potvrzovacím e-mailu.
</label>
</div>
</div>
<div class="summary-box">
<div class="summary-row"><span class="summary-label">✂ Služba</span><span class="summary-value" id="sum-sluzba">—</span></div>
<div class="summary-row" id="sum-doplnky-row" style="display:none"><span class="summary-label">➕ Doplňky</span><span class="summary-value" id="sum-doplnky">—</span></div>
<div class="summary-row"><span class="summary-label">📅 Datum</span><span class="summary-value" id="sum-datum">—</span></div>
<div class="summary-row"><span class="summary-label">🕐 Čas</span><span class="summary-value" id="sum-cas">—</span></div>
<hr class="summary-divider">
<div class="summary-row summary-total"><span class="summary-label">Celkem</span><span class="summary-value" id="sum-cena">—</span></div>
</div>
<div id="alert-form" class="alert error"></div>
<button class="btn-primary" id="btn3" disabled onclick="submitReservation()">Odeslat rezervaci</button>
</div>
<!-- ÚSPĚCH -->
<div class="success-view" id="successView">
<div class="success-icon">✅</div>
<div class="success-title">Rezervace odeslána!</div>
<div class="success-sub">
Děkujeme, <strong id="suc-jmeno"></strong>!<br>
Potvrzení jsme odeslali na <strong id="suc-email"></strong>.<br>
V e-mailu najdete odkaz pro změnu nebo zrušení termínu.
</div>
<div class="success-links">
<button class="link-btn" onclick="resetApp()">✚ Nová rezervace</button>
<a class="link-btn" id="ics-link" href="#" target="_blank">📅 Přidat do kalendáře</a>
</div>
</div>
<div id="footer-hodnoceni" style="display:none;text-align:center;margin-top:2rem;background:#faf5ff;border:0.5px solid #e9d5ff;border-radius:16px;padding:16px">
<div id="footer-hodnoceni-stars" style="font-size:20px;margin-bottom:6px"></div>
<div id="footer-hodnoceni-prumer" style="font-weight:700;font-size:15px;color:#111827"></div>
<div id="footer-hodnoceni-pocet" style="font-size:12px;color:#9ca3af;margin-top:2px"></div>
</div>
<div style="text-align:center;margin-top:2rem;padding-top:1.5rem;border-top:1px solid #e9d5ff">
<div style="font-size:13px;color:#9ca3af;line-height:2">
<div id="footer-adresa" style="display:none">
<span id="footer-adresa-text"></span>
</div>
<div id="footer-telefon" style="display:none">
<span style="font-size:15px">📞</span>
<a id="footer-telefon-link" href="#" style="color:#7c3aed;text-decoration:none;font-weight:500"></a>
</div>
</div>
<div style="margin-top:10px;font-size:11px;color:#d1d5db">
Rezervační systém vytvořil <a href="https://jservices.cz" target="_blank" rel="noopener" style="color:#a855f7;text-decoration:none;font-weight:500">J Services</a>
</div>
</div>
</div>
<script>
var API = '/api';
var state = { sluzba: null, sluzbaId: null, sluzbaMin: null, cena: null, datum: null, cas: null, doplnky: [] };
var calDate = new Date(); calDate.setDate(1);
var MESICE = ['Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec'];
var DNY_NAMES = ['Ne','Po','Út','St','Čt','Pá','So'];
var DNY_HLAVICKA = ['Po','Út','St','Čt','Pá','So','Ne'];
var pracovniDny = {}; // den => {od_min, do_min}
// Načti nastavení a služby při startu
async function init() {
try {
var r = await fetch(API + '/nastaveni/public');
var d = await r.json();
if (d.ok) {
if (d.data.salon_nazev) document.getElementById('salon-nazev').textContent = d.data.salon_nazev;
if (d.data.uvodni_text) document.getElementById('salon-uvod').textContent = d.data.uvodni_text;
if (d.data.salon_adresa) {
var adresa = d.data.salon_adresa;
var mapsUrl = 'https://www.google.com/maps/search/?api=1&query=' + encodeURIComponent(adresa);
var adresaEl = document.getElementById('footer-adresa-text');
adresaEl.innerHTML = '<a href="' + mapsUrl + '" target="_blank" rel="noopener" style="color:#7c3aed;text-decoration:none;font-weight:500">📍 ' + adresa + '</a>';
document.getElementById('footer-adresa').style.display = 'block';
}
if (d.data.salon_telefon) {
var tel = d.data.salon_telefon;
var telLink = document.getElementById('footer-telefon-link');
telLink.textContent = tel;
telLink.href = 'tel:' + tel.replace(/\s/g, '');
document.getElementById('footer-telefon').style.display = 'block';
}
if (d.data.hodnoceni_verejne_zapnuta === '1' && d.data.hodnoceni_prumer) {
var prumer = parseFloat(d.data.hodnoceni_prumer);
var pocet = parseInt(d.data.hodnoceni_pocet || 0);
var starsHtml = '';
for (var i = 1; i <= 5; i++) {
var color = i <= Math.round(prumer) ? '#f59e0b' : '#e5e7eb';
starsHtml += '<span style="color:' + color + '">★</span>';
}
document.getElementById('footer-hodnoceni-stars').innerHTML = starsHtml;
document.getElementById('footer-hodnoceni-prumer').textContent = prumer.toFixed(1).replace('.', ',') + ' z 5';
document.getElementById('footer-hodnoceni-pocet').textContent = pocet + (pocet === 1 ? ' hodnocení' : (pocet < 5 ? ' hodnocení' : ' hodnocení')) + ' od našich klientů';
document.getElementById('footer-hodnoceni').style.display = 'block';
}
}
} catch(e) {}
// Načti pracovní dobu per den
try {
var rPD = await fetch(API + '/pracovni-doba-public');
var dPD = await rPD.json();
if (dPD.ok) pracovniDny = dPD.data;
} catch(e) {}
loadSluzby();
}
var IKONY = {'Střih & styling':'✂️','Barvení':'🎨','Melír':'✨','Péče o vlasy':'💆','Dětský střih':'😊','Foukaná':'💨'};
async function loadSluzby() {
var grid = document.getElementById('servicesGrid');
try {
var r = await fetch(API + '/sluzby');
var d = await r.json();
if (!d.ok || !d.data.length) { grid.innerHTML = '<p style="color:#9ca3af;font-size:14px">Žádné služby nejsou dostupné.</p>'; return; }
grid.innerHTML = '';
d.data.forEach(function(s) {
var el = document.createElement('div');
el.className = 'service-tile';
var ikona = IKONY[s.nazev] || '💇';
el.innerHTML = '<div class="service-tile-icon">' + ikona + '</div>'
+ '<div class="service-tile-name">' + esc(s.nazev) + '</div>'
+ '<div class="service-tile-detail">' + s.trvani_min + ' min</div>'
+ '<div class="service-tile-price">' + formatCena(s.cena) + ' Kč</div>';
el.onclick = function() { selectSluzba(el, s); };
grid.appendChild(el);
});
} catch(e) {
grid.innerHTML = '<p style="color:#991b1b;font-size:14px">Nepodařilo se načíst služby.</p>';
}
}
function selectSluzba(el, s) {
document.querySelectorAll('.service-tile').forEach(function(t){ t.classList.remove('selected'); });
el.classList.add('selected');
state.sluzba = s.nazev; state.sluzbaId = s.id; state.sluzbaMin = s.trvani_min;
state.cena = s.cena; state.datum = null; state.cas = null;
state.doplnky = [];
document.getElementById('btn1').disabled = false;
document.getElementById('step2sub').textContent = s.nazev + ' · ' + s.trvani_min + ' min';
nactiDoplnky(s.id);
}
async function nactiDoplnky(sluzbaId) {
var sekce = document.getElementById('doplnky-sekce');
var grid = document.getElementById('doplnky-grid');
try {
var r = await fetch(API + '/doplnky?sluzba_id=' + sluzbaId);
var d = await r.json();
if (!d.ok || !d.data || d.data.length === 0) {
sekce.style.display = 'none';
return;
}
sekce.style.display = 'block';
grid.innerHTML = d.data.map(function(dd) {
return '<div class="doplnek-row">'
+ '<div style="display:flex;align-items:center;gap:10px">'
+ '<input type="checkbox" class="doplnek-check" id="doplnek-' + dd.id + '" data-id="' + dd.id + '" data-nazev="' + dd.nazev + '" data-cena="' + dd.cena + '" data-min="' + dd.trvani_min + '" onchange="onDoplnekChange()">'
+ '<div><div class="doplnek-name">' + dd.nazev + '</div>'
+ (dd.trvani_min > 0 ? '<div class="doplnek-meta">+' + dd.trvani_min + ' min</div>' : '')
+ '</div></div>'
+ (dd.cena > 0 ? '<div class="doplnek-price">+ ' + Math.round(dd.cena).toLocaleString('cs-CZ') + ' Kč</div>' : '<div class="doplnek-price">zdarma</div>')
+ '</div>';
}).join('');
} catch(e) {
sekce.style.display = 'none';
}
}
function onDoplnekChange() {
var checkboxes = document.querySelectorAll('.doplnek-check:checked');
state.doplnky = Array.from(checkboxes).map(function(ch) {
return { id: parseInt(ch.dataset.id), nazev: ch.dataset.nazev, cena: parseFloat(ch.dataset.cena), trvani_min: parseInt(ch.dataset.min) };
});
updateSummary();
// Pokud je již vybráno datum, znovu načti sloty s novým extra_min
if (state.datum) {
state.cas = null;
selectDatum(state.datum.getDate(), state.datum);
}
}
function goStep(n) {
document.querySelectorAll('.step-view').forEach(function(v){ v.classList.remove('active'); });
document.getElementById('step' + n).classList.add('active');
for (var i = 1; i <= 3; i++) {
var sc = document.getElementById('sc' + i), sl = document.getElementById('sl' + i);
sc.classList.remove('active','done'); sl.classList.remove('active');
if (i < n) sc.classList.add('done');
else if (i === n) { sc.classList.add('active'); sl.classList.add('active'); }
if (i < 3) document.getElementById('line' + i).classList.toggle('done', i < n);
}
if (n === 2) renderCalendar();
if (n === 3) updateSummary();
window.scrollTo(0, 0);
}
function renderCalendar() {
var now = new Date();
var y = calDate.getFullYear(), m = calDate.getMonth();
document.getElementById('calMonth').textContent = MESICE[m] + ' ' + y;
var grid = document.getElementById('calGrid');
grid.innerHTML = '';
DNY_HLAVICKA.forEach(function(d){ var el=document.createElement('div'); el.className='cal-day-name'; el.textContent=d; grid.appendChild(el); });
var prvniDen = new Date(y, m, 1).getDay();
var offset = prvniDen === 0 ? 6 : prvniDen - 1;
for (var i = 0; i < offset; i++) { var e=document.createElement('div'); e.className='cal-day empty'; grid.appendChild(e); }
var dniVMesici = new Date(y, m+1, 0).getDate();
for (var d = 1; d <= dniVMesici; d++) {
var el = document.createElement('div'); el.className = 'cal-day';
el.textContent = d;
var thisDate = new Date(y, m, d);
var isPast = thisDate < new Date(now.getFullYear(), now.getMonth(), now.getDate());
var dow = thisDate.getDay() === 0 ? 7 : thisDate.getDay();
var isNepracovni = !pracovniDny[dow];
if (isPast || isNepracovni) { el.classList.add('disabled'); }
else {
if (d === now.getDate() && m === now.getMonth() && y === now.getFullYear()) el.classList.add('today');
if (state.datum && state.datum.getDate()===d && state.datum.getMonth()===m && state.datum.getFullYear()===y) el.classList.add('selected');
(function(day, dateObj){ el.onclick = function(){ selectDatum(day, dateObj); }; })(d, thisDate);
}
grid.appendChild(el);
}
}
async function selectDatum(d, dateObj) {
state.datum = dateObj; state.cas = null;
document.getElementById('btn2').disabled = true;
renderCalendar();
var tc = document.getElementById('timesCard');
tc.style.display = 'block';
var czDny = ['Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota'];
document.getElementById('timesLabel').textContent = czDny[dateObj.getDay()] + ' ' + d + '. ' + MESICE[dateObj.getMonth()];
document.getElementById('timesLoading').style.display = 'block';
document.getElementById('timesGrid').innerHTML = '';
var datum = formatDate(dateObj);
try {
var extraMin = state.doplnky.reduce(function(s,d){ return s + d.trvani_min; }, 0);
var totalMin = state.sluzbaMin + extraMin;
var r = await fetch(API + '/dostupnost?sluzba_id=' + state.sluzbaId + '&datum=' + datum + (extraMin > 0 ? '&extra_min=' + extraMin : ''));
var data = await r.json();
document.getElementById('timesLoading').style.display = 'none';
if (data.blokovano) {
document.getElementById('timesGrid').innerHTML = '<p style="color:#9ca3af;font-size:13px;grid-column:1/-1">Tento den je zablokován.</p>';
return;
}
if (!data.volne_casy || !data.volne_casy.length) {
document.getElementById('timesGrid').innerHTML = '<p style="color:#9ca3af;font-size:13px;grid-column:1/-1">Žádné volné termíny.</p>';
return;
}
var tg = document.getElementById('timesGrid');
data.volne_casy.forEach(function(t) {
var el = document.createElement('div'); el.className = 'time-slot'; el.textContent = t;
el.onclick = function() { selectCas(t, el); };
tg.appendChild(el);
});
} catch(e) {
document.getElementById('timesLoading').style.display = 'none';
document.getElementById('timesGrid').innerHTML = '<p style="color:#991b1b;font-size:13px;grid-column:1/-1">Chyba při načítání termínů.</p>';
}
}
function selectCas(t, el) {
document.querySelectorAll('.time-slot').forEach(function(s){ s.classList.remove('selected'); });
el.classList.add('selected');
state.cas = t;
document.getElementById('btn2').disabled = false;
}
function changeMonth(dir) {
var now = new Date();
calDate.setMonth(calDate.getMonth() + dir);
if (calDate < new Date(now.getFullYear(), now.getMonth(), 1)) {
calDate = new Date(now.getFullYear(), now.getMonth(), 1);
}
renderCalendar();
}
function updateSummary() {
if (!state.datum) return;
var czDny = ['Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota'];
// Doplňky v summary
var extraMin = state.doplnky.reduce(function(s,d){ return s + d.trvani_min; }, 0);
var extraCena = state.doplnky.reduce(function(s,d){ return s + d.cena; }, 0);
var totalMin = state.sluzbaMin + extraMin;
document.getElementById('sum-sluzba').textContent = state.sluzba + ' (' + totalMin + ' min)';
if (state.doplnky.length > 0) {
document.getElementById('sum-doplnky-row').style.display = '';
document.getElementById('sum-doplnky').textContent = state.doplnky.map(function(d){ return d.nazev; }).join(', ');
} else {
document.getElementById('sum-doplnky-row').style.display = 'none';
}
document.getElementById('sum-datum').textContent = czDny[state.datum.getDay()] + ' ' + state.datum.getDate() + '. ' + MESICE[state.datum.getMonth()] + ' ' + state.datum.getFullYear();
document.getElementById('sum-cas').textContent = state.cas;
var zakladniCena = (state.cena && parseFloat(state.cena) > 0) ? parseFloat(state.cena) : 0;
var celkemCena = zakladniCena + extraCena;
document.getElementById('sum-cena').textContent = celkemCena > 0 ? formatCena(celkemCena) + ' Kč' : (zakladniCena === 0 && extraCena === 0 ? '' : formatCena(celkemCena) + ' Kč');
}
function validateForm() {
var jmeno = document.getElementById('f-jmeno').value.trim();
var prijmeni = document.getElementById('f-prijmeni').value.trim();
var email = document.getElementById('f-email').value.trim();
var souhlas = document.getElementById('f-souhlas').checked;
var telefon = document.getElementById('f-telefon').value.trim();
var emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
document.getElementById('btn3').disabled = !(jmeno && prijmeni && emailOk && telefon && souhlas);
}
async function submitReservation() {
var btn = document.getElementById('btn3');
btn.disabled = true; btn.textContent = 'Odesílám…';
var alertEl = document.getElementById('alert-form');
alertEl.classList.remove('show');
var payload = {
sluzba_id: state.sluzbaId,
datum: formatDate(state.datum),
cas: state.cas,
doplnky: state.doplnky.map(function(d){ return d.id; }),
jmeno: document.getElementById('f-jmeno').value.trim(),
prijmeni: document.getElementById('f-prijmeni').value.trim(),
email: document.getElementById('f-email').value.trim(),
telefon: document.getElementById('f-telefon').value.trim(), // povinný
poznamka: document.getElementById('f-poznamka').value.trim()
};
try {
var r = await fetch(API + '/rezervace', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(payload) });
var d = await r.json();
if (d.ok) {
document.querySelectorAll('.step-view').forEach(function(v){ v.classList.remove('active'); });
document.getElementById('stepper').style.display = 'none';
document.getElementById('suc-jmeno').textContent = payload.jmeno + ' ' + payload.prijmeni;
document.getElementById('suc-email').textContent = payload.email;
document.getElementById('ics-link').href = '/api/ics?token=' + encodeURIComponent(d.token || '');
document.getElementById('successView').classList.add('active');
} else {
alertEl.textContent = d.error || 'Chyba při odesílání rezervace.';
alertEl.classList.add('show');
btn.disabled = false; btn.textContent = 'Odeslat rezervaci';
}
} catch(e) {
alertEl.textContent = 'Nepodařilo se spojit se serverem. Zkuste to prosím znovu.';
alertEl.classList.add('show');
btn.disabled = false; btn.textContent = 'Odeslat rezervaci';
}
}
function resetApp() {
state = { sluzba:null, sluzbaId:null, sluzbaMin:null, cena:null, datum:null, cas:null };
document.getElementById('stepper').style.display = 'flex';
document.getElementById('successView').classList.remove('active');
document.querySelectorAll('.service-tile').forEach(function(t){ t.classList.remove('selected'); });
['f-jmeno','f-prijmeni','f-email','f-telefon','f-poznamka'].forEach(function(id){ document.getElementById(id).value=''; });
document.getElementById('f-souhlas').checked = false;
document.getElementById('timesCard').style.display = 'none';
document.getElementById('btn1').disabled = true;
document.getElementById('btn2').disabled = true;
document.getElementById('btn3').disabled = true;
calDate = new Date(); calDate.setDate(1);
goStep(1);
}
function formatDate(d) {
return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
}
function formatCena(c) { return Number(c).toLocaleString('cs-CZ'); }
function esc(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
init();
</script>
</body>
</html>