Files
2026-08-24 16:26:34 +02:00

1057 lines
48 KiB
PHP

<?php
require_once('include/headscript.php');
/**
* Pannello admin: calendario classi del mese con prenotazioni,
* gestione persa/ripristina/riprogramma, ricerca utenti, inserimento
* manuale e cancellazione completa di una classe schedulata.
*/
$pdo = DBHandlerSelect::getInstance()->getConnection();
function e($value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
function initials(string $first, string $last): string
{
$a = mb_substr(trim($first), 0, 1);
$b = mb_substr(trim($last), 0, 1);
$ini = strtoupper($a . $b);
return $ini !== '' ? $ini : '?';
}
function avatarColor(string $seed): string
{
$palette = ['#1ebf73', '#2980b9', '#8e44ad', '#e67e22', '#16a085', '#c0392b', '#2c3e50', '#d35400'];
$idx = abs(crc32($seed)) % count($palette);
return $palette[$idx];
}
$optRow = $pdo->query("SELECT maxbeforetimecancell FROM `option` LIMIT 1")->fetch();
$timetocancel = $optRow ? (int) $optRow['maxbeforetimecancell'] : 24;
if (isset($_GET['prev_month'])) {
$currentMonthStart = preg_replace('/[^0-9\-]/', '', $_GET['prev_month']) . '-01';
} elseif (isset($_GET['next_month'])) {
$currentMonthStart = preg_replace('/[^0-9\-]/', '', $_GET['next_month']) . '-01';
} else {
$currentMonthStart = date("Y-m-01");
}
if (strtotime($currentMonthStart) === false) {
$currentMonthStart = date("Y-m-01");
}
$currentMonthEnd = date("Y-m-t", strtotime($currentMonthStart));
$sqlSchedule = "SELECT serviceschedule.*, service.servicename, service.colorclass,
service.maxcapacity, service.idservice
FROM serviceschedule
LEFT JOIN service ON serviceschedule.idservice = service.idservice
WHERE serviceschedule.dateschedule BETWEEN :start AND DATE_ADD(:end, INTERVAL 1 DAY)
ORDER BY serviceschedule.dateschedule";
$stmtSchedule = $pdo->prepare($sqlSchedule);
$stmtSchedule->execute([':start' => $currentMonthStart, ':end' => $currentMonthEnd]);
$scheduleRows = $stmtSchedule->fetchAll();
$bookingsBySchedule = [];
$scheduleIds = array_filter(array_map(fn($r) => (int) $r['idserviceschedule'], $scheduleRows));
if (!empty($scheduleIds)) {
$ph = implode(',', array_fill(0, count($scheduleIds), '?'));
$sqlBookings = "SELECT bookingclass.idbookingclass, bookingclass.idserviceschedule,
bookingclass.lostlesson, bookingclass.status,
auth_users.first_name, auth_users.last_name, auth_users.id AS iduser
FROM bookingclass
LEFT JOIN auth_users ON bookingclass.iduser = auth_users.id
WHERE bookingclass.idserviceschedule IN ($ph)
AND bookingclass.status IN ('booked', 'pending')
ORDER BY auth_users.last_name, auth_users.first_name";
$stmtBookings = $pdo->prepare($sqlBookings);
$stmtBookings->execute(array_values($scheduleIds));
foreach ($stmtBookings->fetchAll() as $b) {
$bookingsBySchedule[(int) $b['idserviceschedule']][] = $b;
}
}
$italianMonths = [
"January" => "Gennaio",
"February" => "Febbraio",
"March" => "Marzo",
"April" => "Aprile",
"May" => "Maggio",
"June" => "Giugno",
"July" => "Luglio",
"August" => "Agosto",
"September" => "Settembre",
"October" => "Ottobre",
"November" => "Novembre",
"December" => "Dicembre"
];
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Pannello Classi</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content="YogiBook - Prenotazione facile YogaSoul" name="description" />
<meta content="Advanced Creative Solutions" name="author" />
<link rel="shortcut icon" href="assets/images/favicon.ico">
<link href="assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
<link href="assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
.month-navigation {
display: flex;
justify-content: center;
align-items: center;
gap: 24px;
margin: 10px 0 26px;
}
.month-navigation h2 {
margin: 0;
font-size: 22px;
font-weight: 700;
color: #374151;
min-width: 220px;
text-align: center;
}
.month-navigation a {
color: #1ebf73;
transition: transform 0.15s;
}
.month-navigation a:hover {
transform: scale(1.15);
}
.class-card {
background: #fff;
border-radius: 14px;
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.06);
overflow: hidden;
margin-bottom: 18px;
display: flex;
transition: box-shadow 0.2s;
}
.class-card:hover {
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.1);
}
.class-date {
flex: 0 0 110px;
background: #1ebf73;
color: #fff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 16px;
}
.class-date .day {
font-size: 44px;
font-weight: 800;
line-height: 1;
}
.class-date .month {
font-size: 18px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.class-body {
flex: 1;
padding: 18px 22px;
}
.class-title {
font-size: 19px;
font-weight: 700;
color: #1f2937;
margin: 0 0 4px;
}
.class-meta {
color: #6b7280;
font-size: 14px;
margin-bottom: 3px;
}
.capacity-pill {
display: inline-block;
background: #eef6f1;
color: #1a7f52;
font-weight: 600;
font-size: 13px;
padding: 2px 10px;
border-radius: 20px;
margin-left: 8px;
}
.capacity-pill.full {
background: #fdecec;
color: #c0392b;
}
.class-toolbar {
margin-top: 12px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.btn-soft {
border: none;
border-radius: 8px;
padding: 7px 14px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: filter 0.15s;
}
.btn-soft:hover {
filter: brightness(0.94);
}
.btn-bookings {
background: #eef2f7;
color: #374151;
}
.btn-delete-class {
background: #fdecea;
color: #c0392b;
}
.bookings-panel {
display: none;
margin-top: 16px;
border-top: 1px solid #eef0f3;
padding-top: 16px;
}
.bookings-panel.open {
display: block;
}
.bookings-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.bookings-header .title {
font-size: 14px;
font-weight: 700;
color: #374151;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.booking-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.booking-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: 10px;
background: #f8fafc;
border: 1px solid #eef0f3;
transition: background 0.15s, border-color 0.15s;
}
.booking-item:hover {
background: #f1f6fb;
border-color: #dbe4ee;
}
.booking-item.is-lost {
background: #fbf1f0;
border-color: #f3d6d2;
}
.avatar {
flex: 0 0 40px;
width: 40px;
height: 40px;
border-radius: 50%;
color: #fff;
font-weight: 700;
font-size: 15px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
.booking-info {
flex: 1;
min-width: 0;
}
.booking-name {
font-weight: 600;
color: #1f2937;
font-size: 15px;
}
.status-chip {
display: inline-block;
font-size: 11px;
font-weight: 700;
padding: 2px 9px;
border-radius: 20px;
margin-left: 6px;
vertical-align: middle;
}
.status-chip.booked {
background: #e3f2ec;
color: #1a7f52;
}
.status-chip.lost {
background: #1f2937;
color: #fff;
}
.booking-actions {
display: flex;
gap: 6px;
flex-shrink: 0;
}
.mini-btn {
border: none;
border-radius: 8px;
padding: 7px 12px;
font-size: 12px;
font-weight: 600;
color: #fff;
cursor: pointer;
transition: filter 0.15s;
white-space: nowrap;
}
.mini-btn:hover {
filter: brightness(0.92);
}
.mini-btn.lost {
background: #e67e22;
}
.mini-btn.restore {
background: #27ae60;
}
.mini-btn.reprogram {
background: #2980b9;
}
.mini-btn.remove {
background: #e74c3c;
}
.no-bookings {
text-align: center;
color: #9ca3af;
padding: 20px;
font-size: 14px;
}
.add-box {
background: #f8fbf9;
border: 1px dashed #cfe8db;
border-radius: 12px;
padding: 18px;
margin-top: 16px;
}
.add-box label {
font-size: 13px;
font-weight: 600;
color: #374151;
}
.search-results {
border: 1px solid #e2e4e9;
border-radius: 8px;
margin-top: 6px;
max-height: 220px;
overflow-y: auto;
display: none;
background: #fff;
}
.search-results.show {
display: block;
}
.user-result {
padding: 10px 12px;
cursor: pointer;
font-size: 13px;
border-bottom: 1px solid #f0f2f5;
display: flex;
align-items: center;
gap: 10px;
}
.user-result:last-child {
border-bottom: none;
}
.user-result:hover {
background: #eef6f1;
}
.empty-month {
text-align: center;
color: #9ca3af;
padding: 40px 10px;
}
</style>
</head>
<body>
<div id="layout-wrapper">
<header id="page-topbar" class="isvertical-topbar">
<div class="navbar-header">
<div class="d-flex">
<?php include('include/logoarea.php'); ?>
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
<i class="bx bx-menu align-middle"></i>
</button>
<div class="page-title-box align-self-center d-none d-md-block">
<h4 class="page-title mb-0">Pannello Classi</h4>
</div>
</div>
<div class="d-flex">
<?php include('include/languageselection.php'); ?>
<?php include('include/profiletopbar.php'); ?>
</div>
</div>
</header>
<?php include('include/sidebar.php'); ?>
<header class="ishorizontal-topbar">
<div class="navbar-header">
<div class="d-flex"></div>
</div>
<div class="topnav">
<div class="container-fluid">
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
</div>
</div>
</header>
<div class="main-content">
<div class="page-content">
<div class="container-fluid">
<?php if (isset($_GET['success'])) :
$msgMap = [
'1' => ['danger', 'Lezione segnata come persa con successo.'],
'2' => ['success', 'Lezione ripristinata con successo.'],
'3' => ['success', 'Lezione riprogrammata con successo.'],
'4' => ['success', 'Classe eliminata con successo.'],
'5' => ['success', 'Prenotazione confermata con successo.'],
];
$m = $msgMap[$_GET['success']] ?? null;
if ($m) : ?>
<div class="alert alert-<?php echo $m[0]; ?>" role="alert"><?php echo e($m[1]); ?></div>
<?php endif;
endif; ?>
<?php if (isset($_GET['error'])) :
$errMap = [
'duplicate' => 'Questo utente è già prenotato in questa classe.',
'insert' => 'Errore durante l\'inserimento. Riprova.',
'missingdata' => 'Dati mancanti per l\'inserimento.',
];
$em = $errMap[$_GET['error']] ?? 'Si è verificato un errore.';
?>
<div class="alert alert-danger" role="alert"><?php echo e($em); ?></div>
<?php endif; ?>
<div class="month-navigation">
<a href="?prev_month=<?php echo e(date('Y-m', strtotime('-1 month', strtotime($currentMonthStart)))); ?>"><i class="fas fa-chevron-left fa-2x"></i></a>
<h2><?php echo e($italianMonths[date("F", strtotime($currentMonthStart))] . ' ' . date("Y", strtotime($currentMonthStart))); ?></h2>
<a href="?next_month=<?php echo e(date('Y-m', strtotime('+1 month', strtotime($currentMonthStart)))); ?>"><i class="fas fa-chevron-right fa-2x"></i></a>
</div>
<?php if (empty($scheduleRows)) : ?>
<div class="empty-month">
<i class="fas fa-calendar-xmark fa-2x mb-2"></i>
<p>Nessuna classe programmata per questo mese.</p>
</div>
<?php else : ?>
<?php foreach ($scheduleRows as $sch) :
$dateObj = new DateTime($sch['dateschedule']);
$dayNum = $dateObj->format("d");
$monthIt = $italianMonths[$dateObj->format("F")];
$whenFmt = $dateObj->format("d-m-Y H:i");
$scheduleId = (int) $sch['idserviceschedule'];
$allRows = $bookingsBySchedule[$scheduleId] ?? [];
// separa confermati (booked) da in attesa (pending)
$bookings = array_filter($allRows, fn($b) => $b['status'] === 'booked');
$pendings = array_filter($allRows, fn($b) => $b['status'] === 'pending');
$countPersons = count($bookings); // contatore = solo confermati
$countPending = count($pendings);
$maxcap = (int) $sch['maxcapacity'];
$isFull = $maxcap > 0 && $countPersons >= $maxcap;
$colorclass = $sch['colorclass'] ?: '#e6f3ff';
?>
<div class="class-card" data-date="<?php echo e($dateObj->format('Y-m-d')); ?>">
<div class="class-date">
<div class="day"><?php echo e($dayNum); ?></div>
<div class="month"><?php echo e($monthIt); ?></div>
</div>
<div class="class-body" style="border-left: 6px solid <?php echo e($colorclass); ?>;">
<h3 class="class-title">
<?php echo e($sch['servicename']); ?>
<span class="capacity-pill <?php echo $isFull ? 'full' : ''; ?>">
<i class="fas fa-user-group" style="font-size:11px;"></i>
<?php echo $countPersons; ?>/<?php echo $maxcap; ?>
</span>
<?php if ($countPending > 0) : ?>
<span class="capacity-pill" style="background:#fff4d6;color:#8a6d00;">
<i class="fas fa-hourglass-half" style="font-size:11px;"></i>
<?php echo $countPending; ?> in attesa
</span>
<?php endif; ?>
</h3>
<p class="class-meta"><i class="far fa-clock me-1"></i><?php echo e($whenFmt); ?></p>
<p class="class-meta"><i class="fas fa-location-dot me-1"></i>via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<div class="class-toolbar">
<button class="btn-soft btn-bookings" onclick="togglePanel(this)"><i class="fas fa-list-ul me-1"></i> Vedi prenotazioni</button>
<button class="btn-soft btn-delete-class" onclick="confirmDeleteClass(<?php echo $scheduleId; ?>, '<?php echo e($sch['servicename']); ?>', '<?php echo e($whenFmt); ?>', <?php echo $countPersons; ?>)"><i class="fas fa-trash me-1"></i> Elimina classe</button>
</div>
<div class="bookings-panel">
<div class="bookings-header">
<span class="title"><i class="fas fa-users me-1"></i> Prenotazioni (<?php echo $countPersons; ?>)</span>
</div>
<div class="booking-list">
<?php if (empty($bookings)) : ?>
<div class="no-bookings">
<i class="fas fa-user-slash mb-1" style="font-size:22px;color:#d5d9e0;"></i><br>
Nessuna prenotazione per questa classe.
</div>
<?php else : ?>
<?php foreach ($bookings as $b) :
$idbc = (int) $b['idbookingclass'];
$isLost = ($b['lostlesson'] === 'Y');
$fullName = trim($b['first_name'] . ' ' . $b['last_name']);
$ini = initials($b['first_name'] ?? '', $b['last_name'] ?? '');
$avColor = avatarColor($fullName);
?>
<div class="booking-item <?php echo $isLost ? 'is-lost' : ''; ?>" data-iduser="<?php echo (int) $b['iduser']; ?>">
<div class="avatar" style="background: <?php echo $isLost ? '#7f8c8d' : $avColor; ?>;"><?php echo e($ini); ?></div>
<div class="booking-info">
<span class="booking-name"><?php echo e($fullName); ?></span>
<?php if ($isLost) : ?>
<span class="status-chip lost">Persa</span>
<?php else : ?>
<span class="status-chip booked">Prenotata</span>
<?php endif; ?>
</div>
<div class="booking-actions">
<?php if ($isLost) : ?>
<button class="mini-btn restore" data-idbookingclass="<?php echo $idbc; ?>"><i class="fas fa-undo"></i> Ripristina</button>
<?php else : ?>
<button class="mini-btn lost" data-idbookingclass="<?php echo $idbc; ?>"><i class="fas fa-wind"></i> Persa</button>
<button class="mini-btn reprogram" data-idbookingclass="<?php echo $idbc; ?>"><i class="fas fa-calendar-alt"></i> Riprogramma</button>
<?php endif; ?>
<button class="mini-btn remove" onclick="handleRemoveClick(<?php echo $idbc; ?>)"><i class="fas fa-trash"></i></button>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php if ($countPending > 0) : ?>
<div class="bookings-header" style="margin-top:18px;">
<span class="title" style="color:#8a6d00;"><i class="fas fa-hourglass-half me-1"></i> In attesa di conferma (<?php echo $countPending; ?>)</span>
</div>
<div class="booking-list">
<?php foreach ($pendings as $b) :
$idbc = (int) $b['idbookingclass'];
$fullName = trim($b['first_name'] . ' ' . $b['last_name']);
$ini = initials($b['first_name'] ?? '', $b['last_name'] ?? '');
$avColor = avatarColor($fullName);
?>
<div class="booking-item" style="background:#fffbf0;border-color:#f5e6b8;" data-iduser="<?php echo (int) $b['iduser']; ?>">
<div class="avatar" style="background: <?php echo $avColor; ?>;"><?php echo e($ini); ?></div>
<div class="booking-info">
<span class="booking-name"><?php echo e($fullName); ?></span>
<span class="status-chip" style="background:#fff4d6;color:#8a6d00;">In attesa</span>
</div>
<div class="booking-actions">
<button class="mini-btn restore confirm-pending" data-idbookingclass="<?php echo $idbc; ?>"><i class="fas fa-check"></i> Conferma</button>
<button class="mini-btn remove" onclick="handleRemoveClick(<?php echo $idbc; ?>)"><i class="fas fa-trash"></i></button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="add-box">
<strong class="d-block mb-2" style="font-size:14px;"><i class="fas fa-user-plus me-1" style="color:#1ebf73;"></i> Aggiungi partecipante</strong>
<form class="add-booking-form" action="inserisci_record.php" method="post">
<div class="row g-2">
<div class="col-md-4"><label>Nome</label><input type="text" class="form-control name" name="name" autocomplete="off"></div>
<div class="col-md-4"><label>Cognome</label><input type="text" class="form-control surname" name="surname" autocomplete="off"></div>
<div class="col-md-4"><label>Email</label><input type="text" class="form-control email" name="email"></div>
</div>
<input type="hidden" class="userid" name="userid">
<input type="hidden" name="idserviceschedule" value="<?php echo $scheduleId; ?>">
<input type="hidden" name="status" value="booked">
<input type="hidden" name="idorder" value="0">
<input type="hidden" name="idservice" value="<?php echo (int) $sch['idservice']; ?>">
<input type="hidden" name="bookingstart" value="<?php echo e($sch['dateschedule']); ?>">
<div class="search-results"></div>
<button type="submit" class="btn btn-primary btn-sm mt-2">Inserisci</button>
</form>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<?php include('include/footer.php'); ?>
</div>
</div>
</div>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
<script src="assets/libs/simplebar/simplebar.min.js"></script>
<script src="assets/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.js"></script>
<script>
function togglePanel(button) {
var panel = button.closest('.class-body').querySelector('.bookings-panel');
panel.classList.toggle('open');
}
function handleRemoveClick(idbookingclass) {
Swal.fire({
title: 'Sei sicuro?',
text: 'Questa azione rimuoverà la persona dalla classe.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì, rimuovi',
cancelButtonText: 'Annulla'
})
.then((result) => {
if (result.isConfirmed) {
window.location.href = 'removebooking.php?idbookingclass=' + idbookingclass;
}
});
}
function confirmDeleteClass(idserviceschedule, name, when, count) {
Swal.fire({
title: 'Eliminare questa classe?',
html: 'Stai per eliminare <strong>' + name + '</strong> del <strong>' + when + '</strong>.<br><br>' +
(count > 0 ? '<span style="color:#c0392b;font-weight:600;">Attenzione: verranno rimosse anche ' + count + ' prenotazione/i associate.</span>' : 'Questa classe non ha prenotazioni.') +
'<br><br>L\'operazione non è reversibile.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#e74c3c',
cancelButtonColor: '#6b7280',
confirmButtonText: 'Sì, elimina tutto',
cancelButtonText: 'Annulla'
}).then((result) => {
if (result.isConfirmed) {
window.location.href = 'removeschedule.php?idserviceschedule=' + idserviceschedule;
}
});
}
$(document).ready(function() {
// Scroll automatico alla classe di oggi o alla prima futura
(function scrollToToday() {
var cards = document.querySelectorAll('.class-card[data-date]');
if (!cards.length) return;
var today = new Date();
today.setHours(0, 0, 0, 0);
var target = null;
for (var i = 0; i < cards.length; i++) {
var d = new Date(cards[i].getAttribute('data-date') + 'T00:00:00');
if (d >= today) {
target = cards[i];
break;
}
target = cards[i]; // se tutte passate, resta sull'ultima
}
if (target) {
setTimeout(function() {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 200);
}
})();
$(document).on('click', '.mini-btn.lost', function() {
var id = $(this).data('idbookingclass');
Swal.fire({
title: 'Segnare come persa?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì',
cancelButtonText: 'Annulla'
})
.then((r) => {
if (r.isConfirmed) {
$.post('aggiorna_campo_lost.php', {
idbookingclass: id
}).done(() => window.location.href = 'adminpanel.php?success=1').fail(() => Swal.fire('Errore durante l\'aggiornamento', '', 'error'));
}
});
});
$(document).on('click', '.mini-btn.restore:not(.confirm-pending)', function() {
var id = $(this).data('idbookingclass');
Swal.fire({
title: 'Ripristinare questa lezione?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Sì',
cancelButtonText: 'Annulla'
})
.then((r) => {
if (r.isConfirmed) {
$.post('ripristina_campo_lost.php', {
idbookingclass: id
}).done(() => window.location.href = 'adminpanel.php?success=2').fail(() => Swal.fire('Errore durante il ripristino', '', 'error'));
}
});
});
$(document).on('click', '.confirm-pending', function() {
var id = $(this).data('idbookingclass');
Swal.fire({
title: 'Confermare la prenotazione?',
text: 'La lezione passerà da "in attesa" a confermata.',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Sì, conferma',
cancelButtonText: 'Annulla'
})
.then((r) => {
if (r.isConfirmed) {
$.post('conferma_pending.php', {
idbookingclass: id
}).done(() => window.location.href = 'adminpanel.php?success=5').fail(() => Swal.fire('Errore durante la conferma', '', 'error'));
}
});
});
$(document).on('click', '.mini-btn.reprogram', function() {
var idbookingclass = $(this).data('idbookingclass');
$.ajax({
url: 'get_available_classes.php?t=' + Date.now(),
method: 'GET',
dataType: 'json',
success: function(data) {
if (data.error) {
Swal.fire('Errore', data.error, 'error');
return;
}
let opts = '',
selectedSet = false;
const now = new Date();
const months = {
January: 'Gennaio',
February: 'Febbraio',
March: 'Marzo',
April: 'Aprile',
May: 'Maggio',
June: 'Giugno',
July: 'Luglio',
August: 'Agosto',
September: 'Settembre',
October: 'Ottobre',
November: 'Novembre',
December: 'Dicembre'
};
data.classes.forEach(function(cls) {
let disp;
if (cls.display_dateschedule) {
disp = cls.display_dateschedule;
} else if (cls.dateschedule) {
const d = new Date(cls.dateschedule);
disp = String(d.getDate()).padStart(2, '0') + ' ' + months[d.toLocaleString('en-US', {
month: 'long'
})] + ' ' + d.getFullYear() + ' ' + String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
} else {
disp = 'Data non disponibile';
}
const occ = (typeof cls.occupied_seats !== 'undefined' && typeof cls.maxcapacity !== 'undefined') ? '(' + cls.occupied_seats + '/' + cls.maxcapacity + ')' : '(0/0)';
const cd = cls.iso_dateschedule ? new Date(cls.iso_dateschedule) : (cls.dateschedule ? new Date(cls.dateschedule) : new Date());
const sel = (!selectedSet && cd >= now) ? 'selected' : '';
if (!selectedSet && cd >= now) selectedSet = true;
opts += '<option value="' + cls.idserviceschedule + '" ' + sel + '>' + cls.servicename + ' - ' + disp + ' ' + occ + '</option>';
});
Swal.fire({
title: 'Riprogramma Lezione',
html: '<label for="class-select">Seleziona una lezione:</label><select id="class-select" class="form-control" style="width:100%;">' + opts + '</select><br><label for="is-reprogrammed">Segna come riprogrammata:</label><input type="checkbox" id="is-reprogrammed" checked>',
showCancelButton: true,
confirmButtonText: 'Riprogramma',
cancelButtonText: 'Annulla',
preConfirm: () => {
const sc = document.getElementById('class-select').value;
const ir = document.getElementById('is-reprogrammed').checked ? 'Y' : 'N';
if (!sc) {
Swal.showValidationMessage('Seleziona una lezione');
return false;
}
return {
idbookingclass,
idserviceschedule: sc,
is_reprogrammed: ir
};
}
}).then((result) => {
if (result.isConfirmed) {
$.post('reprogramclass.php', result.value).done(() => window.location.href = 'adminpanel.php?success=3').fail((xhr, status, error) => Swal.fire('Errore durante la riprogrammazione', status + ' - ' + error, 'error'));
}
});
},
error: function(xhr, status, error) {
Swal.fire('Errore durante il recupero delle lezioni', status + ' - ' + error, 'error');
}
});
});
// === Intercetta submit "Aggiungi partecipante" ===
$(document).on('submit', '.add-booking-form', function(e) {
e.preventDefault();
const form = $(this);
const userid = form.find('.userid').val();
const name = form.find('.name').val().trim();
const surname = form.find('.surname').val().trim();
if (!name || !surname) {
Swal.fire('Dati mancanti', 'Inserisci nome e cognome.', 'warning');
return;
}
const hasUser = userid && parseInt(userid) > 0;
// Controllo anti-duplicato PRIMA di aprire il modale:
// l'utente è già presente in questa classe?
if (hasUser) {
const panel = form.closest('.bookings-panel');
const already = panel.find('.booking-item[data-iduser="' + parseInt(userid) + '"]').length > 0;
if (already) {
Swal.fire(
'Già prenotato',
'Questa persona è già presente in questa classe.',
'info'
);
return;
}
}
if (hasUser) {
$.getJSON('get_user_orders.php', {
userid: userid
}).done(function(data) {
showBookingModal(form, data.orders || []);
}).fail(function() {
showBookingModal(form, []);
});
} else {
showBookingModal(form, []);
}
});
function showBookingModal(form, orders) {
const usableOrders = orders.filter(o => o.remaining > 0);
let orderOptions = '';
usableOrders.forEach(function(o) {
const exp = o.expireon ? ' - scade ' + o.expireon : '';
orderOptions += '<option value="' + o.idorderbook + '">' +
o.service_name + ' (' + o.remaining + '/' + o.tickets + ' residui)' + exp +
'</option>';
});
const hasUsableOrders = usableOrders.length > 0;
const d = new Date();
d.setMonth(d.getMonth() + 3);
const defExpiry = d.toISOString().slice(0, 10);
const html =
'<div style="text-align:left;">' +
'<label style="display:block;margin:8px 0;">' +
'<input type="radio" name="bkmode" value="scala" ' + (hasUsableOrders ? 'checked' : 'disabled') + '> ' +
'<strong>Scala da un pacchetto</strong>' + (hasUsableOrders ? '' : ' (nessun pacchetto con residui)') +
'</label>' +
'<select id="bk-order" class="form-control" style="margin:6px 0 14px;" ' + (hasUsableOrders ? '' : 'disabled') + '>' +
orderOptions +
'</select>' +
'<label style="display:block;margin:8px 0;">' +
'<input type="radio" name="bkmode" value="omaggio" ' + (hasUsableOrders ? '' : 'checked') + '> ' +
'<strong>Aggiungi come omaggio</strong> <span style="color:#6b7280;font-size:12px;">(non consuma pacchetti, non spostabile)</span>' +
'</label>' +
'<label style="display:block;margin:8px 0;">' +
'<input type="radio" name="bkmode" value="nuovo"> ' +
'<strong>Crea nuovo ordine</strong> <span style="color:#6b7280;font-size:12px;">(1 lezione)</span>' +
'</label>' +
'<div id="bk-nuovo-fields" style="display:none;margin:6px 0 0;padding-left:22px;">' +
'<label style="font-size:13px;">Scadenza ordine:</label>' +
'<input type="date" id="bk-expiry" class="form-control" value="' + defExpiry + '">' +
'</div>' +
'</div>';
Swal.fire({
title: 'Aggiungi partecipante',
html: html,
showCancelButton: true,
confirmButtonText: 'Inserisci',
cancelButtonText: 'Annulla',
confirmButtonColor: '#1ebf73',
didOpen: () => {
document.querySelectorAll('input[name="bkmode"]').forEach(function(r) {
r.addEventListener('change', function() {
document.getElementById('bk-nuovo-fields').style.display =
(this.value === 'nuovo') ? 'block' : 'none';
});
});
},
preConfirm: () => {
const mode = document.querySelector('input[name="bkmode"]:checked');
if (!mode) {
Swal.showValidationMessage('Seleziona una modalità');
return false;
}
const result = {
mode: mode.value
};
if (mode.value === 'scala') {
const sel = document.getElementById('bk-order').value;
if (!sel) {
Swal.showValidationMessage('Seleziona un pacchetto');
return false;
}
result.idorder = sel;
}
if (mode.value === 'nuovo') {
const exp = document.getElementById('bk-expiry').value;
if (!exp) {
Swal.showValidationMessage('Inserisci la scadenza');
return false;
}
result.expiry = exp;
}
return result;
}
}).then((res) => {
if (!res.isConfirmed) return;
submitBooking(form, res.value);
});
}
function submitBooking(form, choice) {
form.find('input[name="bkmode"]').remove();
form.find('input[name="new_expiry"]').remove();
$('<input>').attr({
type: 'hidden',
name: 'bkmode',
value: choice.mode
}).appendTo(form);
if (choice.mode === 'scala') {
form.find('input[name="idorder"]').val(choice.idorder);
} else if (choice.mode === 'omaggio') {
form.find('input[name="idorder"]').val('');
} else if (choice.mode === 'nuovo') {
form.find('input[name="idorder"]').val('');
$('<input>').attr({
type: 'hidden',
name: 'new_expiry',
value: choice.expiry
}).appendTo(form);
}
form.off('submit');
form[0].submit();
}
let searchTimeout;
$(document).on('input', '.surname, .name', function() {
clearTimeout(searchTimeout);
var form = $(this).closest('form');
var results = form.find('.search-results');
var firstName = form.find('.name').val().trim();
var lastName = form.find('.surname').val().trim();
if (firstName.length < 2 && lastName.length < 2) {
results.removeClass('show').empty();
return;
}
searchTimeout = setTimeout(function() {
$.ajax({
url: 'searchemail.php',
method: 'POST',
data: {
firstName: firstName,
lastName: lastName
},
dataType: 'json',
cache: false,
success: function(data) {
results.empty();
if (data.results && data.results.length > 0) {
data.results.forEach(function(u) {
var ini = ((u.first_name || '').charAt(0) + (u.last_name || '').charAt(0)).toUpperCase();
results.append('<div class="user-result" data-email="' + u.email + '" data-userid="' + u.id + '" data-firstname="' + u.first_name + '" data-lastname="' + u.last_name + '"><span class="avatar" style="width:32px;height:32px;font-size:12px;background:#2980b9;">' + ini + '</span><span><strong>' + u.first_name + ' ' + u.last_name + '</strong><br><small style="color:#6b7280;">' + u.email + '</small></span></div>');
});
results.addClass('show');
results.find('.user-result').on('click', function() {
form.find('.email').val($(this).data('email'));
form.find('.userid').val($(this).data('userid'));
form.find('.name').val($(this).data('firstname'));
form.find('.surname').val($(this).data('lastname'));
results.removeClass('show').empty();
});
} else {
results.append('<div class="user-result" style="color:#9ca3af;">Nessun utente trovato.</div>').addClass('show');
}
},
error: function() {
results.html('<div class="user-result" style="color:#c0392b;">Errore durante la ricerca.</div>').addClass('show');
}
});
}, 300);
});
});
</script>
</body>
</html>