Files

528 lines
20 KiB
PHP

<?php
// book.php — Prenotazione appuntamento (frontend pubblico)
// Flusso: scegli staff → scegli data → scegli slot → conferma (login se serve)
// Arriva da: book.php?shop=X&service=Y
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
require_once(__DIR__ . '/userarea/class/db-functions.php');
$pdo = DBHandlerSelect::getInstance()->getConnection();
// Helper flash
if (!function_exists('getFlash')) {
function getFlash()
{
if (!empty($_SESSION['flash'])) {
$f = $_SESSION['flash'];
unset($_SESSION['flash']);
return $f;
}
return null;
}
}
// --- Parametri ---
$shop_id = (int)($_GET['shop'] ?? 0);
$service_id = (int)($_GET['service'] ?? 0);
// Preselezioni (es. ritorno dal login o da uno slot già scelto)
$pre_staff = (int)($_GET['staff'] ?? 0);
$pre_date = trim($_GET['date'] ?? '');
$pre_time = trim($_GET['time'] ?? '');
$need_login = !empty($_GET['need_login']);
if ($shop_id <= 0 || $service_id <= 0) {
header('Location: search.php');
exit;
}
// --- Salone ---
$s = $pdo->prepare("SELECT id, name, slug, address, city, province, cover_image FROM shops WHERE id = ? AND active = 1");
$s->execute([$shop_id]);
$shop = $s->fetch(PDO::FETCH_ASSOC);
if (!$shop) {
header('Location: search.php');
exit;
}
// --- Servizio ---
$sv = $pdo->prepare("SELECT id, name, description, duration_minutes, price FROM services WHERE id = ? AND shop_id = ? AND is_active = 1");
$sv->execute([$service_id, $shop_id]);
$service = $sv->fetch(PDO::FETCH_ASSOC);
if (!$service) {
header('Location: salon.php?slug=' . urlencode($shop['slug']));
exit;
}
// --- Staff prenotabili online ---
$stf = $pdo->prepare("SELECT id, first_name, last_name, nickname, role, avatar, color_hex
FROM staff
WHERE shop_id = ? AND is_active = 1 AND can_book_online = 1
ORDER BY first_name ASC");
$stf->execute([$shop_id]);
$staffList = $stf->fetchAll(PDO::FETCH_ASSOC);
// --- Limiti calendario ---
$setStmt = $pdo->prepare("SELECT min_booking_notice_hours, max_booking_days_ahead FROM shop_settings WHERE shop_id = ?");
$setStmt->execute([$shop_id]);
$setRow = $setStmt->fetch(PDO::FETCH_ASSOC) ?: [];
$maxDaysAhead = (int)($setRow['max_booking_days_ahead'] ?? 90);
$minDate = (new DateTime('today'))->format('Y-m-d');
$maxDate = (new DateTime('today'))->modify("+{$maxDaysAhead} days")->format('Y-m-d');
$isLogged = !empty($_SESSION['iduserlogin']);
$flash = getFlash();
// Durata leggibile
$dur = (int)$service['duration_minutes'];
$durLabel = $dur >= 60 ? floor($dur / 60) . 'h' . ($dur % 60 ? ' ' . ($dur % 60) . 'min' : '') : $dur . ' min';
?>
<!doctype html>
<html class="no-js" lang="it">
<head>
<?php include('tophead.php'); ?>
<?php include('cssinclude.php'); ?>
<style>
.bk-wrap {
max-width: 820px;
margin: 0 auto;
}
.bk-step {
background: #fff;
border: 1px solid #eef2f7;
border-radius: 16px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 4px 20px rgba(15, 23, 42, .04);
}
.bk-step h4 {
font-size: 17px;
font-weight: 700;
margin: 0 0 16px;
color: #0f172a;
display: flex;
align-items: center;
gap: 10px;
}
.bk-step h4 .bk-num {
width: 28px;
height: 28px;
border-radius: 50%;
background: #e11d6b;
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex: 0 0 auto;
}
.bk-staff-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.bk-staff {
border: 2px solid #eef2f7;
border-radius: 12px;
padding: 14px;
text-align: center;
cursor: pointer;
transition: all .15s ease;
background: #fff;
}
.bk-staff:hover {
border-color: #f9a8c9;
}
.bk-staff.selected {
border-color: #e11d6b;
background: #fff5f9;
}
.bk-staff-av {
width: 56px;
height: 56px;
border-radius: 50%;
margin: 0 auto 8px;
background: #f1f5f9;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
color: #64748b;
font-size: 20px;
overflow: hidden;
}
.bk-staff-av img {
width: 100%;
height: 100%;
object-fit: cover;
}
.bk-staff-name {
font-weight: 600;
font-size: 14px;
color: #0f172a;
}
.bk-staff-role {
font-size: 12px;
color: #94a3b8;
}
.bk-slots {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
gap: 10px;
}
.bk-slot {
border: 2px solid #eef2f7;
border-radius: 10px;
padding: 10px 0;
text-align: center;
cursor: pointer;
font-weight: 600;
color: #0f172a;
transition: all .12s ease;
background: #fff;
}
.bk-slot:hover {
border-color: #f9a8c9;
}
.bk-slot.selected {
border-color: #e11d6b;
background: #e11d6b;
color: #fff;
}
.bk-muted {
color: #94a3b8;
font-size: 14px;
}
.bk-summary {
position: sticky;
top: 20px;
}
.bk-summary-row {
display: flex;
justify-content: space-between;
padding: 6px 0;
font-size: 14px;
}
.bk-summary-row span:first-child {
color: #64748b;
}
.bk-summary-row span:last-child {
font-weight: 600;
color: #0f172a;
text-align: right;
}
.bk-confirm-btn {
width: 100%;
background: #e11d6b;
color: #fff;
border: none;
border-radius: 12px;
padding: 14px;
font-weight: 700;
font-size: 16px;
cursor: pointer;
transition: opacity .15s;
}
.bk-confirm-btn:disabled {
opacity: .4;
cursor: not-allowed;
}
.bk-date-input {
width: 100%;
padding: 12px;
border: 2px solid #eef2f7;
border-radius: 10px;
font-size: 15px;
}
.bk-hidden {
display: none;
}
</style>
</head>
<body>
<?php include('preloader.php'); ?>
<?php include('offcanvas.php'); ?>
<header class="tp-header-height">
<?php include('topbar.php'); ?>
</header>
<main>
<div class="tp-tour-area pt-100 pb-100">
<div class="container">
<div class="bk-wrap">
<h2 class="mb-10" style="font-size:26px;font-weight:700;">Prenota da <?= htmlspecialchars($shop['name']) ?></h2>
<p class="bk-muted mb-30">
<?= htmlspecialchars($service['name']) ?> · <?= $durLabel ?>
<?php if ($service['price'] !== null): ?> · <?= number_format((float)$service['price'], 2, ',', '.') ?> €<?php endif; ?>
</p>
<?php if ($flash): ?>
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> mb-20" style="padding:12px 16px;border-radius:10px;">
<?= htmlspecialchars($flash['text']) ?>
</div>
<?php endif; ?>
<div class="row">
<div class="col-lg-8">
<!-- STEP 1: Staff -->
<div class="bk-step">
<h4><span class="bk-num">1</span> Scegli l'operatore</h4>
<?php if (empty($staffList)): ?>
<p class="bk-muted">Nessun operatore disponibile per la prenotazione online. Contatta direttamente il salone.</p>
<?php else: ?>
<div class="bk-staff-grid">
<?php foreach ($staffList as $st): ?>
<?php
$stName = htmlspecialchars(trim($st['first_name'] . ' ' . $st['last_name']));
$stNick = !empty($st['nickname']) ? htmlspecialchars($st['nickname']) : $stName;
$initial = strtoupper(mb_substr($st['first_name'], 0, 1));
$sel = ($pre_staff === (int)$st['id']) ? ' selected' : '';
?>
<div class="bk-staff<?= $sel ?>" data-staff="<?= (int)$st['id'] ?>" data-name="<?= $stNick ?>">
<div class="bk-staff-av">
<?php if (!empty($st['avatar'])): ?>
<img src="<?= htmlspecialchars($st['avatar']) ?>" alt="<?= $stNick ?>">
<?php else: ?>
<?= $initial ?>
<?php endif; ?>
</div>
<div class="bk-staff-name"><?= $stNick ?></div>
<?php if (!empty($st['role'])): ?>
<div class="bk-staff-role"><?= htmlspecialchars($st['role']) ?></div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- STEP 2: Data -->
<div class="bk-step">
<h4><span class="bk-num">2</span> Scegli il giorno</h4>
<input type="date" id="bkDate" class="bk-date-input"
min="<?= $minDate ?>" max="<?= $maxDate ?>"
value="<?= htmlspecialchars($pre_date) ?>">
</div>
<!-- STEP 3: Slot -->
<div class="bk-step">
<h4><span class="bk-num">3</span> Scegli l'orario</h4>
<div id="bkSlots">
<p class="bk-muted">Seleziona prima operatore e giorno.</p>
</div>
</div>
</div>
<!-- Riepilogo + conferma -->
<div class="col-lg-4">
<div class="bk-step bk-summary">
<h4 style="margin-bottom:12px;">Riepilogo</h4>
<div class="bk-summary-row"><span>Salone</span><span><?= htmlspecialchars($shop['name']) ?></span></div>
<div class="bk-summary-row"><span>Servizio</span><span><?= htmlspecialchars($service['name']) ?></span></div>
<div class="bk-summary-row"><span>Durata</span><span><?= $durLabel ?></span></div>
<div class="bk-summary-row"><span>Operatore</span><span id="sumStaff">—</span></div>
<div class="bk-summary-row"><span>Giorno</span><span id="sumDate">—</span></div>
<div class="bk-summary-row"><span>Orario</span><span id="sumTime">—</span></div>
<hr style="margin:14px 0;border-color:#eef2f7;">
<form id="bkForm" method="post" action="book_save.php">
<input type="hidden" name="shop_id" value="<?= $shop_id ?>">
<input type="hidden" name="service_id" value="<?= $service_id ?>">
<input type="hidden" name="staff_id" id="fStaff" value="<?= $pre_staff ?: '' ?>">
<input type="hidden" name="date" id="fDate" value="<?= htmlspecialchars($pre_date) ?>">
<input type="hidden" name="time" id="fTime" value="<?= htmlspecialchars($pre_time) ?>">
<textarea name="notes" placeholder="Note per il salone (opzionale)"
style="width:100%;border:2px solid #eef2f7;border-radius:10px;padding:10px;font-size:14px;margin-bottom:12px;min-height:70px;"></textarea>
<button type="submit" id="bkConfirm" class="bk-confirm-btn" disabled>
<?= $isLogged ? 'Conferma prenotazione' : 'Accedi e prenota' ?>
</button>
</form>
<p class="bk-muted mt-10" style="font-size:12px;text-align:center;">
La prenotazione sarà confermata dal salone.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer><?php include('footer.php'); ?></footer>
<?php include('jsinclude.php'); ?>
<script>
(function() {
var shopId = <?= $shop_id ?>;
var serviceId = <?= $service_id ?>;
var isLogged = <?= $isLogged ? 'true' : 'false' ?>;
var state = {
staff: <?= $pre_staff ?: 'null' ?>,
staffName: null,
date: <?= $pre_date ? json_encode($pre_date) : 'null' ?>,
time: <?= $pre_time ? json_encode($pre_time) : 'null' ?>
};
var slotsBox = document.getElementById('bkSlots');
var dateInp = document.getElementById('bkDate');
var confirmBtn = document.getElementById('bkConfirm');
// --- Selezione staff ---
document.querySelectorAll('.bk-staff').forEach(function(el) {
el.addEventListener('click', function() {
document.querySelectorAll('.bk-staff').forEach(function(x) {
x.classList.remove('selected');
});
el.classList.add('selected');
state.staff = parseInt(el.getAttribute('data-staff'), 10);
state.staffName = el.getAttribute('data-name');
state.time = null; // reset orario: cambia lo staff, cambiano gli slot
document.getElementById('fStaff').value = state.staff;
document.getElementById('sumStaff').textContent = state.staffName;
loadSlots();
refreshConfirm();
});
});
// Ripristina nome staff preselezionato
var preSel = document.querySelector('.bk-staff.selected');
if (preSel) {
state.staffName = preSel.getAttribute('data-name');
document.getElementById('sumStaff').textContent = state.staffName;
}
// --- Selezione data ---
dateInp.addEventListener('change', function() {
state.date = dateInp.value;
state.time = null;
document.getElementById('fDate').value = state.date;
document.getElementById('sumDate').textContent = formatDate(state.date);
loadSlots();
refreshConfirm();
});
if (state.date) {
document.getElementById('sumDate').textContent = formatDate(state.date);
}
if (state.time) {
document.getElementById('sumTime').textContent = state.time;
}
// --- Carica slot via AJAX ---
function loadSlots() {
if (!state.staff || !state.date) {
slotsBox.innerHTML = '<p class="bk-muted">Seleziona prima operatore e giorno.</p>';
return;
}
slotsBox.innerHTML = '<p class="bk-muted">Caricamento orari…</p>';
var url = 'book_slots.php?shop=' + shopId + '&service=' + serviceId +
'&staff=' + state.staff + '&date=' + encodeURIComponent(state.date);
fetch(url)
.then(function(r) {
return r.json();
})
.then(function(data) {
if (!data.ok) {
slotsBox.innerHTML = '<p class="bk-muted">Errore nel caricamento degli orari.</p>';
return;
}
if (data.closed) {
slotsBox.innerHTML = '<p class="bk-muted">Il salone è chiuso in questa data.</p>';
return;
}
if (!data.slots || data.slots.length === 0) {
slotsBox.innerHTML = '<p class="bk-muted">Nessun orario disponibile in questa data. Prova un altro giorno.</p>';
return;
}
var html = '<div class="bk-slots">';
data.slots.forEach(function(t) {
var sel = (t === state.time) ? ' selected' : '';
html += '<div class="bk-slot' + sel + '" data-time="' + t + '">' + t + '</div>';
});
html += '</div>';
slotsBox.innerHTML = html;
slotsBox.querySelectorAll('.bk-slot').forEach(function(sl) {
sl.addEventListener('click', function() {
slotsBox.querySelectorAll('.bk-slot').forEach(function(x) {
x.classList.remove('selected');
});
sl.classList.add('selected');
state.time = sl.getAttribute('data-time');
document.getElementById('fTime').value = state.time;
document.getElementById('sumTime').textContent = state.time;
refreshConfirm();
});
});
})
.catch(function() {
slotsBox.innerHTML = '<p class="bk-muted">Errore di connessione.</p>';
});
}
// --- Abilita il bottone solo quando tutto è scelto ---
function refreshConfirm() {
confirmBtn.disabled = !(state.staff && state.date && state.time);
}
function formatDate(iso) {
if (!iso) return '—';
var p = iso.split('-');
return p[2] + '/' + p[1] + '/' + p[0];
}
// Se preselezione completa (ritorno dal login), carica gli slot
if (state.staff && state.date) {
loadSlots();
}
refreshConfirm();
<?php if ($need_login && !$isLogged): ?>
// Utente tornato senza login: apri la modale di invito
if (window.openFavLoginModal) {
openFavLoginModal();
}
<?php endif; ?>
})();
</script>
</body>
</html>