| 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/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,maximum-scale=1,user-scalable=no">
<title>Admin – Kadeřnictví Denisa</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;--p500:#a855f7;
--p600:#9333ea;--p700:#7c3aed;--p800:#6b21a8;
--g50:#f9fafb;--g100:#f3f4f6;--g200:#e5e7eb;--g300:#d1d5db;
--g400:#9ca3af;--g500:#6b7280;--g700:#374151;--g900:#111827;
}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--g50);color:var(--g900)}
.app{max-width:480px;margin:0 auto;padding-bottom:80px}
/* Login */
#loginView{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:1rem}
.login-box{background:#fff;border:1px solid var(--g200);border-radius:20px;padding:2rem;width:100%;max-width:360px;text-align:center}
.login-logo{font-size:40px;margin-bottom:12px}
.login-title{font-size:20px;font-weight:700;margin-bottom:4px}
.login-sub{font-size:13px;color:var(--g400);margin-bottom:1.5rem}
input{width:100%;border:2px solid var(--p200);border-radius:8px;padding:11px 12px;font-size:14px;color:var(--g900);outline:none;margin-bottom:10px;font-family:inherit;background:#fff}
input:focus{border-color:var(--p500)}
.btn-login{width:100%;background:var(--p600);color:#fff;border:none;border-radius:999px;padding:13px;font-size:15px;font-weight:600;cursor:pointer}
.btn-login:hover{background:var(--p700)}
.alert{border-radius:10px;padding:10px 14px;font-size:13px;margin-bottom:12px;display:none}
.alert.error{background:#fee2e2;color:#991b1b;display:block}
.alert.success{background:#dcfce7;color:#166534;display:block}
/* Main */
#mainView{display:none}
.topbar{background:#fff;border-bottom:1px solid var(--g200);padding:14px 16px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:10}
.topbar-title{font-size:16px;font-weight:700}
.topbar-sub{font-size:12px;color:var(--g400)}
.topbar-btn{background:none;border:1px solid var(--p200);border-radius:8px;width:34px;height:34px;cursor:pointer;color:var(--p600);font-size:16px}
.scroll{padding:14px 16px;display:flex;flex-direction:column;gap:14px}
/* Tabs */
.bottomnav{position:fixed;bottom:0;left:50%;transform:translateX(-50%);width:100%;max-width:480px;background:#fff;border-top:1px solid var(--g200);display:flex;z-index:20}
.nav-item{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:10px 4px;cursor:pointer;border:none;background:none;color:var(--g400);font-size:10px;gap:3px}
.nav-item.active{color:var(--p700)}
.nav-icon{font-size:20px}
/* Tab views */
.tab-view{display:none}
.tab-view.active{display:flex;flex-direction:column;gap:14px}
/* Cards */
.card{background:#fff;border:1px solid var(--g200);border-radius:14px;padding:14px;overflow:hidden}
.card-title{font-size:14px;font-weight:600;margin-bottom:10px;display:flex;align-items:center;gap:6px}
.card-title span{font-size:16px}
/* Stats */
.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.stat-card{background:var(--p50);border:1px solid var(--p200);border-radius:10px;padding:12px}
.stat-val{font-size:22px;font-weight:700;color:var(--p800)}
.stat-label{font-size:11px;color:var(--p600);margin-top:2px}
/* Rezervace */
.rez-item{display:flex;gap:10px;padding:10px 0;border-bottom:1px solid var(--g100)}
.rez-item:last-child{border-bottom:none;padding-bottom:0}
.rez-time{min-width:44px;background:var(--p100);border-radius:8px;padding:5px 4px;text-align:center}
.rez-time-h{font-size:13px;font-weight:700;color:var(--p800)}
.rez-time-d{font-size:10px;color:var(--p600)}
.rez-info{flex:1}
.rez-name{font-size:13px;font-weight:600}
.rez-service{font-size:12px;color:var(--g400);margin-top:1px}
.rez-actions{display:flex;gap:5px;margin-top:6px}
.btn-sm{border:none;border-radius:6px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer}
.btn-confirm{background:#dcfce7;color:#166534}
.btn-cancel-sm{background:#fee2e2;color:#991b1b}
.btn-detail{background:var(--g100);color:var(--g700)}
/* Badge */
.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:600}
.badge-green{background:#dcfce7;color:#166534}
.badge-amber{background:#fef3c7;color:#92400e}
.badge-red{background:#fee2e2;color:#991b1b}
.badge-purple{background:var(--p100);color:var(--p800)}
/* Služby */
.svc-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--g100)}
.svc-row:last-child{border-bottom:none}
.svc-icon{width:32px;height:32px;background:var(--p100);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0}
.svc-name{font-size:13px;font-weight:600}
.svc-detail{font-size:11px;color:var(--g400)}
.svc-price{font-size:13px;font-weight:700;color:var(--p700)}
.btn-icon{width:30px;height:30px;border:1px solid var(--g200);border-radius:8px;background:none;cursor:pointer;font-size:14px}
/* Nastavení */
.set-row{display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-bottom:1px solid var(--g100);gap:12px}
.set-row:last-child{border-bottom:none}
.set-label{font-size:13px;color:var(--g700);flex-shrink:1;min-width:0}
.set-sub{font-size:11px;color:var(--g400)}
.set-input{border:2px solid var(--p200);border-radius:8px;padding:6px 10px;font-size:13px;color:var(--g900);outline:none;width:130px;max-width:40vw;text-align:right;background:#fff;flex-shrink:0;min-width:0}
.set-input:focus{border-color:var(--p500)}
.days-wrap{display:flex;gap:5px;flex-wrap:wrap}
.day-pill{width:30px;height:30px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;cursor:pointer;border:2px solid var(--p200);color:var(--p700);background:#fff}
.day-pill.on{background:var(--p600);color:#fff;border-color:var(--p600)}
.btn-save{width:100%;background:var(--p600);color:#fff;border:none;border-radius:999px;padding:13px;font-size:15px;font-weight:600;cursor:pointer;margin-top:4px}
.btn-save:hover{background:var(--p700)}
.btn-add{display:inline-flex;align-items:center;gap:4px;background:var(--p100);color:var(--p800);border:none;border-radius:999px;padding:5px 12px;font-size:12px;font-weight:600;cursor:pointer}
/* Modal */
.modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:50;align-items:flex-end;justify-content:center}
.modal-overlay.active{display:flex}
.modal-sheet{background:#fff;border-radius:20px 20px 0 0;padding:16px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto}
.modal-handle{width:36px;height:4px;background:var(--g200);border-radius:2px;margin:0 auto 14px}
.modal-title{font-size:16px;font-weight:700;margin-bottom:14px}
.modal-label{font-size:12px;color:var(--g500);display:block;margin-bottom:4px}
.modal-input{width:100%;border:2px solid var(--p200);border-radius:8px;padding:9px 12px;font-size:14px;color:var(--g900);outline:none;margin-bottom:10px;font-family:inherit;background:#fff}
.modal-input:focus{border-color:var(--p500)}
.btn-row{display:flex;gap:8px;margin-top:4px}
.btn-modal-save{flex:1;background:var(--p600);color:#fff;border:none;border-radius:999px;padding:11px;font-size:14px;font-weight:600;cursor:pointer}
.btn-modal-discard{flex:1;background:var(--g100);color:var(--g700);border:none;border-radius:999px;padding:11px;font-size:14px;cursor:pointer}
.loading{text-align:center;padding:1.5rem;color:var(--g400);font-size:14px}
.empty{text-align:center;padding:1.5rem;color:var(--g400);font-size:13px}
/* Blokace */
.blok-row{display:flex;align-items:center;justify-content:space-between;padding:9px 0;border-bottom:1px solid var(--g100)}
.blok-row:last-child{border-bottom:none}
.blok-date{font-size:13px;font-weight:600}
.blok-reason{font-size:11px;color:var(--g400);margin-top:1px}
.btn-del{background:none;border:none;cursor:pointer;font-size:16px;color:#9ca3af;padding:4px}
.btn-del:hover{color:#991b1b}
.drag-handle{cursor:grab;color:#d1d5db;font-size:18px;padding:0 4px;user-select:none;flex-shrink:0}
.drag-handle:active{cursor:grabbing}
.svc-row.sortable-ghost{opacity:.4;background:#faf5ff}
.svc-row.sortable-chosen{background:#faf5ff}
.zak-card{background:#fff;border:0.5px solid var(--g200);border-radius:12px;padding:12px 14px;cursor:pointer;transition:all .15s;margin-bottom:8px}
.zak-card:hover{border-color:#e9d5ff;background:#faf5ff}
.zak-avatar{width:38px;height:38px;border-radius:50%;background:#f3e8ff;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:700;color:#7c3aed;flex-shrink:0}
.zak-name{font-size:14px;font-weight:600;color:#111827}
.zak-sub{font-size:11px;color:#9ca3af;margin-top:1px}
.zak-stat{font-size:11px;color:#7c3aed;background:#f3e8ff;border-radius:6px;padding:2px 7px;white-space:nowrap}
.stitek{display:inline-flex;align-items:center;gap:3px;background:#f3e8ff;color:#6b21a8;font-size:11px;font-weight:500;padding:2px 8px;border-radius:999px;margin:2px}
.stitek-del{cursor:pointer;opacity:.6;font-size:10px}
.stitek-del:hover{opacity:1}
.rez-checkbox{width:16px;height:16px;accent-color:#9333ea;cursor:pointer;flex-shrink:0;margin-top:2px}
.rez-item.checked{background:#faf5ff;border-radius:8px}
.hromadne-bar{display:none;background:#f3e8ff;border:1.5px solid #e9d5ff;border-radius:10px;padding:10px 12px;margin-bottom:10px;align-items:center;justify-content:space-between;gap:10px}
.hromadne-bar.show{display:flex}
.hromadne-info{font-size:13px;color:#6b21a8;font-weight:500}
.btn-hromadne{background:#9333ea;color:#fff;border:none;border-radius:999px;padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer}
.btn-hromadne:hover{background:#7c3aed}
.btn-hromadne-zrusit{background:none;border:1.5px solid #e9d5ff;border-radius:999px;padding:7px 14px;font-size:12px;color:#9333ea;cursor:pointer}
/* Výběr části dne */
.cal-strip{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px}
.cal-strip::-webkit-scrollbar{display:none}
.view-btn{border:1.5px solid #e9d5ff;border-radius:8px;padding:5px 10px;font-size:12px;color:#7c3aed;background:#fff;cursor:pointer}
.view-btn.active{background:#9333ea;color:#fff;border-color:#9333ea}
.cal-nav-btn{width:28px;height:28px;border:1px solid #e9d5ff;border-radius:6px;background:none;cursor:pointer;color:#9333ea;font-size:16px}
.cal-nav-btn:hover{background:#faf5ff}
.mesic-den{border-radius:6px;padding:4px 2px;text-align:center;font-size:12px;cursor:pointer;min-height:36px;position:relative;border:1.5px solid transparent}
.mesic-den:hover:not(.mesic-disabled):not(.mesic-empty){background:#faf5ff;border-color:#e9d5ff}
.mesic-den.mesic-dnes{border-color:#d8b4fe;color:#7c3aed;font-weight:600}
.mesic-den.mesic-selected{background:#f3e8ff;border-color:#9333ea}
.mesic-den.mesic-disabled{color:#d1d5db;cursor:default}
.mesic-den-dots{display:flex;gap:2px;justify-content:center;margin-top:2px;flex-wrap:wrap}
.dot-ceka{width:6px;height:6px;border-radius:50%;background:#f59e0b;flex-shrink:0}
.dot-potvrzena{width:6px;height:6px;border-radius:50%;background:#22c55e;flex-shrink:0}
.dot-zrusena{width:6px;height:6px;border-radius:50%;background:#ef4444;flex-shrink:0}
.cal-pill{min-width:44px;padding:7px 4px;text-align:center;border-radius:8px;border:2px solid var(--p200);cursor:pointer;flex-shrink:0}
.cal-pill.active{background:var(--p600);border-color:var(--p600)}
.cal-pill .wd{font-size:10px;color:var(--p600)}
.cal-pill.active .wd{color:#e9d5ff}
.cal-pill .dn{font-size:15px;font-weight:700;color:var(--p800)}
.cal-pill.active .dn{color:#fff}
.cal-pill .dot{width:5px;height:5px;border-radius:50%;background:var(--p400);margin:3px auto 0}
.cal-pill.active .dot{background:#e9d5ff}
</style>
</head>
<body>
<!-- LOGIN -->
<div id="loginView">
<div class="login-box">
<div class="login-logo">✂️</div>
<div class="login-title">Admin panel</div>
<div class="login-sub" id="login-salon-nazev">načítám</div>
<div id="login-alert" class="alert"></div>
<input type="text" id="login-user" placeholder="Uživatelské jméno" onkeydown="if(event.key==='Enter')doLogin()">
<input type="password" id="login-pass" placeholder="Heslo" onkeydown="if(event.key==='Enter')doLogin()">
<div style="display:flex;align-items:center;gap:8px;margin:-4px 0 12px;text-align:left">
<input type="checkbox" id="login-remember" style="width:16px;height:16px;accent-color:#9333ea;margin:0;flex-shrink:0">
<label for="login-remember" style="font-size:13px;color:#6b7280;cursor:pointer">Zapamatovat mě na 90 dní</label>
</div>
<button class="btn-login" onclick="doLogin()">Přihlásit se</button>
</div>
</div>
<!-- MAIN -->
<div id="mainView">
<div class="topbar">
<div>
<div class="topbar-title" id="topbar-title">Dnešní přehled</div>
<div class="topbar-sub" id="topbar-sub"></div>
</div>
<div style="display:flex;gap:6px"><button class="topbar-btn" onclick="openModal('modal-nova-rez')" title="Nová rezervace">➕</button><button class="topbar-btn" onclick="location.reload()" title="Obnovit stránku">🔄</button><button class="topbar-btn" onclick="openModal('modal-heslo')" title="Změnit heslo">🔑</button><button class="topbar-btn" onclick="doLogout()" title="Odhlásit">⎋</button></div>
</div>
<div class="app">
<div class="scroll">
<!-- TAB: DNES -->
<div class="tab-view active" id="tab-dnes">
<div class="stat-grid" id="stats-dnes">
<div class="stat-card"><div class="stat-val">…</div><div class="stat-label">rezervací dnes</div></div>
<div class="stat-card"><div class="stat-val">…</div><div class="stat-label">tržby dnes</div></div>
</div>
<div class="card">
<div class="card-title"><span>🕐</span> Průběh dne</div>
<div id="dnes-list"><div class="loading">Načítám…</div></div>
</div>
</div>
<!-- TAB: KALENDÁŘ -->
<div class="tab-view" id="tab-kalendar">
<div class="card" style="padding:12px 14px">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
<div style="display:flex;gap:6px">
<button class="view-btn active" id="vbtn-mesic" onclick="setView('mesic')">📅 Měsíc</button>
<button class="view-btn" id="vbtn-seznam" onclick="setView('seznam')">📋 Seznam</button>
</div>
<select id="kal-filtr" onchange="applyFilter()" style="border:1.5px solid #e9d5ff;border-radius:8px;padding:5px 8px;font-size:12px;color:#374151;background:#fff;outline:none">
<option value="">Všechny</option>
<option value="ceka">⏳ Čekající</option>
<option value="potvrzena">✅ Potvrzené</option>
<option value="zrusena">❌ Zrušené</option>
</select>
</div>
<!-- Měsíční pohled -->
<div id="view-mesic">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
<button class="cal-nav-btn" onclick="mesicChange(-1)">‹</button>
<div style="font-size:14px;font-weight:600;color:#111827" id="mesic-label"></div>
<button class="cal-nav-btn" onclick="mesicChange(1)">›</button>
</div>
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px" id="mesic-hlavicka"></div>
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:2px" id="mesic-grid"></div>
<div style="display:flex;gap:10px;margin-top:8px;flex-wrap:wrap">
<span style="font-size:11px;color:#6b7280;display:flex;align-items:center;gap:4px"><span style="width:8px;height:8px;border-radius:50%;background:#f59e0b;display:inline-block"></span>Čeká</span>
<span style="font-size:11px;color:#6b7280;display:flex;align-items:center;gap:4px"><span style="width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block"></span>Potvrzeno</span>
<span style="font-size:11px;color:#6b7280;display:flex;align-items:center;gap:4px"><span style="width:8px;height:8px;border-radius:50%;background:#ef4444;display:inline-block"></span>Zrušeno</span>
</div>
</div>
<!-- Seznam pohled -->
<div id="view-seznam" style="display:none">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
<button class="cal-nav-btn" onclick="seznamChange(-1)">‹</button>
<div style="font-size:14px;font-weight:600;color:#111827" id="seznam-label"></div>
<button class="cal-nav-btn" onclick="seznamChange(1)">›</button>
</div>
</div>
</div>
<!-- Detail dne / seznam výsledků -->
<div class="hromadne-bar" id="hromadne-bar">
<div class="hromadne-info" id="hromadne-info">0 vybraných</div>
<div style="display:flex;gap:6px">
<button class="btn-hromadne-zrusit" onclick="zrusitVyber()">Zrušit výběr</button>
<button class="btn-hromadne" onclick="hromadnePotvrzeni()">✅ Potvrdit vybrané</button>
</div>
</div>
<div class="card" id="kal-detail-card">
<div class="card-title"><span>📋</span> <span id="kal-datum-label">Vyberte den</span></div>
<div id="kal-list"><div class="empty">Vyberte den v kalendáři</div></div>
</div>
</div>
<!-- TAB: SLUŽBY -->
<div class="tab-view" id="tab-sluzby">
<div class="card">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
<div class="card-title" style="margin-bottom:0"><span>✂️</span> Aktivní služby</div>
<button class="btn-add" onclick="openSvcModal(null)">+ Přidat</button>
</div>
<div id="sluzby-list"><div class="loading">Načítám…</div></div>
</div>
<div class="card">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
<div class="card-title" style="margin-bottom:0"><span>🚫</span> Blokované termíny</div>
<button class="btn-add" onclick="openBlokModal(null)">+ Přidat</button>
</div>
<div id="blok-list"><div class="loading">Načítám…</div></div>
</div>
</div>
<!-- TAB: KARTOTÉKA -->
<div class="tab-view" id="tab-kartoteka">
<div style="display:flex;gap:8px;margin-bottom:4px">
<input type="search" id="kartotek-search" placeholder="🔍 Hledat jméno, e-mail, telefon…" style="flex:1;border:1.5px solid #e9d5ff;border-radius:8px;padding:9px 12px;font-size:13px;outline:none;background:#fff;color:#111827" oninput="searchKartoteka(this.value)">
</div>
<div id="kartoteka-list"><div class="loading">Načítám…</div></div>
</div>
<!-- TAB: QR PLATBA -->
<div class="tab-view" id="tab-qr">
<div class="card">
<div class="card-title"><span>💳</span> Generátor QR platby</div>
<div style="font-size:12px;color:#9ca3af;margin-bottom:14px" id="qr-ucet-popis">Účet: načítám… · VS: dnešní datum (automaticky)</div>
<div class="set-row" style="border:none;padding-top:0">
<div><div class="set-label">Částka (Kč) *</div></div>
<input class="set-input" type="number" id="qr-castka" placeholder="0" min="1" style="width:120px;max-width:30vw" oninput="generateQR()">
</div>
<div class="set-row" style="border:none">
<div><div class="set-label">Poznámka / zpráva</div></div>
<input class="set-input" type="text" id="qr-poznamka" placeholder="Střih…" maxlength="60" style="width:160px;max-width:42vw" oninput="generateQR()">
</div>
<div style="border-top:0.5px solid var(--g200);margin:10px 0;padding-top:14px;text-align:center" id="qr-wrap" style="display:none">
<div id="qr-code" style="display:inline-block;background:#fff;padding:12px;border-radius:12px;border:1px solid #e9d5ff"></div>
<div style="margin-top:10px;font-size:12px;color:#6b7280" id="qr-info"></div>
</div>
<div id="qr-placeholder" style="text-align:center;padding:1.5rem;color:#d1d5db;font-size:13px">Zadejte částku pro vygenerování QR kódu</div>
</div>
</div>
<!-- TAB: NASTAVENÍ -->
<div class="tab-view" id="tab-nastaveni">
<div class="card">
<div class="card-title"><span>🏪</span> Základní údaje</div>
<div class="set-row"><div><div class="set-label">Název salónu</div></div><input class="set-input" id="set-salon_nazev" style="width:160px;max-width:42vw"></div>
<div class="set-row"><div><div class="set-label">Telefon</div></div><input class="set-input" id="set-salon_telefon" style="width:160px;max-width:42vw"></div>
<div class="set-row"><div><div class="set-label">Kontaktní e-mail</div></div><input class="set-input" id="set-salon_email" style="width:160px;max-width:42vw"></div>
<div class="set-row"><div><div class="set-label">Adresa</div><div class="set-sub">Zobrazí se v patičce rezervací</div></div><input class="set-input" id="set-salon_adresa" placeholder="Ulice 1, 123 45 Město" style="width:160px;max-width:42vw"></div>
<div class="set-row"><div><div class="set-label">Číslo účtu</div><div class="set-sub">Pro QR platbu</div></div><input class="set-input" id="set-cislo_uctu" placeholder="1234567890/5500" style="width:160px;max-width:42vw"></div>
</div>
<div class="card">
<div class="card-title"><span>🕐</span> Pracovní doba</div>
<div style="font-size:12px;color:#9ca3af;margin-bottom:12px">Zaškrtněte dny a nastavte časy pro každý den zvlášť</div>
<div id="pracovni-doba-wrap"></div>
<div class="set-row" style="border:none;padding-bottom:0">
<div><div class="set-label">Délka slotu (min)</div></div>
<input class="set-input" type="number" id="set-slot_minut" style="width:90px">
</div>
</div>
<div class="card">
<div class="card-title"><span>⚙️</span> Rezervační pravidla</div>
<div class="set-row">
<div><div class="set-label">Min. předstih (hod)</div></div>
<input class="set-input" type="number" id="set-rezervace_min_predem" style="width:90px">
</div>
<div class="set-row">
<div><div class="set-label">Storno do (hod)</div></div>
<input class="set-input" type="number" id="set-rezervace_storno_hod" style="width:90px">
</div>
</div>
<div class="card">
<div class="card-title"><span>🔔</span> Připomenutí rezervace</div>
<div class="set-row">
<div><div class="set-label">Odesílat připomenutí</div><div class="set-sub">E-mail zákazníkovi před termínem</div></div>
<label style="position:relative;display:inline-block;width:46px;height:26px;flex-shrink:0">
<input type="checkbox" id="set-pripominka_zapnuta" style="opacity:0;width:0;height:0" onchange="togglePripominka(this)">
<span id="pripominka-toggle" style="position:absolute;cursor:pointer;inset:0;background:#e5e7eb;border-radius:999px;transition:.2s"></span>
<span id="pripominka-knob" style="position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2)"></span>
</label>
</div>
<div class="set-row" id="pripominka-hod-row" style="display:none">
<div><div class="set-label">Kolik hodin předem</div><div class="set-sub">Např. 24 = den předem</div></div>
<input class="set-input" type="number" id="set-pripominka_hodin" style="width:90px" min="1" max="168">
</div>
</div>
<div class="card">
<div class="card-title"><span>⭐</span> Hodnocení rezervací</div>
<div class="set-row">
<div><div class="set-label">Žádost o hodnocení</div><div class="set-sub">E-mail po termínu s odkazem na hvězdičky</div></div>
<label style="position:relative;display:inline-block;width:46px;height:26px;flex-shrink:0">
<input type="checkbox" id="set-hodnoceni_zadost_zapnuta" style="opacity:0;width:0;height:0" onchange="toggleSwitch(this,'hodnoceni-hod-row','hodnoceni-zadost-toggle','hodnoceni-zadost-knob')">
<span id="hodnoceni-zadost-toggle" style="position:absolute;cursor:pointer;inset:0;background:#e5e7eb;border-radius:999px;transition:.2s"></span>
<span id="hodnoceni-zadost-knob" style="position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2)"></span>
</label>
</div>
<div class="set-row" id="hodnoceni-hod-row" style="display:none">
<div><div class="set-label">Kolik hodin po termínu</div><div class="set-sub">Např. 2 = dvě hodiny po skončení</div></div>
<input class="set-input" type="number" id="set-hodnoceni_hodin_po" style="width:90px" min="1" max="168">
</div>
<div class="set-row">
<div><div class="set-label">Zobrazit hodnocení na webu</div><div class="set-sub">Průměr a počet v rezervačním formuláři</div></div>
<label style="position:relative;display:inline-block;width:46px;height:26px;flex-shrink:0">
<input type="checkbox" id="set-hodnoceni_verejne_zapnuta" style="opacity:0;width:0;height:0" onchange="toggleSwitchSimple(this,'hodnoceni-verejne-toggle','hodnoceni-verejne-knob')">
<span id="hodnoceni-verejne-toggle" style="position:absolute;cursor:pointer;inset:0;background:#e5e7eb;border-radius:999px;transition:.2s"></span>
<span id="hodnoceni-verejne-knob" style="position:absolute;height:20px;width:20px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2)"></span>
</label>
</div>
</div>
<div id="set-alert" class="alert" style="display:none"></div>
<button class="btn-save" onclick="saveSettings()">Uložit nastavení</button>
<!-- Správa uživatelů - viditelná jen pro admin -->
<div class="card" id="card-uzivatele" style="display:none;margin-top:14px">
<div class="card-title"><span>👤</span> Správa uživatelů</div>
<div id="uzivatele-list"><div class="loading">Načítám…</div></div>
</div>
</div>
</div>
</div>
<div class="bottomnav">
<button class="nav-item active" id="nav-dnes" onclick="switchTab('dnes')"><span class="nav-icon">🏠</span>Dnes</button>
<button class="nav-item" id="nav-kalendar" onclick="switchTab('kalendar')"><span class="nav-icon">📅</span>Kalendář</button>
<button class="nav-item" id="nav-qr" onclick="switchTab('qr')"><span class="nav-icon">💳</span>QR platba</button>
<button class="nav-item" id="nav-kartoteka" onclick="switchTab('kartoteka')"><span class="nav-icon">👥</span>Kartotéka</button>
<button class="nav-item" id="nav-sluzby" onclick="switchTab('sluzby')"><span class="nav-icon">✂️</span>Služby</button>
<button class="nav-item" id="nav-nastaveni" onclick="switchTab('nastaveni')"><span class="nav-icon">⚙️</span>Nastavení</button>
</div>
</div>
<!-- MODAL: Rezervace detail -->
<div class="modal-overlay" id="modal-rez">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Detail rezervace</div>
<div id="modal-rez-content"></div>
<div class="btn-row" id="modal-rez-btns"></div>
</div>
</div>
<!-- MODAL: Služba -->
<div class="modal-overlay" id="modal-svc">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title" id="modal-svc-title">Nová služba</div>
<input type="hidden" id="svc-id">
<label class="modal-label">Název</label>
<input class="modal-input" id="svc-nazev" placeholder="Střih & styling">
<label class="modal-label">Trvání (minuty)</label>
<input class="modal-input" type="number" id="svc-trvani" placeholder="60">
<label class="modal-label">Cena (Kč)</label>
<input class="modal-input" type="text" inputmode="numeric" id="svc-cena" placeholder="Volitelné">
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-svc')">Zrušit</button>
<button class="btn-modal-save" onclick="saveSvc()">Uložit</button>
</div>
</div>
</div>
<!-- MODAL: Blokace -->
<div class="modal-overlay" id="modal-blok">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Blokovat termín</div>
<label class="modal-label">Od</label>
<input class="modal-input" type="date" id="blok-od">
<label class="modal-label">Do (stejný den = jen tento den)</label>
<input class="modal-input" type="date" id="blok-do">
<label class="modal-label">Důvod</label>
<input class="modal-input" id="blok-duvod" placeholder="Dovolená, školení…">
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-blok')">Zrušit</button>
<button class="btn-modal-save" onclick="saveBlok()">Uložit</button>
</div>
</div>
</div>
<script>
var API = '/api';
// Načti název salónu pro přihlašovací obrazovku
document.addEventListener('DOMContentLoaded', function() {
var el = document.getElementById('login-salon-nazev');
if (!el) return;
fetch('/api/nastaveni/public')
.then(function(r){ return r.json(); })
.then(function(d){
el.textContent = (d.ok && d.data && d.data.salon_nazev) ? d.data.salon_nazev : 'Rezervační systém';
document.title = 'Admin – ' + el.textContent;
})
.catch(function(){
el.textContent = 'Rezervační systém';
});
});
// Úložiště tokenu — sessionStorage nebo localStorage podle "zapamatovat mě"
var _store = {
getItem: function(k) {
try { return localStorage.getItem(k) || sessionStorage.getItem(k); } catch(e) { return null; }
},
setItem: function(k, v, persistent) {
try {
// Zjisti zda je token persistentní (uložen v localStorage)
var isPersistent = persistent !== undefined ? persistent : !!localStorage.getItem('admin_remember');
if (isPersistent) { localStorage.setItem(k, v); sessionStorage.removeItem(k); }
else { sessionStorage.setItem(k, v); localStorage.removeItem(k); }
} catch(e) {}
},
removeItem: function(k) {
try { localStorage.removeItem(k); sessionStorage.removeItem(k); } catch(e) {}
}
};
var token = _store.getItem('admin_token') || '';
var MESICE = ['led','úno','bře','dub','kvě','čvn','čvc','srp','zář','říj','lis','pro'];
var DNY_SHORT = ['Ne','Po','Út','St','Čt','Pá','So'];
var activeDays = [1,2,3,4,5,6];
function authHeaders() {
return { 'Content-Type':'application/json', 'Authorization':'Bearer ' + token };
}
async function doLogin() {
var user = document.getElementById('login-user').value.trim();
var pass = document.getElementById('login-pass').value;
var remember = document.getElementById('login-remember').checked;
var alertEl = document.getElementById('login-alert');
alertEl.className = 'alert';
if (!user || !pass) { alertEl.className='alert error'; alertEl.textContent='Vyplňte přihlašovací údaje.'; return; }
try {
var r = await fetch(API + '/auth/login', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({uzivatelske_jmeno:user, heslo:pass, remember_me: remember}) });
var d = await r.json();
if (d.ok) {
token = d.token;
_store.setItem('admin_token', token, remember);
_store.setItem('admin_uzivatel', d.uzivatel || '', remember);
if (remember) localStorage.setItem('admin_remember', '1');
else localStorage.removeItem('admin_remember');
showMain();
} else {
alertEl.className='alert error'; alertEl.textContent = d.error || 'Nesprávné přihlašovací údaje.';
}
} catch(e) {
alertEl.className='alert error'; alertEl.textContent='Chyba připojení.';
}
}
function doLogout() {
fetch(API + '/auth/logout', { method:'POST', headers: authHeaders() });
_store.removeItem('admin_token');
_store.removeItem('admin_uzivatel');
localStorage.removeItem('admin_remember');
// Úklid starých WebAuthn dat
Object.keys(localStorage).forEach(function(k) {
if (k.indexOf('webauthn_') === 0) localStorage.removeItem(k);
});
token = '';
document.getElementById('mainView').style.display = 'none';
document.getElementById('loginView').style.display = 'flex';
}
function showMain() {
document.getElementById('loginView').style.display = 'none';
document.getElementById('mainView').style.display = 'block';
var now = new Date();
var uzivatel = _store.getItem('admin_uzivatel') || '';
document.getElementById('topbar-sub').textContent = now.toLocaleDateString('cs-CZ', {weekday:'long', day:'numeric', month:'long'})
+ (uzivatel ? ' · ' + uzivatel : '');
loadDnes();
buildCalStrip();
loadSluzby();
loadSluzbySel();
loadKartoteka();
loadBlokace();
loadSettings();
setTimeout(zpracujHash, 300);
}
// Při načtení zkus token z localStorage
if (token) showMain();
// Zpracuj hash po přihlášení nebo při načtení
function zpracujHash() {
var hash = window.location.hash; // např. #kalendar/2026-06-24
if (!hash) return;
var parts = hash.replace('#', '').split('/');
var tab = parts[0];
var datum = parts[1];
if (tab === 'kalendar') {
switchTab('kalendar');
if (datum && datum.match(/^\d{4}-\d{2}-\d{2}$/)) {
// Nastavit měsíc podle data a vybrat den
var d = new Date(datum + 'T12:00:00');
mesicDate = new Date(d.getFullYear(), d.getMonth(), 1);
selectedMesicDay = datum;
loadMesicData().then(function() {
var rezy = allRezervace[datum] || [];
showDenDetail(datum, rezy);
});
}
}
// Vymaž hash z URL bez reload
history.replaceState(null, '', window.location.pathname);
}
function switchTab(tab) {
['dnes','kalendar','sluzby','kartoteka','qr','nastaveni'].forEach(function(t) {
document.getElementById('tab-'+t).classList.toggle('active', t===tab);
document.getElementById('nav-'+t).classList.toggle('active', t===tab);
});
var titles = {dnes:'Dnešní přehled', kalendar:'Kalendář', sluzby:'Služby & ceny', kartoteka:'Kartotéka', qr:'QR platba', nastaveni:'Nastavení'};
document.getElementById('topbar-title').textContent = titles[tab];
if (tab === 'nastaveni') loadUzivatele();
}
async function loadDnes() {
var dnes = formatDate(new Date());
try {
var r = await fetch(API + '/rezervace?datum_od=' + dnes + '&datum_do=' + dnes, { headers: authHeaders() });
var d = await r.json();
if (!d.ok) { if(d.error && d.error.includes('Přístup')) { doLogout(); return; } }
var list = d.data || [];
var sg = document.getElementById('stats-dnes');
var trzby = list.filter(function(r){ return r.stav !== 'zrusena'; }).reduce(function(s,r){
if (r.cena_upravena !== null && r.cena_upravena !== undefined && r.cena_upravena !== '') {
return s + (parseFloat(r.cena_upravena) || 0);
}
var zakladni = parseFloat(r.cena) || 0;
var extra = (r.doplnky || []).reduce(function(ss,d){ return ss + parseFloat(d.cena||0); }, 0);
return s + zakladni + extra;
}, 0);
sg.innerHTML = '<div class="stat-card"><div class="stat-val">' + list.filter(function(r){ return r.stav!=='zrusena'; }).length + '</div><div class="stat-label">rezervací dnes</div></div>'
+ '<div class="stat-card"><div class="stat-val">' + Math.round(trzby).toLocaleString('cs-CZ') + ' Kč</div><div class="stat-label">tržby dnes</div></div>';
renderRezList('dnes-list', list);
} catch(e) {}
}
var DNY_SEZNAM = ['Ne','Po','Út','St','Čt','Pá','So'];
var MESICE_SEZNAM = ['led','úno','bře','dub','kvě','čvn','čvc','srp','zář','říj','lis','pro'];
function formatDatumCZ(datum) {
var d = new Date(datum + 'T12:00:00');
return DNY_SEZNAM[d.getDay()] + ' ' + d.getDate() + '. ' + MESICE_SEZNAM[d.getMonth()] + '.';
}
function renderRezList(containerId, list) {
var el = document.getElementById(containerId);
if (!list.length) { el.innerHTML = '<div class="empty">Žádné rezervace</div>'; return; }
var showDate = containerId === 'kal-list' && kalView === 'seznam';
el.innerHTML = list.map(function(r) {
var badgeCls = r.stav==='potvrzena'?'badge-green':r.stav==='zrusena'?'badge-red':'badge-amber';
var badgeTxt = r.stav==='potvrzena'?'Potvrzeno':r.stav==='zrusena'?'Zrušeno':'Čeká';
var btns = r.stav !== 'zrusena' ? '<button class="btn-sm btn-detail" onclick=\'openRezDetail(' + JSON.stringify(r).replace(/'/g,"'") + ')\'>Detail</button>'
+ (r.stav==='ceka' ? '<button class="btn-sm btn-confirm" onclick="confirmRez(' + r.id + ',this)">Potvrdit</button>' : '')
+ '<button class="btn-sm btn-cancel-sm" onclick="cancelRez(' + r.id + ',this)">Zrušit</button>' : '';
var datumBadge = showDate
? '<div style="font-size:11px;font-weight:600;color:#7c3aed;background:#f3e8ff;border-radius:6px;padding:2px 7px;display:inline-block;margin-bottom:4px">' + formatDatumCZ(r.datum) + ' · ' + r.cas_od + '</div><br>'
: '';
var checkbox = r.stav === 'ceka'
? '<input type="checkbox" class="rez-checkbox" id="chk-' + r.id + '" onchange="onCheckChange(' + r.id + ',this)">'
: '<div style="width:16px;flex-shrink:0"></div>';
return '<div class="rez-item" id="rez-' + r.id + '" style="align-items:flex-start">'
+ checkbox
+ (showDate ? '' : '<div class="rez-time"><div class="rez-time-h">' + r.cas_od + '</div><div class="rez-time-d">' + r.cas_do + '</div></div>')
+ '<div class="rez-info" style="flex:1">'
+ datumBadge
+ '<div style="display:flex;align-items:center;justify-content:space-between"><div class="rez-name">' + esc(r.jmeno) + ' ' + esc(r.prijmeni) + '</div><span class="badge ' + badgeCls + '">' + badgeTxt + '</span></div>'
+ '<div class="rez-service">' + esc(r.sluzba)
+ (function() {
var doplnky = r.doplnky || [];
var celkem;
if (r.cena_upravena !== null && r.cena_upravena !== undefined && r.cena_upravena !== '') {
celkem = parseFloat(r.cena_upravena) || 0;
} else {
var zakladni = r.cena && parseFloat(r.cena) > 0 ? parseFloat(r.cena) : 0;
var extraCena = doplnky.reduce(function(s,d){ return s + parseFloat(d.cena||0); }, 0);
celkem = zakladni + extraCena;
}
var cenaStr = celkem > 0 ? ' · ' + Math.round(celkem).toLocaleString('cs-CZ') + ' Kč' : ' · Individuální';
var doplnkyStr = doplnky.length > 0 ? '<span style="color:#9333ea;font-size:11px"> + ' + doplnky.map(function(d){ return d.nazev; }).join(', ') + '</span>' : '';
return cenaStr + doplnkyStr;
})()
+ '</div>'
+ (r.hodnoceni ? '<div style="margin-top:2px">' + renderStars(r.hodnoceni, 12) + '</div>' : '')
+ (r.telefon ? '<div style="font-size:11px;color:#9ca3af;margin-top:2px">📞 ' + esc(r.telefon) + '</div>' : '')
+ '<div class="rez-actions">' + btns + '</div></div></div>';
}).join('');
}
var vybrane = new Set();
function onCheckChange(id, el) {
if (el.checked) {
vybrane.add(id);
document.getElementById('rez-' + id).classList.add('checked');
} else {
vybrane.delete(id);
document.getElementById('rez-' + id).classList.remove('checked');
}
updateHromadneBar();
}
function updateHromadneBar() {
var bar = document.getElementById('hromadne-bar');
var info = document.getElementById('hromadne-info');
if (vybrane.size > 0) {
bar.classList.add('show');
info.textContent = vybrane.size + ' ' + (vybrane.size === 1 ? 'vybraná rezervace' : vybrane.size < 5 ? 'vybrané rezervace' : 'vybraných rezervací');
} else {
bar.classList.remove('show');
}
}
function zrusitVyber() {
vybrane.forEach(function(id) {
var chk = document.getElementById('chk-' + id);
if (chk) chk.checked = false;
var item = document.getElementById('rez-' + id);
if (item) item.classList.remove('checked');
});
vybrane.clear();
updateHromadneBar();
}
async function potvrditJednu(id) {
var r = await fetch(API + '/rezervace/' + id, {
method: 'PUT',
headers: authHeaders(),
body: JSON.stringify({stav: 'potvrzena'})
});
// HTTP 200/201 = úspěch, nehledě na tělo odpovědi
return r.status >= 200 && r.status < 300;
}
async function hromadnePotvrzeni() {
if (vybrane.size === 0) return;
var btn = document.querySelector('.btn-hromadne');
btn.disabled = true;
var ids = Array.from(vybrane).map(Number);
var celkem = ids.length;
var uspesne = 0;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
btn.textContent = 'Potvrzuji ' + (i+1) + ' / ' + celkem + '…';
try {
var ok = await potvrditJednu(id);
if (ok) {
uspesne++;
var item = document.getElementById('rez-' + id);
if (item) {
var badge = item.querySelector('.badge');
if (badge) { badge.className = 'badge badge-green'; badge.textContent = 'Potvrzeno'; }
var chk = document.getElementById('chk-' + id);
if (chk && chk.parentNode) chk.parentNode.removeChild(chk);
var actions = item.querySelector('.rez-actions');
if (actions) actions.innerHTML = '';
item.classList.remove('checked');
}
}
} catch(e) {
console.error('Potvrzení selhalo pro id=' + id, e);
}
if (i < ids.length - 1) await new Promise(function(res){ setTimeout(res, 200); });
}
vybrane.clear();
updateHromadneBar();
btn.disabled = false;
btn.textContent = '✅ Potvrdit vybrané';
showToast(uspesne === celkem
? '✅ Potvrzeno všech ' + celkem + ' rezervací.'
: '✅ Potvrzeno ' + uspesne + ' z ' + celkem + ' rezervací.');
refreshKalendar();
}
async function confirmRez(id, btn) {
btn.disabled = true;
btn.textContent = '…';
var item = document.getElementById('rez-' + id);
if (item) {
var badge = item.querySelector('.badge');
if (badge) { badge.className = 'badge badge-green'; badge.textContent = 'Potvrzuji…'; }
}
var controller = new AbortController();
var timeout = setTimeout(function() { controller.abort(); }, 10000);
try {
var r = await fetch(API + '/rezervace/' + id, {
method: 'PUT', headers: authHeaders(),
body: JSON.stringify({stav:'potvrzena'}),
signal: controller.signal
});
clearTimeout(timeout);
// Úspěch nebo timeout — v obou případech DB je aktualizovaná
if (item) {
var badge2 = item.querySelector('.badge');
if (badge2) { badge2.className = 'badge badge-green'; badge2.textContent = 'Potvrzeno'; }
var actions = item.querySelector('.rez-actions');
if (actions) actions.innerHTML = '';
var chk = document.getElementById('chk-' + id);
if (chk && chk.parentNode) chk.parentNode.removeChild(chk);
}
showToast('✅ Rezervace potvrzena a zákazník byl informován e-mailem.');
refreshKalendar();
} catch(e) {
clearTimeout(timeout);
if (e.name === 'AbortError') {
// Timeout — DB aktualizovaná, email se odesílá
if (item) {
var badge3 = item.querySelector('.badge');
if (badge3) { badge3.className = 'badge badge-green'; badge3.textContent = 'Potvrzeno'; }
var actions2 = item.querySelector('.rez-actions');
if (actions2) actions2.innerHTML = '';
}
showToast('✅ Rezervace potvrzena. Email se odesílá na pozadí.');
refreshKalendar();
} else {
if (item) {
var badge4 = item.querySelector('.badge');
if (badge4) { badge4.className = 'badge badge-amber'; badge4.textContent = 'Čeká'; }
}
btn.disabled = false;
btn.textContent = 'Potvrdit';
showToast('❌ Chyba připojení.', true);
}
}
}
function showToast(msg, isError) {
var t = document.getElementById('toast-notif');
if (!t) {
t = document.createElement('div');
t.id = 'toast-notif';
t.style.cssText = 'position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:#166534;color:#fff;padding:11px 20px;border-radius:12px;font-size:14px;font-weight:500;z-index:999;max-width:320px;text-align:center;box-shadow:0 4px 12px rgba(0,0,0,.15);transition:opacity .3s';
document.body.appendChild(t);
}
t.style.background = isError ? '#991b1b' : '#166534';
t.textContent = msg;
t.style.opacity = '1';
clearTimeout(t._timer);
t._timer = setTimeout(function(){ t.style.opacity = '0'; }, 3000);
}
function cancelRez(id, btn) {
// Otevři modal pro zadání důvodu
document.getElementById('zruseni-rez-id').value = id;
document.getElementById('zruseni-duvod').value = '';
document.getElementById('zruseni-alert').style.display = 'none';
// Ulož referenci na tlačítko pro pozdější update
window._cancelBtn = btn;
closeModal('modal-rez');
openModal('modal-zruseni');
}
async function provestZruseni() {
var id = document.getElementById('zruseni-rez-id').value;
var duvod = document.getElementById('zruseni-duvod').value.trim();
var saveBtn = document.getElementById('zruseni-save-btn');
saveBtn.disabled = true;
saveBtn.textContent = 'Ruším…';
var controller = new AbortController();
// Po 10s vzdej čekání na odpověď — server stejně dokončí práci
var timeout = setTimeout(function() { controller.abort(); }, 10000);
try {
var r = await fetch(API + '/rezervace/' + id, {
method: 'PUT',
headers: authHeaders(),
body: JSON.stringify({stav: 'zrusena', duvod_zruseni: duvod}),
signal: controller.signal
});
clearTimeout(timeout);
// Jakýkoliv HTTP status = server přijal požadavek
closeModal('modal-zruseni');
var item = document.getElementById('rez-' + id);
if (item) {
item.style.opacity = '.4';
item.querySelector('.badge').className = 'badge badge-red';
item.querySelector('.badge').textContent = 'Zrušeno';
item.querySelector('.rez-actions').innerHTML = '';
}
showToast('Rezervace byla zrušena a zákazník byl informován.');
refreshKalendar();
} catch(e) {
clearTimeout(timeout);
if (e.name === 'AbortError') {
// Timeout — server stále pracuje (odesílá emaily), ale DB je již aktualizovaná
closeModal('modal-zruseni');
var item2 = document.getElementById('rez-' + id);
if (item2) {
item2.style.opacity = '.4';
item2.querySelector('.badge').className = 'badge badge-red';
item2.querySelector('.badge').textContent = 'Zrušeno';
item2.querySelector('.rez-actions').innerHTML = '';
}
showToast('Rezervace zrušena. Email se odesílá na pozadí.');
refreshKalendar();
} else {
var alertEl = document.getElementById('zruseni-alert');
alertEl.textContent = 'Chyba: ' + e.message;
alertEl.style.display = 'block';
saveBtn.disabled = false;
saveBtn.textContent = 'Zrušit rezervaci';
}
}
}
function openRezDetail(r) {
var zakladniCena = r.sluzba_cena && parseFloat(r.sluzba_cena) > 0 ? parseFloat(r.sluzba_cena) : 0;
var doplnkyCena = (r.doplnky||[]).reduce(function(s,d){ return s + parseFloat(d.cena||0); }, 0);
// Pokud existuje ručně upravená cena (cena_upravena), použij ji — jinak součet základní + doplňky
var celkemCena = (r.cena_upravena !== null && r.cena_upravena !== undefined && r.cena_upravena !== '')
? parseFloat(r.cena_upravena)
: (zakladniCena + doplnkyCena);
var doplnkyRowsHtml = (r.doplnky||[]).map(function(d) {
return '<div style="display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid #f3e8ff;font-size:13px">'
+ '<span style="color:#6b7280">+ ' + d.nazev + '</span>'
+ '<span style="font-weight:600;color:#7c3aed">+ ' + Math.round(parseFloat(d.cena)).toLocaleString('cs-CZ') + ' Kč</span>'
+ '</div>';
}).join('');
document.getElementById('modal-rez-content').innerHTML =
'<div style="display:flex;flex-direction:column;gap:0">'
+ detailRow('Jméno', r.jmeno + ' ' + r.prijmeni)
+ detailRow('E-mail', r.email)
+ (r.telefon ? detailRowHtml('Telefon', '<a href="tel:' + r.telefon.replace(/\s/g,'') + '" style="color:#7c3aed;text-decoration:none">' + esc(r.telefon) + '</a>') : '')
+ detailRow('Služba', r.sluzba + (zakladniCena > 0 ? ' (' + Math.round(zakladniCena).toLocaleString('cs-CZ') + ' Kč)' : ''))
+ detailRow('Datum', r.datum)
+ detailRow('Čas', r.cas_od + ' – ' + r.cas_do)
+ doplnkyRowsHtml
+ (r.hodnoceni ? detailRowHtml('Hodnocení', renderStars(r.hodnoceni, 16)) : '')
+ (r.poznamka ? detailRow('Poznámka', r.poznamka) : '')
+ '<div style="display:flex;justify-content:space-between;padding:10px 0;border-bottom:1px solid #f3e8ff;font-size:13px;align-items:center">'
+ '<span style="color:#6b7280;font-weight:600">Celkem</span>'
+ '<div style="display:flex;align-items:center;gap:8px">'
+ '<input type="number" id="rez-cena-input" value="' + Math.round(celkemCena) + '" min="0" style="width:90px;border:2px solid #e9d5ff;border-radius:8px;padding:4px 8px;font-size:13px;text-align:right;outline:none;font-weight:600">'
+ '<span style="font-size:13px;color:#6b7280">Kč</span>'
+ '</div></div>'
+ '</div>';
document.getElementById('_rez_detail_data') && document.getElementById('_rez_detail_data').remove();
var dataEl = document.createElement('input');
dataEl.type = 'hidden'; dataEl.id = '_rez_detail_data';
dataEl.value = JSON.stringify(r);
document.getElementById('modal-rez-content').appendChild(dataEl);
document.getElementById('modal-rez-btns').innerHTML =
'<button class="btn-modal-discard" onclick="closeModal(\'modal-rez\')">Zavřít</button>'
+ (r.stav!=='zrusena' ? '<button class="btn-modal-discard" style="background:#f3e8ff;color:#6b21a8" onclick="openQrZRezervace()">💳 QR</button>' : '')
+ (r.stav!=='zrusena' ? '<button class="btn-modal-discard" style="background:#f3e8ff;color:#6b21a8" onclick="openUpravTermin(' + r.id + ');closeModal(\'modal-rez\')">✏️ Termín</button>' : '')
+ (r.stav!=='zrusena' ? '<button class="btn-modal-save" style="background:#166534;font-size:12px" onclick="ulozitCenuRezervace(' + r.id + ')">💾 Cenu</button>' : '')
+ (r.stav!=='zrusena' ? '<button class="btn-modal-save" style="background:#991b1b;font-size:12px" onclick="cancelRez('+r.id+',this);closeModal(\'modal-rez\')">Zrušit</button>' : '');
openModal('modal-rez');
}
async function ulozitCenuRezervace(id) {
var novaCena = parseFloat(document.getElementById('rez-cena-input').value) || 0;
try {
var r = await fetch(API + '/rezervace/' + id + '/cena', {
method: 'PUT',
headers: authHeaders(),
body: JSON.stringify({ cena: novaCena })
});
if (r.status >= 200 && r.status < 300) {
closeModal('modal-rez');
showToast('✅ Cena uložena a zákazník byl informován.');
refreshKalendar();
loadDnes();
} else {
var d = await r.json().catch(function(){ return {}; });
showToast('Chyba: ' + (d.error || 'Nepodařilo se uložit.'), true);
}
} catch(e) {
showToast('Chyba připojení.', true);
}
}
async function openQrZRezervace() {
// Přečti hodnoty PŘED zavřením modalu
var dataEl = document.getElementById('_rez_detail_data');
var cenaEl = document.getElementById('rez-cena-input');
if (!dataEl || !cenaEl) return;
var r = JSON.parse(dataEl.value);
var novaCena = parseFloat(cenaEl.value) || 0;
var jmeno = r.jmeno + ' ' + r.prijmeni;
closeModal('modal-rez');
switchTab('qr');
// Zajisti že cislo_uctu je načteno
var ucetEl = document.getElementById('set-cislo_uctu');
if (!ucetEl || !ucetEl.value) {
try {
var resp = await fetch(API + '/nastaveni', { headers: authHeaders() });
var data = await resp.json();
if (data.ok && data.data && data.data.cislo_uctu) {
ucetEl.value = data.data.cislo_uctu.hodnota || '';
}
} catch(e) {}
}
// Nastav částku a poznámku a generuj QR přímo
var castkaEl = document.getElementById('qr-castka');
var poznamkaEl = document.getElementById('qr-poznamka');
castkaEl.value = Math.round(novaCena);
poznamkaEl.value = jmeno;
generateQR();
}
// ---- Úprava termínu rezervace ----
var _upravRez = null;
async function openUpravTermin(id) {
// Načti detail rezervace z API
try {
var resp = await fetch(API + '/rezervace/' + id, { headers: authHeaders() });
var data = await resp.json();
if (!data.ok) { showToast('Chyba načítání rezervace.', true); return; }
var r = data.data;
_upravRez = r;
document.getElementById('ut-rez-id').value = r.id;
document.getElementById('ut-sluzba-nazev').textContent = r.sluzba || r.sluzba_nazev || '';
document.getElementById('ut-datum').value = r.datum;
document.getElementById('ut-cas-manual').value = r.cas_od;
document.getElementById('ut-times-wrap').style.display = 'none';
document.getElementById('ut-times-grid').innerHTML = '';
document.getElementById('ut-alert').style.display = 'none';
utNactiCasy(r.datum, r.sluzba_id || '');
openModal('modal-uprav-termin');
} catch(e) {
showToast('Chyba připojení.', true);
}
}
function utDatumChange() {
var datum = document.getElementById('ut-datum').value;
document.getElementById('ut-cas-manual').value = '';
if (datum) utNactiCasy(datum, _upravRez ? (_upravRez.sluzba_id || '') : '');
}
async function utNactiCasy(datum, sluzbaId) {
var wrap = document.getElementById('ut-times-wrap');
wrap.style.display = 'block';
document.getElementById('ut-times-loading').style.display = 'block';
document.getElementById('ut-times-grid').innerHTML = '';
if (!sluzbaId) {
document.getElementById('ut-times-loading').style.display = 'none';
document.getElementById('ut-times-grid').innerHTML = '<p style="font-size:12px;color:#9ca3af;grid-column:1/-1">Zadejte čas ručně níže.</p>';
return;
}
try {
var r = await fetch(API + '/dostupnost?sluzba_id=' + sluzbaId + '&datum=' + datum, { headers: authHeaders() });
var d = await r.json();
document.getElementById('ut-times-loading').style.display = 'none';
var tg = document.getElementById('ut-times-grid');
if (d.blokovano) {
tg.innerHTML = '<p style="font-size:12px;color:#f59e0b;grid-column:1/-1">⚠️ Den je blokován — přesto můžete zadat čas ručně.</p>';
} else if (!d.volne_casy || !d.volne_casy.length) {
tg.innerHTML = '<p style="font-size:12px;color:#9ca3af;grid-column:1/-1">Žádné volné časy — zadejte čas ručně níže.</p>';
} else {
d.volne_casy.forEach(function(t) {
var el = document.createElement('div');
el.style.cssText = 'border:1.5px solid #e9d5ff;border-radius:8px;padding:7px 4px;text-align:center;font-size:12px;cursor:pointer;transition:all .15s';
el.textContent = t;
el.onclick = function() {
document.querySelectorAll('#ut-times-grid div').forEach(function(s){ s.style.background=''; s.style.color=''; s.style.borderColor='#e9d5ff'; });
el.style.background = '#9333ea'; el.style.color = '#fff'; el.style.borderColor = '#9333ea';
document.getElementById('ut-cas-manual').value = t;
};
tg.appendChild(el);
});
}
} catch(e) {
document.getElementById('ut-times-loading').style.display = 'none';
}
}
async function ulozitUpravTermin() {
var id = document.getElementById('ut-rez-id').value;
var datum = document.getElementById('ut-datum').value;
var cas = document.getElementById('ut-cas-manual').value;
var alertEl = document.getElementById('ut-alert');
alertEl.style.display = 'none';
if (!datum || !cas) {
alertEl.className = 'alert error'; alertEl.textContent = 'Vyplňte datum a čas.'; alertEl.style.display = 'block'; return;
}
var btn = document.getElementById('ut-btn-ulozit');
btn.disabled = true; btn.textContent = 'Ukládám…';
var controller = new AbortController();
var timeout = setTimeout(function() { controller.abort(); }, 10000);
try {
var r = await fetch(API + '/rezervace/' + id + '/termin', {
method: 'PUT',
headers: authHeaders(),
body: JSON.stringify({ datum: datum, cas: cas }),
signal: controller.signal
});
clearTimeout(timeout);
if (r.status >= 200 && r.status < 300) {
closeModal('modal-uprav-termin');
showToast('✅ Termín byl změněn a zákazník byl informován.');
refreshKalendar();
loadDnes();
} else {
var d = await r.json().catch(function(){ return {}; });
alertEl.className = 'alert error'; alertEl.textContent = d.error || 'Chyba při ukládání.'; alertEl.style.display = 'block';
}
} catch(e) {
clearTimeout(timeout);
if (e.name === 'AbortError') {
closeModal('modal-uprav-termin');
showToast('✅ Termín byl změněn. Email se odesílá na pozadí.');
refreshKalendar();
loadDnes();
} else {
alertEl.className = 'alert error'; alertEl.textContent = 'Chyba připojení.'; alertEl.style.display = 'block';
}
}
btn.disabled = false; btn.textContent = 'Uložit termín';
}
function detailRow(label, val) {
return '<div style="display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid #f3e8ff;font-size:13px">'
+ '<span style="color:#6b7280">' + label + '</span><span style="font-weight:600">' + esc(String(val)) + '</span></div>';
}
function detailRowHtml(label, html) {
return '<div style="display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid #f3e8ff;font-size:13px">'
+ '<span style="color:#6b7280">' + label + '</span><span style="font-weight:600">' + html + '</span></div>';
}
var kalView = 'mesic';
var mesicDate = new Date();
var seznamDate = new Date();
var allRezervace = {};
var kalFiltr = '';
var selectedMesicDay = null;
function setView(v) {
kalView = v;
document.getElementById('view-mesic').style.display = v === 'mesic' ? 'block' : 'none';
document.getElementById('view-seznam').style.display = v === 'seznam' ? 'block' : 'none';
document.getElementById('vbtn-mesic').classList.toggle('active', v === 'mesic');
document.getElementById('vbtn-seznam').classList.toggle('active', v === 'seznam');
if (v === 'mesic') renderMesic();
else renderSeznam();
}
function applyFilter() {
kalFiltr = document.getElementById('kal-filtr').value;
if (kalView === 'mesic') renderMesic();
else renderSeznam();
}
// ---- MĚSÍČNÍ POHLED ----
var MESICE_LONG = ['Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec'];
async function loadMesicData() {
var y = mesicDate.getFullYear(), m = mesicDate.getMonth();
var od = y + '-' + String(m+1).padStart(2,'0') + '-01';
var do_ = y + '-' + String(m+1).padStart(2,'0') + '-' + String(new Date(y,m+1,0).getDate()).padStart(2,'0');
try {
var r = await fetch(API + '/rezervace?datum_od=' + od + '&datum_do=' + do_, { headers: authHeaders() });
var d = await r.json();
allRezervace = {};
(d.data || []).forEach(function(rez) {
if (!allRezervace[rez.datum]) allRezervace[rez.datum] = [];
allRezervace[rez.datum].push(rez);
});
renderMesic();
} catch(e) {}
}
function renderMesic() {
var y = mesicDate.getFullYear(), m = mesicDate.getMonth();
document.getElementById('mesic-label').textContent = MESICE_LONG[m] + ' ' + y;
var hl = document.getElementById('mesic-hlavicka');
hl.innerHTML = '';
['Po','Út','St','Čt','Pá','So','Ne'].forEach(function(d){
var el = document.createElement('div');
el.style.cssText = 'text-align:center;font-size:10px;color:#9ca3af;padding:2px 0;font-weight:500';
el.textContent = d; hl.appendChild(el);
});
var grid = document.getElementById('mesic-grid');
grid.innerHTML = '';
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 = 'mesic-den mesic-empty'; grid.appendChild(e);
}
var dniVM = new Date(y, m+1, 0).getDate();
var dnes = formatDate(new Date());
for (var d = 1; d <= dniVM; d++) {
var datum = y + '-' + String(m+1).padStart(2,'0') + '-' + String(d).padStart(2,'0');
var el = document.createElement('div');
el.className = 'mesic-den';
if (datum === dnes) el.classList.add('mesic-dnes');
if (selectedMesicDay === datum) el.classList.add('mesic-selected');
var rezy = allRezervace[datum] || [];
var filtrovane = kalFiltr ? rezy.filter(function(r){ return r.stav === kalFiltr; }) : rezy;
var dots = '';
filtrovane.forEach(function(r){
dots += '<div class="dot-' + r.stav + '"></div>';
});
el.innerHTML = '<div style="font-size:12px">' + d + '</div>' + (dots ? '<div class="mesic-den-dots">' + dots + '</div>' : '');
(function(datum_copy, rezy_copy) {
el.onclick = function() {
selectedMesicDay = datum_copy;
renderMesic();
showDenDetail(datum_copy, rezy_copy);
};
})(datum, filtrovane);
grid.appendChild(el);
}
}
function showDenDetail(datum, rezy) {
var czDny = ['Neděle','Pondělí','Úterý','Středa','Čtvrtek','Pátek','Sobota'];
var d = new Date(datum + 'T12:00:00');
document.getElementById('kal-datum-label').textContent = czDny[d.getDay()] + ' ' + d.getDate() + '. ' + (d.getMonth()+1) + '. ' + d.getFullYear();
renderRezList('kal-list', rezy);
}
function mesicChange(dir) {
mesicDate.setMonth(mesicDate.getMonth() + dir);
selectedMesicDay = null;
document.getElementById('kal-datum-label').textContent = 'Vyberte den';
document.getElementById('kal-list').innerHTML = '<div class="empty">Vyberte den v kalendáři</div>';
loadMesicData();
}
// ---- SEZNAM POHLED ----
var seznamOd = new Date();
var seznamTydny = 4;
async function renderSeznam() {
var od = formatDate(seznamOd);
var do_date = new Date(seznamOd);
do_date.setDate(do_date.getDate() + 30);
var do_ = formatDate(do_date);
document.getElementById('seznam-label').textContent = 'Příštích 30 dní';
document.getElementById('kal-datum-label').textContent = 'Rezervace';
document.getElementById('kal-list').innerHTML = '<div class="loading">Načítám…</div>';
var stav_param = kalFiltr ? '&stav=' + kalFiltr : '';
try {
var r = await fetch(API + '/rezervace?datum_od=' + od + '&datum_do=' + do_ + stav_param, { headers: authHeaders() });
var d = await r.json();
renderRezList('kal-list', d.data || []);
} catch(e) {
document.getElementById('kal-list').innerHTML = '<div class="empty">Chyba načítání</div>';
}
}
function seznamChange(dir) {
seznamOd.setDate(seznamOd.getDate() + dir * 30);
renderSeznam();
}
// Refresh kalendáře po změně stavu
function refreshKalendar() {
loadMesicData();
// Pokud je vybraný den, aktualizuj i jeho detail
if (selectedMesicDay) {
var rezy = allRezervace[selectedMesicDay] || [];
var filtr = kalFiltr;
var filtrovane = filtr ? rezy.filter(function(r){ return r.stav === filtr; }) : rezy;
// Nestačí, data ještě nejsou aktuální - loadMesicData to udělá async
// Po načtení dat znovu zobrazíme detail
setTimeout(function() {
var rezy2 = allRezervace[selectedMesicDay] || [];
var filtrovane2 = kalFiltr ? rezy2.filter(function(r){ return r.stav === kalFiltr; }) : rezy2;
showDenDetail(selectedMesicDay, filtrovane2);
}, 800);
}
}
// Inicializace kalendáře
function buildCalStrip() {
mesicDate = new Date();
seznamOd = new Date();
loadMesicData();
}
async function loadKalendarDen(date, el) {
var datum = formatDate(date);
selectedMesicDay = datum;
renderMesic();
var rezy = allRezervace[datum] || [];
showDenDetail(datum, rezy);
}
// Služby
var ikony = {'Střih & styling':'✂️','Barvení':'🎨','Melír':'✨','Péče o vlasy':'💆','Dětský střih':'😊','Foukaná':'💨'};
async function loadSluzbySel() {
var r = await fetch(API + '/sluzby');
var d = await r.json();
var sel = document.getElementById('nr-sluzba');
sel.innerHTML = '<option value="">— vyberte —</option>';
(d.data || []).forEach(function(s) {
var o = document.createElement('option');
o.value = s.id; o.textContent = s.nazev + ' · ' + s.trvani_min + ' min · ' + (s.cena && parseFloat(s.cena) > 0 ? Math.round(s.cena).toLocaleString('cs-CZ') + ' Kč' : 'Individuální');
o.dataset.trvani = s.trvani_min;
sel.appendChild(o);
});
var dnes = new Date(); dnes.setDate(dnes.getDate() + 1);
document.getElementById('nr-datum').min = formatDate(new Date());
}
async function loadSluzby() {
var r = await fetch(API + '/sluzby');
var d = await r.json();
var el = document.getElementById('sluzby-list');
if (!d.data || !d.data.length) { el.innerHTML = '<div class="empty">Žádné služby</div>'; return; }
el.innerHTML = d.data.map(function(s) {
return '<div class="svc-row" data-id="' + s.id + '"><div style="display:flex;align-items:center;gap:8px">'
+ '<span class="drag-handle" title="Přetáhněte pro změnu pořadí">⠿</span>'
+ '<div class="svc-icon">' + (ikony[s.nazev]||'💇') + '</div>'
+ '<div><div class="svc-name">' + esc(s.nazev) + '</div><div class="svc-detail">' + s.trvani_min + ' min</div></div></div>'
+ '<div style="display:flex;align-items:center;gap:8px"><div class="svc-price">' + (s.cena && parseFloat(s.cena) > 0 ? Math.round(s.cena).toLocaleString('cs-CZ') + ' Kč' : 'Individuální') + '</div>'
+ '<button class="btn-icon" onclick=\'openSvcModal(' + JSON.stringify(s).replace(/'/g,"'") + ')\'>✏️</button>'
+ '<button class="btn-icon" style="color:#991b1b" onclick="deleteSvc(' + s.id + ',this)" title="Odebrat">🗑️</button></div>'
+ '<div id="doplnky-wrap-' + s.id + '" style="margin-top:8px"></div>'
+ '</div>';
}).join('');
// Načti doplňky pro každou službu
(d.data || []).forEach(function(s) { loadDoplnky(s.id); });
if (window.Sortable) {
new Sortable(el, {
handle: '.drag-handle', animation: 150,
ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen',
onEnd: function() { ulozitPoradiSluzeb(); }
});
}
}
// ---- Správa doplňků ----
async function loadDoplnky(sluzbaId) {
var wrap = document.getElementById('doplnky-wrap-' + sluzbaId);
if (!wrap) return;
try {
var r = await fetch(API + '/doplnky?sluzba_id=' + sluzbaId, { headers: authHeaders() });
var d = await r.json();
var items = d.data || [];
wrap.innerHTML = '<div style="background:#faf5ff;border-radius:8px;padding:8px 12px;">'
+ '<div style="font-size:11px;font-weight:600;color:#6b21a8;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center">'
+ '<span>Doplňkové služby</span>'
+ '<button class="btn-add" style="font-size:10px;padding:3px 8px" onclick="openDoplnekModal(' + sluzbaId + ',null)">+ Přidat</button>'
+ '</div>'
+ (items.length ? items.map(function(dd) {
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:0.5px solid #e9d5ff">'
+ '<div><div style="font-size:12px;color:#111827">' + esc(dd.nazev) + '</div>'
+ '<div style="font-size:11px;color:#9ca3af">+' + dd.trvani_min + ' min</div></div>'
+ '<div style="display:flex;align-items:center;gap:8px">'
+ '<span style="font-size:12px;font-weight:600;color:#7c3aed">+' + Math.round(dd.cena).toLocaleString('cs-CZ') + ' Kč</span>'
+ '<button class="btn-icon" style="width:24px;height:24px;font-size:11px" onclick="openDoplnekModal(' + sluzbaId + ',' + JSON.stringify(dd).replace(/"/g,'"') + ')">✏️</button>'
+ '<button class="btn-del" onclick="deleteDoplnek(' + dd.id + ',' + sluzbaId + ')">🗑️</button>'
+ '</div></div>';
}).join('') : '<div style="font-size:11px;color:#9ca3af;padding:4px 0">Žádné doplňky</div>')
+ '</div>';
} catch(e) {}
}
function openDoplnekModal(sluzbaId, dd) {
document.getElementById('doplnek-sluzba-id').value = sluzbaId;
document.getElementById('doplnek-id').value = dd ? dd.id : '';
document.getElementById('doplnek-nazev').value = dd ? dd.nazev : '';
document.getElementById('doplnek-cena').value = dd ? dd.cena : '';
document.getElementById('doplnek-min').value = dd ? dd.trvani_min : '';
document.getElementById('modal-doplnek-title').textContent = dd ? 'Upravit doplněk' : 'Nový doplněk';
document.getElementById('doplnek-alert').style.display = 'none';
openModal('modal-doplnek');
}
async function saveDoplnek() {
var sluzbaId = document.getElementById('doplnek-sluzba-id').value;
var id = document.getElementById('doplnek-id').value;
var nazev = document.getElementById('doplnek-nazev').value.trim();
var cena = parseFloat(document.getElementById('doplnek-cena').value) || 0;
var min = parseInt(document.getElementById('doplnek-min').value) || 0;
var alertEl = document.getElementById('doplnek-alert');
alertEl.style.display = 'none';
if (!nazev) { alertEl.className = 'alert error'; alertEl.textContent = 'Vyplňte název.'; alertEl.style.display = 'block'; return; }
var url = id ? API + '/doplnky/' + id : API + '/doplnky';
var method = id ? 'PUT' : 'POST';
var body = { nazev: nazev, cena: cena, trvani_min: min };
if (!id) body.sluzba_id = parseInt(sluzbaId);
try {
var r = await fetch(url, { method: method, headers: authHeaders(), body: JSON.stringify(body) });
var d = await r.json();
if (d.ok) {
closeModal('modal-doplnek');
loadDoplnky(parseInt(sluzbaId));
showToast('Doplněk uložen.');
} else {
alertEl.className = 'alert error'; alertEl.textContent = d.error || 'Chyba.'; alertEl.style.display = 'block';
}
} catch(e) {
alertEl.className = 'alert error'; alertEl.textContent = 'Chyba připojení.'; alertEl.style.display = 'block';
}
}
async function deleteDoplnek(id, sluzbaId) {
if (!confirm('Smazat doplněk?')) return;
var r = await fetch(API + '/doplnky/' + id, { method: 'DELETE', headers: authHeaders() });
var d = await r.json();
if (d.ok) { loadDoplnky(sluzbaId); showToast('Doplněk smazán.'); }
}
async function loadBlokace() {
var r = await fetch(API + '/admin/blokovane', { headers: authHeaders() });
var d = await r.json();
var el = document.getElementById('blok-list');
if (!d.data || !d.data.length) { el.innerHTML = '<div class="empty">Žádné blokace</div>'; return; }
el.innerHTML = d.data.map(function(b) {
var range = b.datum_od === b.datum_do ? b.datum_od : b.datum_od + ' – ' + b.datum_do;
return '<div class="blok-row"><div><div class="blok-date">' + range + '</div><div class="blok-reason">' + esc(b.duvod||'') + '</div></div>'
+ '<button class="btn-del" onclick="deleteBlok(' + b.id + ',this)">🗑️</button></div>';
}).join('');
}
async function deleteBlok(id, btn) {
if (!confirm('Smazat blokaci?')) return;
btn.disabled = true;
var r = await fetch(API + '/admin/blokovane/' + id, { method:'DELETE', headers: authHeaders() });
var d = await r.json();
if (d.ok) loadBlokace();
}
async function deleteSvc(id, btn) {
if (!confirm('Opravdu odebrat tuto službu? Existující rezervace nebudou smazány.')) return;
btn.disabled = true;
var r = await fetch(API + '/sluzby/' + id, { method:'DELETE', headers: authHeaders() });
var d = await r.json();
if (d.ok) { loadSluzby(); showToast('Služba byla odebrána.'); }
else { btn.disabled = false; showToast('Chyba: ' + (d.error || 'Nepodařilo se smazat.'), true); }
}
var kartotek_timeout;
var kpick_timeout;
var kpickData = [];
async function kpickSearch(q) {
var res = document.getElementById('kpick-results');
clearTimeout(kpick_timeout);
if (!q || q.length < 1) { res.innerHTML = '<div class="empty">Začněte psát pro vyhledání</div>'; return; }
kpick_timeout = setTimeout(async function() {
try {
var r = await fetch(API + '/zakaznici?q=' + encodeURIComponent(q), { headers: authHeaders() });
var d = await r.json();
var list = d.data || [];
if (!list.length) { res.innerHTML = '<div class="empty">Žádné výsledky</div>'; return; }
// Uložíme výsledky do globální proměnné a renderujeme s index
kpickData = list;
res.innerHTML = list.map(function(z, idx) {
return '<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid #f3e8ff;cursor:pointer" onclick="kpickVybrat(' + idx + ')">'
+ '<div class="zak-avatar" style="width:32px;height:32px;font-size:12px">' + esc(((z.jmeno||'')[0]+(z.prijmeni||'')[0]).toUpperCase()) + '</div>'
+ '<div><div style="font-size:13px;font-weight:600">' + esc(z.prijmeni) + ' ' + esc(z.jmeno) + '</div>'
+ '<div style="font-size:11px;color:#9ca3af">' + esc(z.email) + (z.telefon ? ' · ' + esc(z.telefon) : '') + '</div></div>'
+ '</div>';
}).join('');
} catch(e) { res.innerHTML = '<div class="empty">Chyba načítání</div>'; }
}, 250);
}
function kpickVybrat(idx) {
var z = kpickData[idx];
if (!z) return;
document.getElementById('nr-jmeno').value = z.jmeno || '';
document.getElementById('nr-prijmeni').value = z.prijmeni || '';
document.getElementById('nr-email').value = z.email || '';
document.getElementById('nr-telefon').value = z.telefon || '';
closeModal('modal-kartotek-pick');
document.getElementById('kpick-search').value = '';
document.getElementById('kpick-results').innerHTML = '<div class="empty">Začněte psát pro vyhledání</div>';
}
async function ulozitPoradiSluzeb() {
var rows = document.querySelectorAll('#sluzby-list .svc-row[data-id]');
var poradi = Array.from(rows).map(function(r, i) {
return { id: parseInt(r.dataset.id), poradi: i + 1 };
});
try {
await fetch(API + '/sluzby/poradi', {
method: 'PUT',
headers: authHeaders(),
body: JSON.stringify({ poradi: poradi })
});
} catch(e) { console.error('Chyba uložení pořadí', e); }
}
function openSvcModal(s) {
document.getElementById('modal-svc-title').textContent = s ? 'Upravit službu' : 'Nová služba';
document.getElementById('svc-id').value = s ? s.id : '';
document.getElementById('svc-nazev').value = s ? s.nazev : '';
document.getElementById('svc-trvani').value = s ? s.trvani_min : '';
document.getElementById('svc-cena').value = (s && s.cena && parseFloat(s.cena) > 0) ? Math.round(s.cena) : '';
openModal('modal-svc');
}
async function saveSvc() {
var id = document.getElementById('svc-id').value;
var nazev = document.getElementById('svc-nazev').value.trim();
var trvani = parseInt(document.getElementById('svc-trvani').value);
var cenaRaw = document.getElementById('svc-cena').value.trim();
var cena = cenaRaw !== '' ? parseFloat(cenaRaw) : null;
if (!nazev) { alert('Vyplňte název služby.'); return; }
if (!trvani || trvani < 1) { alert('Vyplňte trvání služby.'); return; }
var payload = { nazev: nazev, trvani_min: trvani, cena: cena };
var url = id ? API + '/sluzby/' + id : API + '/sluzby';
var method = id ? 'PUT' : 'POST';
try {
var r = await fetch(url, { method: method, headers: authHeaders(), body: JSON.stringify(payload) });
var d = await r.json();
if (d.ok) { closeModal('modal-svc'); loadSluzby(); showToast('Služba uložena.'); }
else { alert(d.error || 'Chyba při ukládání: ' + JSON.stringify(d)); }
} catch(e) { alert('Síťová chyba: ' + e.message); }
}
function openBlokModal() {
var today = formatDate(new Date());
document.getElementById('blok-od').value = today;
document.getElementById('blok-do').value = today;
document.getElementById('blok-duvod').value = '';
openModal('modal-blok');
}
async function saveBlok() {
var payload = { datum_od: document.getElementById('blok-od').value, datum_do: document.getElementById('blok-do').value, duvod: document.getElementById('blok-duvod').value };
var r = await fetch(API + '/admin/blokovane', { method:'POST', headers: authHeaders(), body: JSON.stringify(payload) });
var d = await r.json();
if (d.ok) { closeModal('modal-blok'); loadBlokace(); }
else alert(d.error || 'Chyba při ukládání.');
}
// Nastavení
var DAYS_DEF = [{l:'Po',v:1},{l:'Út',v:2},{l:'St',v:3},{l:'Čt',v:4},{l:'Pá',v:5},{l:'So',v:6},{l:'Ne',v:7}];
function buildDaysWrap(active) {
activeDays = active;
var wrap = document.getElementById('daysWrap');
wrap.innerHTML = '';
DAYS_DEF.forEach(function(d) {
var el = document.createElement('div');
el.className = 'day-pill' + (active.indexOf(d.v)>-1?' on':'');
el.textContent = d.l;
el.onclick = function() {
var idx = activeDays.indexOf(d.v);
if (idx>-1) { activeDays.splice(idx,1); el.classList.remove('on'); }
else { activeDays.push(d.v); activeDays.sort(); el.classList.add('on'); }
document.getElementById('set-pracovni_dny').value = activeDays.join(',');
};
wrap.appendChild(el);
});
document.getElementById('set-pracovni_dny').value = active.join(',');
}
var DNY_NASTAVENI = [{l:'Pondělí',v:1},{l:'Úterý',v:2},{l:'Středa',v:3},{l:'Čtvrtek',v:4},{l:'Pátek',v:5},{l:'Sobota',v:6},{l:'Neděle',v:7}];
async function loadSettings() {
var r = await fetch(API + '/nastaveni', { headers: authHeaders() });
var d = await r.json();
if (!d.ok) return;
var keys = ['salon_nazev','salon_telefon','salon_email','salon_adresa','cislo_uctu','slot_minut','rezervace_min_predem','rezervace_storno_hod','pripominka_hodin','hodnoceni_hodin_po'];
keys.forEach(function(k) {
var el = document.getElementById('set-' + k);
if (el && d.data[k]) el.value = d.data[k].hodnota;
});
// Připomenutí - toggle switch
var pripZapnuta = d.data['pripominka_zapnuta'] && d.data['pripominka_zapnuta'].hodnota === '1';
document.getElementById('set-pripominka_zapnuta').checked = pripZapnuta;
document.getElementById('pripominka-toggle').style.background = pripZapnuta ? '#9333ea' : '#e5e7eb';
document.getElementById('pripominka-knob').style.left = pripZapnuta ? '23px' : '3px';
document.getElementById('pripominka-hod-row').style.display = pripZapnuta ? 'flex' : 'none';
if (!d.data['pripominka_hodin']) document.getElementById('set-pripominka_hodin').value = 24;
// Hodnocení - žádost o hodnocení
var hodZadost = d.data['hodnoceni_zadost_zapnuta'] && d.data['hodnoceni_zadost_zapnuta'].hodnota === '1';
document.getElementById('set-hodnoceni_zadost_zapnuta').checked = hodZadost;
document.getElementById('hodnoceni-zadost-toggle').style.background = hodZadost ? '#9333ea' : '#e5e7eb';
document.getElementById('hodnoceni-zadost-knob').style.left = hodZadost ? '23px' : '3px';
document.getElementById('hodnoceni-hod-row').style.display = hodZadost ? 'flex' : 'none';
if (!d.data['hodnoceni_hodin_po']) document.getElementById('set-hodnoceni_hodin_po').value = 2;
// Hodnocení - veřejné zobrazení
var hodVerejne = d.data['hodnoceni_verejne_zapnuta'] && d.data['hodnoceni_verejne_zapnuta'].hodnota === '1';
document.getElementById('set-hodnoceni_verejne_zapnuta').checked = hodVerejne;
document.getElementById('hodnoceni-verejne-toggle').style.background = hodVerejne ? '#9333ea' : '#e5e7eb';
document.getElementById('hodnoceni-verejne-knob').style.left = hodVerejne ? '23px' : '3px';
// Aktualizuj popis účtu v QR záložce
var ucetEl = document.getElementById('set-cislo_uctu');
var popisEl = document.getElementById('qr-ucet-popis');
if (ucetEl && popisEl) {
var ucetVal = (d.data['cislo_uctu'] && d.data['cislo_uctu'].hodnota) ? d.data['cislo_uctu'].hodnota : '—';
popisEl.textContent = 'Účet: ' + ucetVal + ' · VS: dnešní datum (automaticky)';
}
// Načti pracovní dobu per den
try {
var r2 = await fetch(API + '/pracovni-doba', { headers: authHeaders() });
var d2 = await r2.json();
buildPracovniDobaWrap(d2.ok ? d2.data : {});
} catch(e) { buildPracovniDobaWrap({}); }
}
function buildPracovniDobaWrap(data) {
var wrap = document.getElementById('pracovni-doba-wrap');
if (!wrap) return;
wrap.innerHTML = DNY_NASTAVENI.map(function(den) {
var db = data[den.v] || {aktivni: den.v <= 6, od_min: 480, do_min: 1020};
var checked = db.aktivni ? 'checked' : '';
var od = minNaCas(db.od_min || 480);
var doCas = minNaCas(db.do_min || 1020);
return '<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:0.5px solid var(--g200)">'
+ '<input type="checkbox" id="pd-aktivni-' + den.v + '" ' + checked + ' style="accent-color:#9333ea;width:16px;height:16px;flex-shrink:0" onchange="toggleDenDoba(' + den.v + ',this.checked)">'
+ '<span style="font-size:13px;color:#374151;min-width:72px">' + den.l + '</span>'
+ '<div id="pd-doba-' + den.v + '" style="display:' + (db.aktivni ? 'flex' : 'none') + ';align-items:center;gap:6px;flex:1">'
+ '<input type="time" id="pd-od-' + den.v + '" value="' + od + '" style="border:1.5px solid #e9d5ff;border-radius:6px;padding:4px 8px;font-size:13px;color:#111827;background:#fff">'
+ '<span style="font-size:12px;color:#9ca3af">–</span>'
+ '<input type="time" id="pd-do-' + den.v + '" value="' + doCas + '" style="border:1.5px solid #e9d5ff;border-radius:6px;padding:4px 8px;font-size:13px;color:#111827;background:#fff">'
+ '</div>'
+ '<span id="pd-zavreno-' + den.v + '" style="display:' + (db.aktivni ? 'none' : 'block') + ';font-size:12px;color:#9ca3af">Zavřeno</span>'
+ '</div>';
}).join('');
}
function toggleDenDoba(den, aktivni) {
document.getElementById('pd-doba-' + den).style.display = aktivni ? 'flex' : 'none';
document.getElementById('pd-zavreno-' + den).style.display = aktivni ? 'none' : 'block';
}
async function saveSettings() {
var keys = ['salon_nazev','salon_telefon','salon_email','salon_adresa','cislo_uctu','slot_minut','rezervace_min_predem','rezervace_storno_hod','pripominka_hodin','hodnoceni_hodin_po'];
var payload = {};
keys.forEach(function(k) {
var el = document.getElementById('set-' + k);
if (el) payload[k] = el.value;
});
payload['pripominka_zapnuta'] = document.getElementById('set-pripominka_zapnuta').checked ? '1' : '0';
payload['hodnoceni_zadost_zapnuta'] = document.getElementById('set-hodnoceni_zadost_zapnuta').checked ? '1' : '0';
payload['hodnoceni_verejne_zapnuta'] = document.getElementById('set-hodnoceni_verejne_zapnuta').checked ? '1' : '0';
var alertEl = document.getElementById('set-alert');
alertEl.style.display = 'none';
// Uložit obecná nastavení
var r1 = await fetch(API + '/nastaveni', { method:'PUT', headers: authHeaders(), body: JSON.stringify(payload) });
var d1 = await r1.json();
// Uložit pracovní dobu per den
var dny = {};
DNY_NASTAVENI.forEach(function(d) {
var aktivni = document.getElementById('pd-aktivni-' + d.v);
var od = document.getElementById('pd-od-' + d.v);
var doCas = document.getElementById('pd-do-' + d.v);
if (aktivni) {
dny[d.v] = {
aktivni: aktivni.checked ? 1 : 0,
od_min: casNaMin(od ? od.value : '08:00'),
do_min: casNaMin(doCas ? doCas.value : '17:00')
};
}
});
var r2 = await fetch(API + '/pracovni-doba', { method:'PUT', headers: authHeaders(), body: JSON.stringify({dny: dny}) });
var d2 = await r2.json();
alertEl.style.display = 'block';
if (d1.ok && d2.ok) { alertEl.className='alert success'; alertEl.textContent='✅ Nastavení uloženo.'; }
else { alertEl.className='alert error'; alertEl.textContent = (!d1.ok ? d1.error : d2.error) || 'Chyba při ukládání.'; }
setTimeout(function(){ alertEl.style.display='none'; }, 3000);
}
function togglePripominka(el) {
document.getElementById('pripominka-hod-row').style.display = el.checked ? 'flex' : 'none';
document.getElementById('pripominka-toggle').style.background = el.checked ? '#9333ea' : '#e5e7eb';
document.getElementById('pripominka-knob').style.left = el.checked ? '23px' : '3px';
}
function toggleSwitch(el, rowId, toggleId, knobId) {
document.getElementById(rowId).style.display = el.checked ? 'flex' : 'none';
document.getElementById(toggleId).style.background = el.checked ? '#9333ea' : '#e5e7eb';
document.getElementById(knobId).style.left = el.checked ? '23px' : '3px';
}
function toggleSwitchSimple(el, toggleId, knobId) {
document.getElementById(toggleId).style.background = el.checked ? '#9333ea' : '#e5e7eb';
document.getElementById(knobId).style.left = el.checked ? '23px' : '3px';
}
function casNaMin(cas) {
var parts = cas.split(':');
return parseInt(parts[0]) * 60 + parseInt(parts[1] || 0);
}
function minNaCas(min) {
return String(Math.floor(min/60)).padStart(2,'0') + ':' + String(min%60).padStart(2,'0');
}
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
function formatDate(d) { return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
function renderStars(hodnoceni, size) {
size = size || 13;
var html = '<span style="font-size:' + size + 'px">';
for (var i = 1; i <= 5; i++) {
var color = i <= hodnoceni ? '#f59e0b' : '#d1d5db';
html += '<span style="color:' + color + '">★</span>';
}
html += '</span>';
return html;
}
async function changeHeslo() {
var stare = document.getElementById('heslo-stare').value;
var nove = document.getElementById('heslo-nove').value;
var potvrzeni = document.getElementById('heslo-potvrzeni').value;
var alertEl = document.getElementById('heslo-alert');
alertEl.style.display = 'none';
if (!stare || !nove || !potvrzeni) {
alertEl.className = 'alert error'; alertEl.textContent = 'Vyplňte všechna pole.'; alertEl.style.display = 'block'; return;
}
if (nove.length < 8) {
alertEl.className = 'alert error'; alertEl.textContent = 'Nové heslo musí mít alespoň 8 znaků.'; alertEl.style.display = 'block'; return;
}
if (nove !== potvrzeni) {
alertEl.className = 'alert error'; alertEl.textContent = 'Nové heslo a potvrzení se neshodují.'; alertEl.style.display = 'block'; return;
}
try {
var r = await fetch(API + '/heslo', {
method: 'POST',
headers: authHeaders(),
body: JSON.stringify({ stare_heslo: stare, nove_heslo: nove, potvrzeni: potvrzeni })
});
var d = await r.json();
if (d.ok) {
alertEl.className = 'alert success'; alertEl.textContent = '✅ Heslo bylo úspěšně změněno.'; alertEl.style.display = 'block';
setTimeout(function() { closeModal('modal-heslo'); resetHesloForm(); }, 2000);
} else {
alertEl.className = 'alert error'; alertEl.textContent = d.error || 'Chyba při změně hesla.'; alertEl.style.display = 'block';
}
} catch(e) {
alertEl.className = 'alert error'; alertEl.textContent = 'Chyba připojení.'; alertEl.style.display = 'block';
}
}
// ---- KARTOTÉKA ----
var kartotekaData = [];
async function loadKartoteka(q) {
var url = API + '/zakaznici' + (q ? '?q=' + encodeURIComponent(q) : '');
var el = document.getElementById('kartoteka-list');
try {
var r = await fetch(url, { headers: authHeaders() });
var d = await r.json();
if (!d.ok) {
// Tabulka neexistuje – sync
await fetch(API + '/zakaznici/sync', { method:'POST', headers: authHeaders() });
return loadKartoteka(q);
}
kartotekaData = d.data || [];
renderKartoteka(kartotekaData);
} catch(e) { el.innerHTML = '<div class="empty">Chyba načítání</div>'; }
}
function renderKartoteka(list) {
var el = document.getElementById('kartoteka-list');
if (!list.length) { el.innerHTML = '<div class="empty">Žádní zákazníci</div>'; return; }
el.innerHTML = list.map(function(z) {
var initials = (z.jmeno[0] || '') + (z.prijmeni[0] || '');
var stitky = z.stitky ? z.stitky.split(',').filter(Boolean).map(function(s){
return '<span class="stitek">' + esc(s.trim()) + '</span>';
}).join('') : '';
var posledni = z.posledni_navsteva ? '<span style="font-size:11px;color:#9ca3af">Naposledy: ' + z.posledni_navsteva + '</span>' : '';
return '<div class="zak-card" id="zak-' + z.id + '">'
+ '<div style="display:flex;align-items:center;gap:10px" onclick="openZakaznik(' + z.id + ')">'
+ '<div class="zak-avatar">' + esc(initials.toUpperCase()) + '</div>'
+ '<div style="flex:1;min-width:0">'
+ '<div style="display:flex;align-items:center;justify-content:space-between;gap:6px">'
+ '<div class="zak-name">' + esc(z.prijmeni) + ' ' + esc(z.jmeno) + '</div>'
+ '<span class="zak-stat">' + (z.navstevy || z.pocet_rezervaci || 0) + ' návštěv</span>'
+ '</div>'
+ '<div class="zak-sub">' + esc(z.email) + (z.telefon ? ' · ' + esc(z.telefon) : '') + '</div>'
+ (stitky ? '<div style="margin-top:4px">' + stitky + '</div>' : '')
+ (posledni ? '<div style="margin-top:2px">' + posledni + '</div>' : '')
+ '</div></div>'
+ '<div style="display:flex;justify-content:flex-end;padding-top:6px;border-top:1px solid #f3e8ff;margin-top:6px">'
+ '<button class="btn-sm" style="background:#fee2e2;color:#991b1b" onclick="event.stopPropagation();smazatZakaznika(' + z.id + ',\'' + esc(z.jmeno) + ' ' + esc(z.prijmeni) + '\')">🗑 Smazat</button>'
+ '</div></div>';
}).join('');
}
async function smazatZakaznika(id, jmeno) {
if (!confirm('Smazat zákazníka ' + jmeno + ' z kartotéky?\nTato akce je nevratná.')) return;
try {
var r = await fetch(API + '/zakaznici/' + id, { method: 'DELETE', headers: authHeaders() });
var d = await r.json();
if (d.ok) {
var el = document.getElementById('zak-' + id);
if (el) el.remove();
showToast('Zákazník ' + jmeno + ' byl smazán.');
} else {
showToast('Chyba: ' + (d.error || 'Nepodařilo se smazat.'), true);
}
} catch(e) {
showToast('Chyba připojení.', true);
}
}
var searchTimeout;
function searchKartoteka(q) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function(){ loadKartoteka(q); }, 300);
}
async function openZakaznik(id) {
var r = await fetch(API + '/zakaznici/' + id, { headers: authHeaders() });
var d = await r.json();
if (!d.ok) return;
var z = d.zakaznik, stats = d.stats, rezervace = d.rezervace || [];
var stitky = (z.stitky || '').split(',').filter(Boolean);
var stitkyHtml = stitky.map(function(s,i){
return '<span class="stitek">' + esc(s.trim()) + ' <span class="stitek-del" onclick="removeStitok(' + id + ',' + i + ')">✕</span></span>';
}).join('');
var rezHtml = rezervace.length ? rezervace.map(function(rez){
var badgeCls = rez.stav==='potvrzena'?'badge-green':rez.stav==='zrusena'?'badge-red':'badge-amber';
var badgeTxt = rez.stav==='potvrzena'?'Potvrzeno':rez.stav==='zrusena'?'Zrušeno':'Čeká';
return '<div style="display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid #f3e8ff;font-size:12px">'
+ '<div style="min-width:80px;color:#6b7280">' + rez.datum + '</div>'
+ '<div style="flex:1">' + esc(rez.sluzba)
+ (function() {
var doplnky = rez.doplnky || [];
if (doplnky.length > 0) {
return '<br><span style="color:#9333ea;font-size:11px">+ ' + doplnky.map(function(d){ return d.nazev; }).join(', ') + '</span>';
}
return '';
})()
+ '<br><span style="color:#9ca3af">' + rez.cas_od + ' · '
+ (function() {
var doplnky = rez.doplnky || [];
var celkem;
if (rez.cena_upravena !== null && rez.cena_upravena !== undefined && rez.cena_upravena !== '') {
celkem = parseFloat(rez.cena_upravena) || 0;
} else {
var extra = doplnky.reduce(function(s,d){ return s + parseFloat(d.cena||0); }, 0);
celkem = (parseFloat(rez.cena)||0) + extra;
}
return celkem > 0 ? Math.round(celkem).toLocaleString('cs-CZ') + ' Kč' : 'Individuální';
})()
+ (rez.hodnoceni ? ' · ' + renderStars(rez.hodnoceni, 11) : '')
+ '</span></div>'
+ '<span class="badge ' + badgeCls + '" style="font-size:10px">' + badgeTxt + '</span>'
+ '</div>';
}).join('') : '<div class="empty" style="padding:8px 0">Žádné rezervace</div>';
document.getElementById('modal-zak-content').innerHTML =
'<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px">'
+ '<div class="zak-avatar" style="width:48px;height:48px;font-size:17px">' + esc((z.jmeno[0]||'')+(z.prijmeni[0]||'')).toUpperCase() + '</div>'
+ '<div><div style="font-size:16px;font-weight:700">' + esc(z.prijmeni) + ' ' + esc(z.jmeno) + '</div>'
+ '<div style="font-size:12px;color:#9ca3af">' + esc(z.email) + '</div>'
+ (stats.pocet_hodnoceni > 0 ? '<div style="margin-top:3px;display:flex;align-items:center;gap:5px">' + renderStars(Math.round(stats.prumer_hodnoceni), 13) + '<span style="font-size:11px;color:#9ca3af">' + stats.prumer_hodnoceni.toFixed(1) + ' (' + stats.pocet_hodnoceni + ')</span></div>' : '')
+ '</div></div>'
+ '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:14px">'
+ '<div class="stat-card"><div class="stat-val" style="font-size:18px">' + (stats.navstevy||0) + '</div><div class="stat-label">návštěv</div></div>'
+ '<div class="stat-card"><div class="stat-val" style="font-size:14px">' + Math.round(stats.celkem_utraceno||0).toLocaleString('cs-CZ') + ' Kč</div><div class="stat-label">celkem</div></div>'
+ '<div class="stat-card"><div class="stat-val" style="font-size:13px">' + (stats.posledni||'—') + '</div><div class="stat-label">naposledy</div></div>'
+ '</div>'
+ '<label class="modal-label">Telefon</label>'
+ '<input class="modal-input" id="zak-telefon" value="' + esc(z.telefon||'') + '" placeholder="+420...">'
+ '<label class="modal-label">Štítky</label>'
+ '<div style="margin-bottom:8px" id="zak-stitky-wrap">' + (stitkyHtml||'<span style="font-size:12px;color:#9ca3af">Žádné štítky</span>') + '</div>'
+ '<div style="display:flex;gap:6px;margin-bottom:10px">'
+ '<input class="modal-input" id="zak-novy-stitek" placeholder="Nový štítek (VIP, Alergie…)" style="margin:0;flex:1" onkeydown="if(event.key=="Enter")addStitok(' + id + ')">'
+ '<button class="btn-modal-save" style="padding:0 12px;font-size:12px" onclick="addStitok(' + id + ')">+</button>'
+ '</div>'
+ '<label class="modal-label">Interní poznámka</label>'
+ '<textarea class="modal-input" id="zak-poznamka" style="height:60px;resize:none;line-height:1.5">' + esc(z.poznamka||'') + '</textarea>'
+ '<label class="modal-label" style="margin-top:10px">Historie rezervací</label>'
+ '<div style="max-height:200px;overflow-y:auto">' + rezHtml + '</div>';
document.getElementById('modal-zak-id').value = id;
document.getElementById('modal-zak-stitky').value = z.stitky || '';
openModal('modal-zak');
}
function smazatZakModal() {
var id = document.getElementById('modal-zak-id').value;
var nazev = document.querySelector('#modal-zak-content .zak-jmeno')?.textContent || 'zákazníka';
closeModal('modal-zak');
smazatZakaznika(id, nazev);
}
async function saveZakaznik() {
var id = document.getElementById('modal-zak-id').value;
var payload = {
poznamka: document.getElementById('zak-poznamka').value,
stitky: document.getElementById('modal-zak-stitky').value,
telefon: document.getElementById('zak-telefon').value,
};
var r = await fetch(API + '/zakaznici/' + id, { method:'PUT', headers: authHeaders(), body: JSON.stringify(payload) });
var d = await r.json();
if (d.ok) { closeModal('modal-zak'); loadKartoteka(document.getElementById('kartotek-search').value); }
}
function addStitok(id) {
var inp = document.getElementById('zak-novy-stitek');
var val = inp.value.trim();
if (!val) return;
var current = document.getElementById('modal-zak-stitky').value;
var arr = current ? current.split(',').filter(Boolean) : [];
if (arr.indexOf(val) === -1) arr.push(val);
document.getElementById('modal-zak-stitky').value = arr.join(',');
inp.value = '';
// Aktualizuj zobrazení
var wrap = document.getElementById('zak-stitky-wrap');
wrap.innerHTML = arr.map(function(s,i){
return '<span class="stitek">' + esc(s.trim()) + ' <span class="stitek-del" onclick="removeStitok(' + id + ',' + i + ')">✕</span></span>';
}).join('');
}
function removeStitok(zakId, idx) {
var current = document.getElementById('modal-zak-stitky').value;
var arr = current.split(',').filter(Boolean);
arr.splice(idx, 1);
document.getElementById('modal-zak-stitky').value = arr.join(',');
var wrap = document.getElementById('zak-stitky-wrap');
wrap.innerHTML = arr.length ? arr.map(function(s,i){
return '<span class="stitek">' + esc(s.trim()) + ' <span class="stitek-del" onclick="removeStitok(' + zakId + ',' + i + ')">✕</span></span>';
}).join('') : '<span style="font-size:12px;color:#9ca3af">Žádné štítky</span>';
}
// ---- QR PLATBA ----
var qrInstance = null;
function getVS() {
var d = new Date();
return d.getFullYear().toString() +
String(d.getMonth()+1).padStart(2,'0') +
String(d.getDate()).padStart(2,'0');
}
function getIBAN() {
var ucet = (document.getElementById('set-cislo_uctu') || {}).value || '';
if (!ucet) return '';
var parts = ucet.split('/');
var kodBanky = (parts[1] || '0800').replace(/\s/g, '');
var cisloParts = parts[0].replace(/\s/g, '').split('-');
var predcisli = cisloParts.length > 1 ? cisloParts[0] : '0';
var zakladni = cisloParts.length > 1 ? cisloParts[1] : cisloParts[0];
// CZ IBAN: kod(4) + predcisli(6) + zakladni(10)
predcisli = predcisli.padStart(6, '0');
zakladni = zakladni.padStart(10, '0');
var bban = kodBanky + predcisli + zakladni;
// Přidej CZ00 na konec pro MOD97
var raw = bban + '123500'; // CZ=1235, 00=placeholder
// MOD97 přes BigInt pro přesnost
var mod;
try {
mod = Number(BigInt(raw) % BigInt(97));
} catch(e) {
// Fallback pro starší prohlížeče
mod = 0;
for (var i = 0; i < raw.length; i++) { mod = (mod * 10 + parseInt(raw[i])) % 97; }
}
var check = String(98 - mod).padStart(2, '0');
return 'CZ' + check + bban;
}
function removeDiacritics(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-zA-Z0-9 ]/g, '');
}
function buildSPD(castka, poznamka) {
var iban = getIBAN();
if (!iban) return '';
var vs = getVS();
// Minimální validní SPD*1.0 formát dle české normy
var str = 'SPD*1.0*ACC:' + iban + '*AM:' + parseFloat(castka).toFixed(2) + '*CC:CZK';
str += '*X-VS:' + vs;
if (poznamka) str += '*MSG:' + removeDiacritics(poznamka).substring(0, 60);
return str;
}
function generateQR() {
var castka = document.getElementById('qr-castka').value;
var poznamka = document.getElementById('qr-poznamka').value.trim();
var wrap = document.getElementById('qr-wrap');
var placeholder = document.getElementById('qr-placeholder');
if (!castka || parseFloat(castka) <= 0) {
wrap.style.display = 'none';
placeholder.style.display = 'block';
return;
}
var spd = buildSPD(castka, poznamka);
var container = document.getElementById('qr-code');
container.innerHTML = '';
console.log('SPD:', spd);
if (typeof QRCode !== 'undefined') {
new QRCode(container, {
text: spd,
width: 200,
height: 200,
colorDark: '#111827',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.L
});
}
var vs = getVS();
var ucet_display = (document.getElementById('set-cislo_uctu') || {}).value || '—';
var info = ucet_display + ' · ' + parseFloat(castka).toLocaleString('cs-CZ') + ' Kč · VS: ' + vs;
if (poznamka) info += ' · ' + poznamka;
document.getElementById('qr-info').textContent = info;
wrap.style.display = 'block';
placeholder.style.display = 'none';
}
function downloadQR() {
var canvas = document.querySelector('#qr-code canvas');
if (!canvas) return;
var castka = document.getElementById('qr-castka').value;
var link = document.createElement('a');
link.download = 'qr-platba-' + castka + 'kc-' + getVS() + '.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
var nrSluzbaTrvani = 0;
var nrVybranyCas = '';
function nrSluzbaChange() {
var sel = document.getElementById('nr-sluzba');
var opt = sel.options[sel.selectedIndex];
nrSluzbaTrvani = opt ? parseInt(opt.dataset.trvani || 0) : 0;
nrVybranyCas = '';
document.getElementById('nr-times-wrap').style.display = 'none';
document.getElementById('nr-cas-manual').value = '';
var sluzbaId = sel.value;
// Načti doplňky
if (sluzbaId) {
nrNactiDoplnky(sluzbaId);
} else {
document.getElementById('nr-doplnky-wrap').style.display = 'none';
}
var datum = document.getElementById('nr-datum').value;
if (datum && nrSluzbaTrvani) nrNactiCasy(datum);
}
async function nrNactiDoplnky(sluzbaId) {
var wrap = document.getElementById('nr-doplnky-wrap');
var grid = document.getElementById('nr-doplnky-grid');
try {
var r = await fetch(API + '/doplnky?sluzba_id=' + sluzbaId, { headers: authHeaders() });
var d = await r.json();
if (!d.ok || !d.data || d.data.length === 0) {
wrap.style.display = 'none';
return;
}
wrap.style.display = 'block';
grid.innerHTML = d.data.map(function(dd) {
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:6px 0;border-bottom:0.5px solid #e9d5ff">'
+ '<div style="display:flex;align-items:center;gap:8px">'
+ '<input type="checkbox" class="nr-doplnek-check" id="nr-doplnek-' + dd.id + '" data-id="' + dd.id + '" data-nazev="' + esc(dd.nazev) + '" data-cena="' + dd.cena + '" data-min="' + dd.trvani_min + '" style="width:16px;height:16px;accent-color:#9333ea;cursor:pointer" onchange="nrDoplnekChange()">'
+ '<div><div style="font-size:12px;color:#111827">' + esc(dd.nazev) + '</div>'
+ (dd.trvani_min > 0 ? '<div style="font-size:11px;color:#9ca3af">+' + dd.trvani_min + ' min</div>' : '')
+ '</div></div>'
+ '<div style="font-size:12px;font-weight:600;color:#7c3aed">+' + Math.round(dd.cena).toLocaleString('cs-CZ') + ' Kč</div>'
+ '</div>';
}).join('') + '<div style="border-top:none"></div>';
} catch(e) {
wrap.style.display = 'none';
}
}
var nrDoplnkyExtraMin = 0;
function nrDoplnekChange() {
var checkboxes = document.querySelectorAll('.nr-doplnek-check:checked');
nrDoplnkyExtraMin = Array.from(checkboxes).reduce(function(s, ch) {
return s + parseInt(ch.dataset.min || 0);
}, 0);
// Znovu načti časy s novým extra_min
var datum = document.getElementById('nr-datum').value;
if (datum && nrSluzbaTrvani) {
nrVybranyCas = '';
document.getElementById('nr-cas-manual').value = '';
nrNactiCasy(datum);
}
}
function nrDatumChange() {
var datum = document.getElementById('nr-datum').value;
nrVybranyCas = '';
document.getElementById('nr-cas-manual').value = '';
if (datum && nrSluzbaTrvani) nrNactiCasy(datum);
}
async function nrNactiCasy(datum) {
var sluzbaId = document.getElementById('nr-sluzba').value;
if (!sluzbaId) return;
var wrap = document.getElementById('nr-times-wrap');
wrap.style.display = 'block';
document.getElementById('nr-times-loading').style.display = 'block';
document.getElementById('nr-times-grid').innerHTML = '';
try {
var extraMin = nrDoplnkyExtraMin || 0;
var r = await fetch(API + '/dostupnost?sluzba_id=' + sluzbaId + '&datum=' + datum + (extraMin > 0 ? '&extra_min=' + extraMin : ''), { headers: authHeaders() });
var d = await r.json();
document.getElementById('nr-times-loading').style.display = 'none';
var tg = document.getElementById('nr-times-grid');
if (d.blokovano) {
tg.innerHTML = '<p style="font-size:12px;color:#f59e0b;grid-column:1/-1">⚠️ Den je blokován — admin může přesto rezervovat.</p>';
} else if (!d.volne_casy || !d.volne_casy.length) {
tg.innerHTML = '<p style="font-size:12px;color:#9ca3af;grid-column:1/-1">Žádné volné časy — zadejte vlastní čas níže.</p>';
} else {
d.volne_casy.forEach(function(t) {
var el = document.createElement('div');
el.style.cssText = 'border:1.5px solid #e9d5ff;border-radius:8px;padding:7px 4px;text-align:center;font-size:12px;cursor:pointer;transition:all .15s';
el.textContent = t;
el.onclick = function() {
document.querySelectorAll('#nr-times-grid div').forEach(function(s){ s.style.background=''; s.style.color=''; s.style.borderColor='#e9d5ff'; });
el.style.background = '#9333ea'; el.style.color = '#fff'; el.style.borderColor = '#9333ea';
nrVybranyCas = t;
document.getElementById('nr-cas-manual').value = t;
};
tg.appendChild(el);
});
}
} catch(e) {
document.getElementById('nr-times-loading').style.display = 'none';
}
}
function nrManualCas() {
nrVybranyCas = document.getElementById('nr-cas-manual').value;
document.querySelectorAll('#nr-times-grid div').forEach(function(s){ s.style.background=''; s.style.color=''; s.style.borderColor='#e9d5ff'; });
}
async function ulozitNovuRez() {
var alertEl = document.getElementById('nova-rez-alert');
alertEl.style.display = 'none';
var sluzbaId = document.getElementById('nr-sluzba').value;
var datum = document.getElementById('nr-datum').value;
var cas = nrVybranyCas || document.getElementById('nr-cas-manual').value;
var jmeno = document.getElementById('nr-jmeno').value.trim();
var prijmeni = document.getElementById('nr-prijmeni').value.trim();
var email = document.getElementById('nr-email').value.trim();
if (!sluzbaId || !datum || !cas || !jmeno || !prijmeni || !email) {
alertEl.className = 'alert error'; alertEl.textContent = 'Vyplňte všechna povinná pole.'; alertEl.style.display = 'block'; return;
}
var btn = document.getElementById('nr-btn-ulozit');
btn.disabled = true; btn.textContent = 'Ukládám…';
try {
var r = await fetch(API + '/rezervace/admin', {
method: 'POST',
headers: authHeaders(),
body: JSON.stringify({
sluzba_id: sluzbaId, datum: datum, cas: cas,
jmeno: jmeno, prijmeni: prijmeni, email: email,
telefon: document.getElementById('nr-telefon').value.trim(),
poznamka: document.getElementById('nr-poznamka').value.trim(),
admin_override: true,
doplnky: Array.from(document.querySelectorAll('.nr-doplnek-check:checked')).map(function(ch){ return parseInt(ch.dataset.id); })
})
});
var d = await r.json();
if (d.ok) {
alertEl.className = 'alert success'; alertEl.textContent = '✅ Rezervace vytvořena!'; alertEl.style.display = 'block';
setTimeout(function(){ closeModal('modal-nova-rez'); resetNovaRez(); loadDnes(); loadMesicData(); }, 1500);
} else {
alertEl.className = 'alert error'; alertEl.textContent = d.error || 'Chyba při ukládání.'; alertEl.style.display = 'block';
}
} catch(e) {
alertEl.className = 'alert error'; alertEl.textContent = 'Chyba připojení.'; alertEl.style.display = 'block';
}
btn.disabled = false; btn.textContent = 'Uložit';
}
function resetNovaRez() {
['nr-sluzba','nr-datum','nr-jmeno','nr-prijmeni','nr-email','nr-telefon','nr-poznamka','nr-cas-manual'].forEach(function(id){
var el = document.getElementById(id); if (el) el.value = '';
});
document.getElementById('nr-times-wrap').style.display = 'none';
document.getElementById('nr-times-grid').innerHTML = '';
document.getElementById('nr-doplnky-wrap').style.display = 'none';
document.getElementById('nr-doplnky-grid').innerHTML = '';
document.getElementById('nova-rez-alert').style.display = 'none';
nrVybranyCas = ''; nrSluzbaTrvani = 0; nrDoplnkyExtraMin = 0;
}
function resetHesloForm() {
['heslo-stare','heslo-nove','heslo-potvrzeni'].forEach(function(id){ document.getElementById(id).value = ''; });
document.getElementById('heslo-alert').style.display = 'none';
}
// --- Správa uživatelů ---
var _prihlasenyUzivatel = '';
async function loadUzivatele() {
try {
var r = await fetch(API + '/uzivatele', { headers: authHeaders() });
if (r.status === 403) return; // Demo účet - nezobrazovat
var d = await r.json();
if (!d.ok) return;
// Zobraz kartu pouze pro admin
document.getElementById('card-uzivatele').style.display = 'block';
var el = document.getElementById('uzivatele-list');
el.innerHTML = d.data.map(function(u) {
return '<div class="set-row" id="uzivatel-row-' + u.id + '">'
+ '<div><div class="set-label">' + esc(u.uzivatelske_jmeno) + '</div>'
+ '<div class="set-sub">' + (u.zmena_hesla_zakaz ? '🔒 Změna hesla zakázána' : '🔓 Může měnit heslo') + '</div></div>'
+ '<button class="btn-sm btn-detail" onclick="openZmenaHeslaUzivatele(' + u.id + ',"' + esc(u.uzivatelske_jmeno) + '")">Změnit heslo</button>'
+ '</div>';
}).join('');
} catch(e) {}
}
function openZmenaHeslaUzivatele(id, jmeno) {
document.getElementById('uzivatel-heslo-id').value = id;
document.getElementById('uzivatel-heslo-jmeno').textContent = jmeno;
document.getElementById('uzivatel-heslo-input').value = '';
document.getElementById('uzivatel-heslo-alert').style.display = 'none';
openModal('modal-uzivatel-heslo');
}
async function ulozitHesloUzivatele() {
var id = document.getElementById('uzivatel-heslo-id').value;
var heslo = document.getElementById('uzivatel-heslo-input').value;
var alertEl = document.getElementById('uzivatel-heslo-alert');
alertEl.style.display = 'none';
if (heslo.length < 8) {
alertEl.className = 'alert error'; alertEl.textContent = 'Heslo musí mít alespoň 8 znaků.'; alertEl.style.display = 'block'; return;
}
try {
var r = await fetch(API + '/uzivatele/' + id, {
method: 'PUT', headers: authHeaders(),
body: JSON.stringify({ nove_heslo: heslo })
});
var d = await r.json();
if (d.ok) {
closeModal('modal-uzivatel-heslo');
showToast('✅ Heslo bylo změněno.');
} else {
alertEl.className = 'alert error'; alertEl.textContent = d.error || 'Chyba.'; alertEl.style.display = 'block';
}
} catch(e) {
alertEl.className = 'alert error'; alertEl.textContent = 'Chyba připojení.'; alertEl.style.display = 'block';
}
}
</script>
<!-- MODAL: Výběr zákazníka z kartotéky -->
<div class="modal-overlay" id="modal-kartotek-pick" style="z-index:200">
<div class="modal-sheet" style="max-height:80vh">
<div class="modal-handle"></div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
<div class="modal-title" style="margin-bottom:0">Vybrat z kartotéky</div>
<button class="btn-modal-discard" style="padding:6px 14px;font-size:13px" onclick="closeModal('modal-kartotek-pick')">Zavřít</button>
</div>
<input class="modal-input" type="search" id="kpick-search" placeholder="🔍 Hledat jméno nebo e-mail…" oninput="kpickSearch(this.value)" style="margin-bottom:8px">
<div id="kpick-results" style="max-height:50vh;overflow-y:auto">
<div class="empty">Začněte psát pro vyhledání</div>
</div>
</div>
</div>
<!-- MODAL: Detail zákazníka -->
<div class="modal-overlay" id="modal-zak">
<div class="modal-sheet" style="max-height:92vh">
<div class="modal-handle"></div>
<div class="modal-title">Karta zákazníka</div>
<input type="hidden" id="modal-zak-id">
<input type="hidden" id="modal-zak-stitky">
<div id="modal-zak-content"></div>
<div class="btn-row" style="margin-top:12px">
<button class="btn-modal-discard" onclick="closeModal('modal-zak')">Zavřít</button>
<button class="btn-modal-discard" style="background:#fee2e2;color:#991b1b" onclick="smazatZakModal()">🗑 Smazat</button>
<button class="btn-modal-save" onclick="saveZakaznik()">Uložit</button>
</div>
</div>
</div>
<!-- MODAL: Nová rezervace (admin) -->
<div class="modal-overlay" id="modal-nova-rez">
<div class="modal-sheet" style="max-height:92vh">
<div class="modal-handle"></div>
<div class="modal-title">Nová rezervace</div>
<div id="nova-rez-alert" class="alert" style="display:none"></div>
<label class="modal-label">Služba *</label>
<select class="modal-input" id="nr-sluzba" onchange="nrSluzbaChange()" style="margin-bottom:10px">
<option value="">— vyberte —</option>
</select>
<div id="nr-doplnky-wrap" style="display:none;margin-bottom:10px">
<label class="modal-label">Doplňkové služby</label>
<div id="nr-doplnky-grid" style="background:#faf5ff;border-radius:8px;padding:8px 10px"></div>
</div>
<label class="modal-label">Datum *</label>
<input class="modal-input" type="date" id="nr-datum" min="" onchange="nrDatumChange()">
<div id="nr-times-wrap" style="display:none;margin-bottom:10px">
<label class="modal-label">Čas *</label>
<div id="nr-times-loading" style="font-size:13px;color:#9ca3af;padding:6px 0">Načítám časy…</div>
<div style="font-size:11px;color:#9ca3af;margin-bottom:6px">Zobrazeny jsou volné časy zákazníků. Jako admin můžete zadat libovolný čas níže.</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:6px" id="nr-times-grid"></div>
<div style="margin-top:6px">
<label class="modal-label">Nebo zadat vlastní čas</label>
<input class="modal-input" type="time" id="nr-cas-manual" style="margin-bottom:0" oninput="nrManualCas()">
</div>
</div>
<button type="button" class="btn-add" style="margin-bottom:10px;width:100%;justify-content:center;padding:9px" onclick="openModal('modal-kartotek-pick')">👥 Vybrat z kartotéky</button>
<label class="modal-label">Jméno *</label>
<input class="modal-input" type="text" id="nr-jmeno" placeholder="Jana">
<label class="modal-label">Příjmení *</label>
<input class="modal-input" type="text" id="nr-prijmeni" placeholder="Nováková">
<label class="modal-label">E-mail *</label>
<input class="modal-input" type="email" id="nr-email" placeholder="jana@email.cz">
<label class="modal-label">Telefon</label>
<input class="modal-input" type="tel" id="nr-telefon" placeholder="+420 123 456 789">
<label class="modal-label">Poznámka</label>
<input class="modal-input" type="text" id="nr-poznamka" placeholder="Volitelná poznámka">
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-nova-rez');resetNovaRez()">Zrušit</button>
<button class="btn-modal-save" id="nr-btn-ulozit" onclick="ulozitNovuRez()">Uložit</button>
</div>
</div>
</div>
<!-- MODAL: Změna hesla -->
<div class="modal-overlay" id="modal-heslo">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Změna hesla</div>
<div id="heslo-alert" class="alert" style="display:none"></div>
<label class="modal-label">Stávající heslo</label>
<input class="modal-input" type="password" id="heslo-stare" placeholder="••••••••">
<label class="modal-label">Nové heslo (min. 8 znaků)</label>
<input class="modal-input" type="password" id="heslo-nove" placeholder="••••••••">
<label class="modal-label">Potvrzení nového hesla</label>
<input class="modal-input" type="password" id="heslo-potvrzeni" placeholder="••••••••">
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-heslo');resetHesloForm()">Zrušit</button>
<button class="btn-modal-save" onclick="changeHeslo()">Uložit heslo</button>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
<!-- MODAL: Zrušení rezervace s důvodem -->
<div class="modal-overlay" id="modal-zruseni">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Zrušit rezervaci</div>
<div id="zruseni-alert" class="alert error" style="display:none"></div>
<input type="hidden" id="zruseni-rez-id">
<label class="modal-label">Důvod zrušení (volitelné)</label>
<textarea class="modal-input" id="zruseni-duvod" placeholder="Např. kapacitní důvody, nemoc, technická závada…" style="height:90px;resize:none;line-height:1.5"></textarea>
<div style="font-size:12px;color:#9ca3af;margin-bottom:12px">Důvod bude uveden v e-mailu zákazníkovi. Pokud nevyplníte, zákazník dostane pouze oznámení o zrušení.</div>
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-zruseni')">Zpět</button>
<button class="btn-modal-save" id="zruseni-save-btn" style="background:#991b1b" onclick="provestZruseni()">Zrušit rezervaci</button>
</div>
</div>
</div>
<!-- MODAL: Změna hesla uživatele (admin) -->
<div class="modal-overlay" id="modal-uzivatel-heslo">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Změna hesla — <span id="uzivatel-heslo-jmeno"></span></div>
<input type="hidden" id="uzivatel-heslo-id">
<div id="uzivatel-heslo-alert" class="alert" style="display:none"></div>
<label class="modal-label">Nové heslo (min. 8 znaků)</label>
<input class="modal-input" type="password" id="uzivatel-heslo-input" placeholder="••••••••">
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-uzivatel-heslo')">Zrušit</button>
<button class="btn-modal-save" onclick="ulozitHesloUzivatele()">Uložit heslo</button>
</div>
</div>
</div>
<!-- MODAL: Doplněk služby -->
<div class="modal-overlay" id="modal-doplnek">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title" id="modal-doplnek-title">Nový doplněk</div>
<input type="hidden" id="doplnek-sluzba-id">
<input type="hidden" id="doplnek-id">
<div id="doplnek-alert" class="alert" style="display:none"></div>
<label class="modal-label">Název doplňku *</label>
<input class="modal-input" id="doplnek-nazev" placeholder="Mytí vlasů">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div>
<label class="modal-label">Cena (Kč)</label>
<input class="modal-input" type="number" id="doplnek-cena" placeholder="40" min="0">
</div>
<div>
<label class="modal-label">Čas navíc (min)</label>
<input class="modal-input" type="number" id="doplnek-min" placeholder="10" min="0">
</div>
</div>
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-doplnek')">Zrušit</button>
<button class="btn-modal-save" onclick="saveDoplnek()">Uložit</button>
</div>
</div>
</div>
<!-- MODAL: Úprava termínu rezervace -->
<div class="modal-overlay" id="modal-uprav-termin">
<div class="modal-sheet">
<div class="modal-handle"></div>
<div class="modal-title">Upravit termín</div>
<input type="hidden" id="ut-rez-id">
<div style="font-size:13px;color:#6b7280;margin-bottom:12px">Služba: <strong id="ut-sluzba-nazev"></strong></div>
<div id="ut-alert" class="alert" style="display:none"></div>
<label class="modal-label">Nové datum *</label>
<input class="modal-input" type="date" id="ut-datum" onchange="utDatumChange()">
<div id="ut-times-wrap" style="display:none;margin-bottom:10px">
<label class="modal-label">Volné časy</label>
<div id="ut-times-loading" style="font-size:13px;color:#9ca3af;padding:6px 0">Načítám časy…</div>
<div style="font-size:11px;color:#9ca3af;margin-bottom:6px">Jako admin můžete zadat libovolný čas ručně.</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:6px" id="ut-times-grid"></div>
</div>
<label class="modal-label">Čas *</label>
<input class="modal-input" type="time" id="ut-cas-manual">
<div style="font-size:11px;color:#9ca3af;margin-bottom:12px">Zákazník dostane e-mail s novým termínem.</div>
<div class="btn-row">
<button class="btn-modal-discard" onclick="closeModal('modal-uprav-termin')">Zrušit</button>
<button class="btn-modal-save" id="ut-btn-ulozit" onclick="ulozitUpravTermin()">Uložit termín</button>
</div>
</div>
</div>
</body>
</html>