| Server IP : 193.86.120.172 / Your IP : 216.73.216.215 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/kadernictvidenisa_cz/ |
Upload File : |
<?php
require_once __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/mailer.php';
require_once __DIR__ . '/includes/calendar.php';
$token = sanitize($_GET['token'] ?? '');
$error = '';
$rezervace = null;
$sluzby = [];
$uspech = false;
if (strlen($token) !== 64) { $error = 'Neplatný odkaz.'; }
if (!$error) {
$stmt = db()->prepare("SELECT r.*, s.nazev AS sluzba_nazev, s.cena FROM rezervace r JOIN sluzby s ON s.id = r.sluzba_id WHERE r.token_zmena = ?");
$stmt->execute([$token]);
$rezervace = $stmt->fetch();
if (!$rezervace) { $error = 'Rezervace nenalezena.'; }
elseif ($rezervace['stav'] === 'zrusena') { $error = 'Tato rezervace byla již zrušena.'; }
}
if (!$error) {
$stmt2 = db()->query("SELECT id, nazev, trvani_min, cena FROM sluzby WHERE aktivni = 1 ORDER BY poradi");
$sluzby = $stmt2->fetchAll();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !$error) {
$nova_sluzba_id = (int)($_POST['sluzba_id'] ?? 0);
$novy_datum = sanitize($_POST['datum'] ?? '');
$novy_cas = sanitize($_POST['cas'] ?? '');
if (!$nova_sluzba_id || !valid_date($novy_datum) || !preg_match('/^\d{2}:\d{2}$/', $novy_cas)) {
$error = 'Vyplňte všechna pole.';
} elseif ($novy_datum < date('Y-m-d')) {
$error = 'Nelze rezervovat v minulosti.';
} else {
$stmtS = db()->prepare("SELECT * FROM sluzby WHERE id = ? AND aktivni = 1");
$stmtS->execute([$nova_sluzba_id]);
$nova_sluzba = $stmtS->fetch();
if (!$nova_sluzba) { $error = 'Neplatná služba.'; }
else {
$volne = volne_casy($novy_datum, $nova_sluzba['trvani_min']);
if (!in_array($novy_cas, $volne)) { $error = 'Vybraný termín již není dostupný. Vyberte prosím jiný.'; }
else {
$cas_od = $novy_cas . ':00';
$cas_do = date('H:i:s', strtotime($cas_od) + $nova_sluzba['trvani_min'] * 60);
db()->prepare("UPDATE rezervace SET sluzba_id=?, datum=?, cas_od=?, cas_do=?, stav='ceka' WHERE token_zmena=?")
->execute([$nova_sluzba_id, $novy_datum, $cas_od, $cas_do, $token]);
// Notifikace admina i zákazníka
$rez_mail = [
'jmeno' => $rezervace['jmeno'],
'prijmeni' => $rezervace['prijmeni'],
'email' => $rezervace['email'],
'telefon' => $rezervace['telefon'] ?? '',
'poznamka' => $rezervace['poznamka'] ?? '',
'datum' => $novy_datum,
'cas_od' => $novy_cas,
'cena' => number_format($nova_sluzba['cena'], 0, ',', ' '),
'token_zmena' => $token,
];
// Aktualizuj nebo vytvoř událost v Microsoft Calendar
$rez_cal = [
'jmeno' => $rezervace['jmeno'], 'prijmeni' => $rezervace['prijmeni'],
'email' => $rezervace['email'], 'telefon' => $rezervace['telefon'] ?? '',
'poznamka' => $rezervace['poznamka'] ?? '',
'datum' => $novy_datum,
'cas_od' => $novy_cas,
'cas_do' => substr($cas_do, 0, 5),
'cena' => number_format($nova_sluzba['cena'], 0, ',', ' '),
];
try {
if (!empty($rezervace['ms_event_id'])) {
graph_aktualizuj_udalost($rezervace['ms_event_id'], $rez_cal, $nova_sluzba['nazev']);
} else {
$new_event_id = graph_vytvor_udalost($rez_cal, $nova_sluzba['nazev']);
if ($new_event_id) {
db()->prepare("UPDATE rezervace SET ms_event_id = ? WHERE token_zmena = ?")
->execute([$new_event_id, $token]);
}
}
} catch (Exception $e) { error_log('[Zmena] Calendar error: ' . $e->getMessage()); }
try { email_notifikace_admin($rez_mail, $nova_sluzba['nazev'], 'zmenena'); } catch (Exception $e) { error_log('[Zmena] Admin e-mail selhal: ' . $e->getMessage()); }
$uspech = true;
}
}
}
}
// Načti pracovní dobu per den z DB
$pracovni_dny_raw = [];
try {
$stmt_pd = db()->query("SELECT den, od_min, do_min FROM pracovni_doba WHERE aktivni = 1 ORDER BY den");
foreach ($stmt_pd->fetchAll() as $pd) {
$pracovni_dny_raw[(int)$pd['den']] = true;
}
} catch (Exception $e) {
// Fallback
foreach (explode(',', nastaveni('pracovni_dny', '1,2,3,4,5,6')) as $d) {
$pracovni_dny_raw[(int)trim($d)] = true;
}
}
$pracovni_dny = implode(',', array_keys($pracovni_dny_raw));
$salon_nazev = nastaveni('salon_nazev', 'Kadeřnictví Denisa');
?>
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Změna rezervace – <?= htmlspecialchars($salon_nazev) ?></title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,sans-serif;background:#f9fafb;color:#111827;min-height:100vh}
.page{max-width:520px;margin:0 auto;padding:2rem 1rem}
.logo{text-align:center;margin-bottom:1.5rem;font-size:20px;font-weight:700;color:#7c3aed}
.card{background:#fff;border:1px solid #e5e7eb;border-radius:16px;padding:1.5rem;margin-bottom:1rem}
h2{font-size:18px;font-weight:700;margin-bottom:6px}
.sub{font-size:13px;color:#6b7280;margin-bottom:1.25rem}
label{font-size:13px;color:#6b7280;display:block;margin-bottom:6px;margin-top:12px}
select{width:100%;border:2px solid #e9d5ff;border-radius:8px;padding:10px 12px;font-size:14px;color:#111827;outline:none;margin-bottom:4px;font-family:inherit;background:#fff}
select:focus{border-color:#a855f7}
.btn{width:100%;background:#9333ea;color:#fff;border:none;border-radius:999px;padding:13px;font-size:15px;font-weight:600;cursor:pointer;margin-top:16px}
.btn:hover:not(:disabled){background:#7c3aed}
.btn:disabled{background:#d8b4fe;cursor:default}
.alert{border-radius:10px;padding:12px 14px;font-size:13px;margin-bottom:1rem}
.alert.error{background:#fee2e2;color:#991b1b;border:1px solid #fca5a5}
.alert.success{background:#dcfce7;color:#166534;border:1px solid #86efac}
.info-row{display:flex;justify-content:space-between;font-size:13px;padding:6px 0;border-bottom:1px solid #f3e8ff}
.info-row:last-child{border-bottom:none;margin-bottom:1rem}
.info-label{color:#6b7280}
.info-val{font-weight:600}
.cal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.cal-month{font-size:14px;font-weight:600}
.cal-nav{width:30px;height:30px;border:1px solid #e9d5ff;border-radius:8px;background:none;cursor:pointer;color:#9333ea;font-size:16px}
.cal-nav:hover{background:#faf5ff}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:3px}
.cal-day-name{text-align:center;font-size:11px;color:#9ca3af;padding:3px 0;font-weight:500}
.cal-day{text-align:center;padding:6px 2px;border-radius:7px;font-size:13px;cursor:pointer;border:2px solid transparent}
.cal-day:hover:not(.disabled):not(.empty){background:#f3e8ff;border-color:#e9d5ff}
.cal-day.today{border-color:#d8b4fe;color:#7c3aed;font-weight:600}
.cal-day.selected{background:#9333ea;color:#fff;border-color:#9333ea}
.cal-day.disabled{color:#d1d5db;cursor:default}
.cal-day.empty{cursor:default}
.times-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;margin-top:10px}
.time-slot{border:2px solid #e9d5ff;border-radius:8px;padding:8px 4px;text-align:center;font-size:13px;cursor:pointer;transition:all .15s}
.time-slot:hover:not(.taken){background:#f3e8ff;border-color:#a855f7}
.time-slot.selected{background:#9333ea;color:#fff;border-color:#9333ea}
.time-slot.taken{color:#d1d5db;cursor:default;text-decoration:line-through;background:#f9fafb}
.loading{text-align:center;padding:1rem;color:#9ca3af;font-size:13px}
.note{font-size:12px;color:#9ca3af;margin-top:8px}
</style>
</head>
<body>
<div class="page">
<div class="logo">✂️ <?= htmlspecialchars($salon_nazev) ?></div>
<?php if ($uspech): ?>
<div class="card">
<div class="alert success">✅ Rezervace byla úspěšně změněna. Potvrzení jsme vám zaslali e-mailem.</div>
<a href="/" style="display:block;text-align:center;margin-top:1rem;color:#7c3aed;font-size:14px">← Zpět na rezervace</a>
</div>
<?php elseif ($error && empty($rezervace)): ?>
<div class="card">
<div class="alert error"><?= htmlspecialchars($error) ?></div>
<a href="/" style="display:block;text-align:center;margin-top:1rem;color:#7c3aed;font-size:14px">← Nová rezervace</a>
</div>
<?php else: ?>
<div class="card">
<h2>Změnit rezervaci</h2>
<div class="sub">Aktuální termín</div>
<div class="info-row"><span class="info-label">Služba</span><span class="info-val"><?= htmlspecialchars($rezervace['sluzba_nazev']) ?></span></div>
<div class="info-row"><span class="info-label">Datum</span><span class="info-val"><?= htmlspecialchars($rezervace['datum']) ?></span></div>
<div class="info-row"><span class="info-label">Čas</span><span class="info-val"><?= substr($rezervace['cas_od'],0,5) ?></span></div>
<?php if ($error): ?>
<div class="alert error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="post" id="zmenForm">
<input type="hidden" name="datum" id="f-datum">
<input type="hidden" name="cas" id="f-cas">
<label>Nová služba</label>
<select name="sluzba_id" id="f-sluzba" onchange="onSluzbaChange()">
<?php foreach ($sluzby as $s): ?>
<option value="<?= $s['id'] ?>" data-trvani="<?= $s['trvani_min'] ?>" <?= $s['id']==$rezervace['sluzba_id']?'selected':'' ?>>
<?= htmlspecialchars($s['nazev']) ?> – <?= $s['trvani_min'] ?> min – <?= number_format($s['cena'],0,',',' ') ?> Kč
</option>
<?php endforeach; ?>
</select>
<label>Nové datum</label>
<div class="cal-header">
<button type="button" class="cal-nav" onclick="changeMonth(-1)">‹</button>
<div class="cal-month" id="calMonth"></div>
<button type="button" class="cal-nav" onclick="changeMonth(1)">›</button>
</div>
<div class="cal-grid" id="calGrid"></div>
<div id="timesSection" style="display:none;margin-top:14px">
<label>Nový čas</label>
<div id="timesLoading" class="loading" style="display:none">Načítám časy…</div>
<div class="times-grid" id="timesGrid"></div>
<p class="note">Přeškrtnuté časy jsou obsazeny.</p>
</div>
<button type="submit" class="btn" id="btnUlozit" disabled>Uložit změnu</button>
</form>
</div>
<?php endif; ?>
</div>
<script>
var PRACOVNI_DNY = [<?= $pracovni_dny ?>];
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_HL = ['Po','Út','St','Čt','Pá','So','Ne'];
var selectedDatum = null;
function getSluzbaTrvani() {
var sel = document.getElementById('f-sluzba');
return parseInt(sel.options[sel.selectedIndex].dataset.trvani);
}
function onSluzbaChange() {
document.getElementById('f-datum').value = '';
document.getElementById('f-cas').value = '';
document.getElementById('timesSection').style.display = 'none';
document.getElementById('btnUlozit').disabled = true;
selectedDatum = null;
renderCalendar();
}
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_HL.forEach(function(d){ var el=document.createElement('div'); el.className='cal-day-name'; el.textContent=d; grid.appendChild(el); });
var prvni = new Date(y, m, 1).getDay();
var offset = prvni === 0 ? 6 : prvni - 1;
for (var i = 0; i < offset; i++) { var e=document.createElement('div'); e.className='cal-day empty'; grid.appendChild(e); }
var dniVM = new Date(y, m+1, 0).getDate();
for (var d = 1; d <= dniVM; 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 = PRACOVNI_DNY.indexOf(dow) === -1;
if (isPast || isNepracovni) {
el.classList.add('disabled');
} else {
if (d === now.getDate() && m === now.getMonth() && y === now.getFullYear()) el.classList.add('today');
if (selectedDatum && selectedDatum.getDate()===d && selectedDatum.getMonth()===m && selectedDatum.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) {
selectedDatum = dateObj;
document.getElementById('f-datum').value = formatDate(dateObj);
document.getElementById('f-cas').value = '';
document.getElementById('btnUlozit').disabled = true;
renderCalendar();
var ts = document.getElementById('timesSection');
ts.style.display = 'block';
document.getElementById('timesLoading').style.display = 'block';
document.getElementById('timesGrid').innerHTML = '';
var sluzbaId = document.getElementById('f-sluzba').value;
try {
var r = await fetch('/api/dostupnost?sluzba_id=' + sluzbaId + '&datum=' + formatDate(dateObj));
var data = await r.json();
document.getElementById('timesLoading').style.display = 'none';
if (data.blokovano || !data.volne_casy || !data.volne_casy.length) {
document.getElementById('timesGrid').innerHTML = '<p style="color:#9ca3af;font-size:13px;grid-column:1/-1">' + (data.blokovano ? 'Tento den je zablokován.' : 'Žá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() {
document.querySelectorAll('.time-slot').forEach(function(s){ s.classList.remove('selected'); });
el.classList.add('selected');
document.getElementById('f-cas').value = t;
document.getElementById('btnUlozit').disabled = false;
};
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 načítání termínů.</p>';
}
}
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 formatDate(d) {
return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
}
renderCalendar();
</script>
</body>
</html>