456 lines
18 KiB
PHP
456 lines
18 KiB
PHP
<?php
|
|
// Forza la visualizzazione degli errori (solo dev)
|
|
ini_set('display_errors', 1);
|
|
ini_set('display_startup_errors', 1);
|
|
error_reporting(E_ALL);
|
|
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
|
|
include('include/headscript.php');
|
|
|
|
// Connessione DB
|
|
$dbHandler = DBHandlerSelect::getInstance();
|
|
$pdo = $dbHandler->getConnection();
|
|
|
|
// Verifica utente loggato
|
|
if (!isset($iduserlogin)) {
|
|
header("Location: login.php");
|
|
exit;
|
|
}
|
|
|
|
// Controlla se esiste almeno un salone
|
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
|
$stmt->execute([$iduserlogin]);
|
|
if ((int)$stmt->fetchColumn() === 0) {
|
|
header("Location: onboarding_salon.php");
|
|
exit;
|
|
}
|
|
|
|
// Prendi il primo salone
|
|
$stmt = $pdo->prepare("
|
|
SELECT id, name
|
|
FROM shops
|
|
WHERE owner_id = ?
|
|
ORDER BY created_at ASC
|
|
LIMIT 1
|
|
");
|
|
$stmt->execute([$iduserlogin]);
|
|
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$shop) {
|
|
die("Errore: salone non trovato.");
|
|
}
|
|
|
|
$shop_id = (int)$shop['id'];
|
|
$shop_name = $shop['name'];
|
|
|
|
// Helpers flash
|
|
function setFlash(string $type, string $text): void
|
|
{
|
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
|
}
|
|
|
|
function getFlash(): ?array
|
|
{
|
|
if (!isset($_SESSION['flash'])) return null;
|
|
$f = $_SESSION['flash'];
|
|
unset($_SESSION['flash']);
|
|
return $f;
|
|
}
|
|
|
|
// POST - Salva impostazioni
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
try {
|
|
$show_prices_online = isset($_POST['show_prices_online']) ? 1 : 0;
|
|
$appointment_slot_interval = (int)($_POST['appointment_slot_interval'] ?? 30);
|
|
$allow_same_time_multiple = isset($_POST['allow_same_time_multiple']) ? 1 : 0;
|
|
$min_booking_notice_hours = (int)($_POST['min_booking_notice_hours'] ?? 2);
|
|
$max_booking_days_ahead = (int)($_POST['max_booking_days_ahead'] ?? 90);
|
|
$require_appointment_confirmation = isset($_POST['require_appointment_confirmation']) ? 1 : 0;
|
|
$no_show_warning_after = (int)($_POST['no_show_warning_after'] ?? 3);
|
|
$no_show_block_after = (int)($_POST['no_show_block_after'] ?? 5);
|
|
|
|
// Validazione restrict_start_minutes
|
|
$restrict_start_minutes = trim($_POST['restrict_start_minutes'] ?? '00,30');
|
|
$valid_options = ['any', '00', '00,30', '00,15,30,45'];
|
|
if (!in_array($restrict_start_minutes, $valid_options)) {
|
|
$restrict_start_minutes = '00,30';
|
|
}
|
|
|
|
// Validazione intervallo slot
|
|
if ($appointment_slot_interval < 5 || $appointment_slot_interval > 120) {
|
|
$appointment_slot_interval = 30;
|
|
}
|
|
|
|
// Controlla esistenza
|
|
$stmt = $pdo->prepare("SELECT id FROM shop_settings WHERE shop_id = ?");
|
|
$stmt->execute([$shop_id]);
|
|
$exists = $stmt->fetchColumn() !== false;
|
|
|
|
if ($exists) {
|
|
$stmt = $pdo->prepare("
|
|
UPDATE shop_settings SET
|
|
show_prices_online = ?,
|
|
appointment_slot_interval = ?,
|
|
allow_same_time_multiple = ?,
|
|
min_booking_notice_hours = ?,
|
|
max_booking_days_ahead = ?,
|
|
allowed_start_minutes = ?,
|
|
require_appointment_confirmation = ?,
|
|
no_show_warning_after = ?,
|
|
no_show_block_after = ?,
|
|
updated_at = NOW()
|
|
WHERE shop_id = ?
|
|
");
|
|
$ok = $stmt->execute([
|
|
$show_prices_online,
|
|
$appointment_slot_interval,
|
|
$allow_same_time_multiple,
|
|
$min_booking_notice_hours,
|
|
$max_booking_days_ahead,
|
|
$restrict_start_minutes,
|
|
$require_appointment_confirmation,
|
|
$no_show_warning_after,
|
|
$no_show_block_after,
|
|
$shop_id
|
|
]);
|
|
} else {
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO shop_settings (
|
|
shop_id, show_prices_online, appointment_slot_interval,
|
|
allow_same_time_multiple, min_booking_notice_hours, max_booking_days_ahead,
|
|
allowed_start_minutes, require_appointment_confirmation,
|
|
no_show_warning_after, no_show_block_after
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
");
|
|
$ok = $stmt->execute([
|
|
$shop_id,
|
|
$show_prices_online,
|
|
$appointment_slot_interval,
|
|
$allow_same_time_multiple,
|
|
$min_booking_notice_hours,
|
|
$max_booking_days_ahead,
|
|
$restrict_start_minutes,
|
|
$require_appointment_confirmation,
|
|
$no_show_warning_after,
|
|
$no_show_block_after
|
|
]);
|
|
}
|
|
|
|
setFlash($ok ? 'success' : 'danger', $ok ? "Impostazioni salvate con successo!" : "Errore durante il salvataggio.");
|
|
header("Location: salon_settings.php");
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
setFlash('danger', "Errore: " . $e->getMessage());
|
|
header("Location: salon_settings.php");
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Fetch impostazioni
|
|
$stmt = $pdo->prepare("SELECT * FROM shop_settings WHERE shop_id = ?");
|
|
$stmt->execute([$shop_id]);
|
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [
|
|
'show_prices_online' => 1,
|
|
'appointment_slot_interval' => 30,
|
|
'allow_same_time_multiple' => 0,
|
|
'min_booking_notice_hours' => 2,
|
|
'max_booking_days_ahead' => 90,
|
|
'allowed_start_minutes' => '00,30',
|
|
'require_appointment_confirmation' => 1,
|
|
'no_show_warning_after' => 3,
|
|
'no_show_block_after' => 5
|
|
];
|
|
|
|
$flash = getFlash();
|
|
?>
|
|
|
|
<!doctype html>
|
|
<html lang="it">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
|
<?php include('cssinclude.php'); ?>
|
|
<?php include('siteinfo.php'); ?>
|
|
<title>Impostazioni Salone - <?= htmlspecialchars($shop_name) ?></title>
|
|
<style>
|
|
.settings-card {
|
|
border: none;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.settings-header {
|
|
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
|
|
color: white;
|
|
padding: 1.8rem 2rem;
|
|
}
|
|
|
|
.form-section {
|
|
background: #ffffff;
|
|
padding: 2.5rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.form-label {
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
margin-bottom: 0.75rem;
|
|
display: block;
|
|
}
|
|
|
|
.form-text {
|
|
font-size: 0.9rem;
|
|
color: #6b7280;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.preview-box {
|
|
background: #f9fafb;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 10px;
|
|
padding: 1.25rem;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.preview-slots {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.preview-slot {
|
|
background: #e5e7eb;
|
|
color: #374151;
|
|
padding: 6px 14px;
|
|
border-radius: 9999px;
|
|
font-size: 0.9rem;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.preview-slot.allowed {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.btn-save {
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
border: none;
|
|
padding: 1rem 2.5rem;
|
|
font-size: 1.15rem;
|
|
font-weight: 600;
|
|
border-radius: 12px;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
.btn-save:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
|
|
}
|
|
|
|
.toggle-group {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 1.25rem 0;
|
|
border-bottom: 1px solid #f3f4f6;
|
|
}
|
|
|
|
.toggle-group:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.toggle-label {
|
|
font-size: 1.15rem;
|
|
font-weight: 600;
|
|
color: #111827;
|
|
}
|
|
|
|
.form-check-input-lg {
|
|
width: 3.2rem;
|
|
height: 1.7rem;
|
|
}
|
|
|
|
.form-check-input-lg:checked {
|
|
background-color: #6366f1;
|
|
border-color: #6366f1;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="wrapper">
|
|
<?php include('include/navbar.php'); ?>
|
|
<?php include('include/topbar.php'); ?>
|
|
|
|
<div class="page-wrapper">
|
|
<div class="page-content">
|
|
<div class="card settings-card">
|
|
<div class="settings-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">Impostazioni Salone</h5>
|
|
<a href="salon_dashboard.php" class="btn btn-light btn-sm px-4">
|
|
<i class="bx bx-arrow-back me-2"></i> Dashboard
|
|
</a>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<?php if ($flash): ?>
|
|
<div class="alert alert-<?= $flash['type'] ?> alert-dismissible fade show mb-4" role="alert">
|
|
<?= htmlspecialchars($flash['text']) ?>
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<form action="" method="POST">
|
|
<!-- Visibilità prezzi -->
|
|
<div class="toggle-group">
|
|
<label class="toggle-label" for="showPrices">Mostra i prezzi online ai clienti</label>
|
|
<div class="form-check form-switch form-switch-lg">
|
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="show_prices_online" id="showPrices" <?= $settings['show_prices_online'] ? 'checked' : '' ?>>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Richiesta conferma manuale -->
|
|
<div class="toggle-group">
|
|
<label class="toggle-label" for="requireConfirmation">Richiedi conferma manuale per ogni nuovo appuntamento</label>
|
|
<div class="form-check form-switch form-switch-lg">
|
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="require_appointment_confirmation" id="requireConfirmation" <?= $settings['require_appointment_confirmation'] ? 'checked' : '' ?>>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Orari inizio -->
|
|
<div class="form-group mt-4">
|
|
<label class="form-label" for="restrictSelect">Orari di inizio appuntamenti permessi</label>
|
|
<select class="form-select form-select-lg" name="restrict_start_minutes" id="restrictSelect">
|
|
<option value="any" <?= $settings['allowed_start_minutes'] === 'any' ? 'selected' : '' ?>>Qualsiasi minuto (libero)</option>
|
|
<option value="00" <?= $settings['allowed_start_minutes'] === '00' ? 'selected' : '' ?>>Solo ore piene (:00)</option>
|
|
<option value="00,30" <?= $settings['allowed_start_minutes'] === '00,30' ? 'selected' : '' ?>>Solo :00 e :30 (ogni mezz'ora piena)</option>
|
|
<option value="00,15,30,45" <?= $settings['allowed_start_minutes'] === '00,15,30,45' ? 'selected' : '' ?>>Ogni 15 minuti (:00, :15, :30, :45)</option>
|
|
</select>
|
|
<div class="preview-box">
|
|
<strong>Anteprima slot orari (es. dalle 9:00):</strong>
|
|
<div class="preview-slots" id="previewSlots"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Limiti temporali -->
|
|
<div class="row g-4 mt-4">
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="slotInterval">Intervallo slot appuntamenti (minuti)</label>
|
|
<input type="number" class="form-control form-control-lg" name="appointment_slot_interval" id="slotInterval" min="5" max="120"
|
|
value="<?= (int)$settings['appointment_slot_interval'] ?>">
|
|
<div class="form-text">Es: 30 = slot ogni mezz'ora</div>
|
|
</div>
|
|
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="minNotice">Preavviso minimo prenotazione (ore)</label>
|
|
<input type="number" class="form-control form-control-lg" name="min_booking_notice_hours" id="minNotice" min="0" max="72"
|
|
value="<?= (int)$settings['min_booking_notice_hours'] ?>">
|
|
<div class="form-text">Es: 2 = non prenotabile per oggi se mancano meno di 2 ore.</div>
|
|
</div>
|
|
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="maxDays">Giorni massimi in anticipo</label>
|
|
<input type="number" class="form-control form-control-lg" name="max_booking_days_ahead" id="maxDays" min="7" max="365"
|
|
value="<?= (int)$settings['max_booking_days_ahead'] ?>">
|
|
<div class="form-text">Es: 90 = massimo 90 giorni da oggi.</div>
|
|
</div>
|
|
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="allowMultiple">Permetti più appuntamenti allo stesso orario</label>
|
|
<div class="form-check form-switch form-switch-lg mt-2">
|
|
<input class="form-check-input" type="checkbox" name="allow_same_time_multiple" id="allowMultiple" <?= $settings['allow_same_time_multiple'] ? 'checked' : '' ?>>
|
|
</div>
|
|
<div class="form-text">Utile se più parrucchieri possono lavorare contemporaneamente sullo stesso slot.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No-show -->
|
|
<div class="section-title mt-5">Gestione No-Show (assenze)</div>
|
|
<div class="row g-4">
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="noShowWarn">Segnala utente dopo quante assenze consecutive</label>
|
|
<input type="number" class="form-control form-control-lg" name="no_show_warning_after" id="noShowWarn" min="0" max="20"
|
|
value="<?= (int)$settings['no_show_warning_after'] ?>">
|
|
<div class="form-text">0 = disattivato. Es: 3 = dopo 3 no-show invia alert al titolare.</div>
|
|
</div>
|
|
|
|
<div class="col-md-6 form-group">
|
|
<label class="form-label" for="noShowBlock">Blocca utente dopo quante assenze consecutive</label>
|
|
<input type="number" class="form-control form-control-lg" name="no_show_block_after" id="noShowBlock" min="0" max="50"
|
|
value="<?= (int)$settings['no_show_block_after'] ?>">
|
|
<div class="form-text">0 = mai bloccare. Es: 5 = dopo 5 no-show impedisce nuove prenotazioni.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pulsante salva -->
|
|
<div class="d-grid mt-5">
|
|
<button type="submit" class="btn btn-save">
|
|
<i class="bx bx-save me-2"></i> Salva Impostazioni Salone
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<?php include('include/footer.php'); ?>
|
|
</div>
|
|
|
|
<?php include('jsinclude.php'); ?>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
const restrictSelect = document.getElementById('restrictSelect');
|
|
const previewSlots = document.getElementById('previewSlots');
|
|
|
|
function updatePreview() {
|
|
const value = restrictSelect.value;
|
|
previewSlots.innerHTML = '';
|
|
|
|
const examples = [{
|
|
val: 'any',
|
|
slots: ['9:00', '9:05', '9:10', '9:15', '9:20', '...']
|
|
},
|
|
{
|
|
val: '00',
|
|
slots: ['9:00', '10:00', '11:00', '12:00']
|
|
},
|
|
{
|
|
val: '00,30',
|
|
slots: ['9:00', '9:30', '10:00', '10:30', '11:00']
|
|
},
|
|
{
|
|
val: '00,15,30,45',
|
|
slots: ['9:00', '9:15', '9:30', '9:45', '10:00']
|
|
}
|
|
];
|
|
|
|
const selected = examples.find(e => e.val === value) || examples[2];
|
|
|
|
selected.slots.forEach(slot => {
|
|
const span = document.createElement('span');
|
|
span.className = 'preview-slot' + (value !== 'any' ? ' allowed' : '');
|
|
span.textContent = slot;
|
|
previewSlots.appendChild(span);
|
|
});
|
|
}
|
|
|
|
restrictSelect.addEventListener('change', updatePreview);
|
|
updatePreview(); // init
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|