branch waiting list with update

This commit is contained in:
2026-09-16 16:27:47 +02:00
parent 7fe956ba9e
commit 434eb8bc48
15 changed files with 687 additions and 22 deletions
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddWaitingListSupport extends AbstractMigration
{
public function change(): void
{
$table = $this->table('bookingclass');
$table
->addColumn('created_at', 'timestamp', [
'default' => 'CURRENT_TIMESTAMP',
'null' => false,
'after' => 'cancellation_token',
])
->update();
}
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddWaitingListEnabledToOption extends AbstractMigration
{
public function change(): void
{
$this->table('option')
->addColumn('waitinglist_enabled', 'char', [
'limit' => 1,
'default' => 'N',
'null' => false,
'after' => 'maxbeforetimecancell',
])
->update();
}
}
+40 -2
View File
@@ -74,8 +74,8 @@ if (!empty($scheduleIds)) {
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";
AND bookingclass.status IN ('booked', 'pending', 'waiting')
ORDER BY bookingclass.created_at, auth_users.last_name, auth_users.first_name";
$stmtBookings = $pdo->prepare($sqlBookings);
$stmtBookings->execute(array_values($scheduleIds));
foreach ($stmtBookings->fetchAll() as $b) {
@@ -575,8 +575,10 @@ $italianMonths = [
// 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');
$waitings = array_filter($allRows, fn($b) => $b['status'] === 'waiting');
$countPersons = count($bookings); // contatore = solo confermati
$countPending = count($pendings);
$countWaiting = count($waitings);
$maxcap = (int) $sch['maxcapacity'];
$isFull = $maxcap > 0 && $countPersons >= $maxcap;
$colorclass = $sch['colorclass'] ?: '#e6f3ff';
@@ -599,6 +601,12 @@ $italianMonths = [
<?php echo $countPending; ?> in attesa
</span>
<?php endif; ?>
<?php if ($countWaiting > 0) : ?>
<span class="capacity-pill" style="background:#fbeef3;color:#d1698a;">
<i class="fas fa-list-ol" style="font-size:11px;"></i>
<?php echo $countWaiting; ?> in coda
</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>
@@ -676,6 +684,36 @@ $italianMonths = [
</div>
<?php endif; ?>
<?php if ($countWaiting > 0) : ?>
<div class="bookings-header" style="margin-top:18px;">
<span class="title" style="color:#d1698a;"><i class="fas fa-list-ol me-1"></i> Lista d'attesa (<?php echo $countWaiting; ?>)</span>
</div>
<div class="booking-list">
<?php $qpos = 1;
foreach ($waitings 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:#fdf4f8;border-color:#f0d6e2;" 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">
<span style="display:inline-block;min-width:22px;font-weight:800;color:#d1698a;"><?php echo $qpos; ?>°</span>
<?php echo e($fullName); ?>
</span>
<span class="status-chip" style="background:#fbeef3;color:#d1698a;">In coda</span>
</div>
<div class="booking-actions">
<button class="mini-btn remove" onclick="handleRemoveClick(<?php echo $idbc; ?>)"><i class="fas fa-trash"></i></button>
</div>
</div>
<?php $qpos++;
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">
+17 -4
View File
@@ -1,6 +1,7 @@
<?php
require_once('include/headscript.php');
require_once __DIR__ . '/class/mailer.php';
require_once('include/waitinglist.php');
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
@@ -55,11 +56,20 @@ if ($rowUser = $resUser->fetch_assoc()) {
}
$stmtUser->close();
// Decide lo stato: pending se c'è posto, waiting se la classe è piena
$bookingStatus = decideBookingStatus($conn, $idnewbooking);
if ($bookingStatus === 'full') {
// Classe piena e lista d'attesa disattivata: non inseriamo nulla
header("Location: userpanel.php?full=Y");
exit();
}
// Inserisce la nuova prenotazione in bookingclass
$insertQuery = "INSERT INTO bookingclass (idserviceschedule, iduser, prevbookingstart, idprevserviceschedule, idservice, idorder, bookingstart, status)
VALUES (?, ?, NULL, '0', ?, ?, ?, 'pending')";
VALUES (?, ?, NULL, '0', ?, ?, ?, ?)";
$stmt = $conn->prepare($insertQuery);
$stmt->bind_param("iiiis", $idnewbooking, $iduser, $idservicenew, $idorder, $newtime);
$stmt->bind_param("iiiiss", $idnewbooking, $iduser, $idservicenew, $idorder, $newtime, $bookingStatus);
if (!$stmt->execute()) {
$stmt->close();
die("Errore durante l'inserimento della nuova prenotazione: " . $conn->error);
@@ -78,9 +88,12 @@ $stmt->close();
// Connessione $bkngstm gestita globalmente: non la chiudiamo qui
// Parametro di ritorno diverso se finito in lista d'attesa
$redirectFlag = ($bookingStatus === 'waiting') ? 'waiting=Y' : 'reprogram=Y';
// Se non abbiamo un'email valida, salta l'invio ma completa comunque il flusso
if ($emailuser === '') {
header("Location: userpanel.php?reprogram=Y");
header("Location: userpanel.php?$redirectFlag");
exit();
}
@@ -168,5 +181,5 @@ try {
}
// Esegui il reindirizzamento
header("Location: userpanel.php?reprogram=Y");
header("Location: userpanel.php?$redirectFlag");
exit();
+7 -3
View File
@@ -507,7 +507,7 @@ $conn->close();
$mysqli = new mysqli($servername, $username, $password, $dbname);
$eventId = $bookedclass->getColumnVal("idserviceschedule");
$bookingQuery = "SELECT iduser FROM bookingclass WHERE bookingclass.idserviceschedule = ? AND bookingclass.status='booked'";
$bookingQuery = "SELECT iduser FROM bookingclass WHERE bookingclass.idserviceschedule = ? AND bookingclass.status IN ('booked','pending')";
$stmt = $mysqli->prepare($bookingQuery);
$stmt->bind_param("i", $eventId);
$stmt->execute();
@@ -538,7 +538,7 @@ $conn->close();
<?php } elseif ($freeplace > 0) { ?>
<div class="custom-date-box" style="background-color:#1ebf73">
<?php } else { ?>
<div class="custom-date-box" style="background-color:#9C9C9C">
<div class="custom-date-box" style="background-color:#d1698a">
<?php } ?>
<div class="custom-day"><?php echo $dayInItalian; ?></div>
<div class="custom-month"><?php echo $monthInItalian; ?></div>
@@ -548,7 +548,7 @@ $conn->close();
<?php } elseif ($freeplace > 0) { ?>
<div class="custom-event-details" style="border-left-color:<?php echo ($bookedclass->getColumnVal("colorclass")); ?>">
<?php } else { ?>
<div class="custom-event-details" style="background-color:#CDCDCD;border-left-color:#9C9C9C">
<div class="custom-event-details" style="background-color:#fbeef3;border-left-color:#d1698a">
<?php } ?>
<h2 class="custom-heading"><?php echo ($bookedclass->getColumnVal("servicename")); ?> -<?php echo $countPersons; ?>/<?php echo ($bookedclass->getColumnVal("maxcapacity")); ?></h2>
<p class="custom-paragraph">Quando: <?php echo $newDateFormat; ?></p>
@@ -560,9 +560,13 @@ $conn->close();
<button class="booking-button"><i class="fa fa-check"></i> Sei già prenotata/o</button>
<?php } elseif ($freeplace > 0) { ?>
<a href="rebookandgo.php?idpreviousbooking=<?php echo $idpreviousbooking; ?>&idservicenew=<?php echo $idservicenew; ?>&idnewbooking=<?php echo $idnewbooking; ?>&iduser=<?php echo $iduserlogin; ?>"><button class="booking-button"><i class="fas fa-arrow-circle-right"></i> Riprogramma Classe</button></a>
<?php } else { ?>
<?php if ($optionquery->getColumnVal("waitinglist_enabled") === 'Y') { ?>
<a href="rebookandgo.php?idpreviousbooking=<?php echo $idpreviousbooking; ?>&idservicenew=<?php echo $idservicenew; ?>&idnewbooking=<?php echo $idnewbooking; ?>&iduser=<?php echo $iduserlogin; ?>"><button class="booking-button" style="background-color:#d1698a;color:#fff;"><i class="fas fa-hourglass-half"></i> Mettiti in lista d'attesa</button></a>
<?php } else { ?>
<button class="booking-button"><i class="fa fa-stop"></i> Classe Piena</button>
<?php } ?>
<?php } ?>
</div>
</div>
</div>
+1 -1
View File
@@ -742,7 +742,7 @@ $conn->close();
?>
<a href="bookandgo.php?idpreviousbooking=<?php echo '0'; ?>&idorder=<?php echo $idorder; ?>&idservicenew=<?php echo $idservicenew; ?>&idnewbooking=<?php echo $idnewbooking; ?>&iduser=<?php echo $iduserlogin; ?>" ?><button class="booking-button"><i class="fas fa-arrow-circle-right"></i> Riprogramma Classe</button></a>
<?php } else { ?>
<button class="booking-button"><i class="fa fa-stop"></i> Classe Piena</button>
<a href="bookandgo.php?idpreviousbooking=0&idorder=<?php echo $idorder; ?>&idservicenew=<?php echo $idservicenew; ?>&idnewbooking=<?php echo $idnewbooking; ?>&iduser=<?php echo $iduserlogin; ?>"><button class="booking-button" style="background-color:#f0ad4e;color:#fff;"><i class="fas fa-hourglass-half"></i> Mettiti in lista d'attesa</button></a>
<?php } ?>
</div>
+43
View File
@@ -0,0 +1,43 @@
<?php
$conn = new mysqli($servername, $username, $password, $dbname);
require_once('include/headscript.php'); // per avere le credenziali DB, se sono lì
// Se le credenziali sono in headscript.php, sposta la connessione DOPO il require.
// Adatta questa parte a come sono definite $servername ecc. nelle tue altre pagine.
if ($conn->connect_error) {
die("Connessione fallita: " . $conn->connect_error);
}
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header("Location: riepilogo.php?message=error_delete");
exit();
}
$idorder = (int) $_GET['id'];
$conn->begin_transaction();
try {
// 1. Cancella tutte le prenotazioni di questo ordine
$stmt1 = $conn->prepare("DELETE FROM bookingclass WHERE idorder = ?");
$stmt1->bind_param("i", $idorder);
$stmt1->execute();
$stmt1->close();
// 2. Cancella l'ordine
$stmt2 = $conn->prepare("DELETE FROM orderbook WHERE idorderbook = ?");
$stmt2->bind_param("i", $idorder);
$stmt2->execute();
$stmt2->close();
$conn->commit();
header("Location: riepilogo.php?message=success_delete");
exit();
} catch (Exception $e) {
$conn->rollback();
header("Location: riepilogo.php?message=error_delete");
exit();
}
$conn->close();
+6
View File
@@ -120,6 +120,12 @@
<span class="menu-item" data-key="t-email">Sommario Ordini</span>
</a>
</li>
<li>
<a href="optionsschool.php" class="">
<i class="bx bx-receipt icon nav-icon"></i>
<span class="menu-item" data-key="t-email">Opzioni Scuola</span>
</a>
</li>
<li class="menu-title" data-key="t-applications">Template</li>
+111
View File
@@ -0,0 +1,111 @@
<?php
/**
* Helper per la gestione della lista d'attesa (waiting list).
*/
/**
* La scuola ha attivato la lista d'attesa?
* Legge l'unico record della tabella option.
*/
function isWaitingListEnabled(mysqli $conn): bool
{
$res = $conn->query("SELECT waitinglist_enabled FROM `option` LIMIT 1");
if ($res && ($row = $res->fetch_assoc())) {
return $row['waitinglist_enabled'] === 'Y';
}
return false;
}
/**
* Capienza, posti impegnati (booked + pending) e posti liberi di una classe.
*
* @return array{maxcapacity:int, occupied:int, free:int}
*/
function getClassOccupancy(mysqli $conn, int $idserviceschedule): array
{
// Capienza (da service via serviceschedule)
$cap = 0;
$stmt = $conn->prepare(
"SELECT s.maxcapacity
FROM serviceschedule ss
JOIN service s ON ss.idservice = s.idservice
WHERE ss.idserviceschedule = ?"
);
$stmt->bind_param("i", $idserviceschedule);
$stmt->execute();
$res = $stmt->get_result();
if ($rowCap = $res->fetch_assoc()) {
$cap = (int) $rowCap['maxcapacity'];
}
$stmt->close();
// Posti impegnati: booked + pending
$occupied = 0;
$stmt = $conn->prepare(
"SELECT COUNT(*) AS occupied
FROM bookingclass
WHERE idserviceschedule = ?
AND status IN ('booked', 'pending')"
);
$stmt->bind_param("i", $idserviceschedule);
$stmt->execute();
$res = $stmt->get_result();
if ($rowOcc = $res->fetch_assoc()) {
$occupied = (int) $rowOcc['occupied'];
}
$stmt->close();
$free = $cap - $occupied;
return [
'maxcapacity' => $cap,
'occupied' => $occupied,
'free' => $free < 0 ? 0 : $free,
];
}
/**
* Decide lo stato di una nuova prenotazione:
* - 'pending' -> c'è posto (flusso normale, insegnante approva)
* - 'waiting' -> classe piena E waiting list attiva
* - 'full' -> classe piena E waiting list disattivata (NON prenotabile)
*/
function decideBookingStatus(mysqli $conn, int $idserviceschedule): string
{
$occ = getClassOccupancy($conn, $idserviceschedule);
if ($occ['free'] > 0) {
return 'pending';
}
return isWaitingListEnabled($conn) ? 'waiting' : 'full';
}
/**
* Posizione (1-based) di un utente nella coda waiting di una classe.
* 0 se non è in coda.
*/
function getWaitingPosition(mysqli $conn, int $idserviceschedule, int $iduser): int
{
$stmt = $conn->prepare(
"SELECT COUNT(*) AS pos
FROM bookingclass
WHERE idserviceschedule = ?
AND status = 'waiting'
AND created_at <= (
SELECT created_at FROM bookingclass
WHERE idserviceschedule = ? AND iduser = ? AND status = 'waiting'
ORDER BY created_at ASC LIMIT 1
)"
);
$stmt->bind_param("iii", $idserviceschedule, $idserviceschedule, $iduser);
$stmt->execute();
$res = $stmt->get_result();
$pos = 0;
if ($row = $res->fetch_assoc()) {
$pos = (int) $row['pos'];
}
$stmt->close();
return $pos;
}
+26
View File
@@ -0,0 +1,26 @@
<?php
require_once('include/headscript.php');
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
header("Location: userpanel.php");
exit();
}
$iduserlogin = (int) $iduserlogin;
$idbookingclass = isset($_GET['id']) ? (int) $_GET['id'] : 0;
if ($idbookingclass > 0) {
// Cancella solo se è una riga waiting DELL'utente loggato (sicurezza)
$stmt = $conn->prepare(
"DELETE FROM bookingclass
WHERE idbookingclass = ? AND iduser = ? AND status = 'waiting'"
);
$stmt->bind_param("ii", $idbookingclass, $iduserlogin);
$stmt->execute();
$stmt->close();
}
$conn->close();
header("Location: userpanel.php?leftwaiting=Y");
exit();
+33
View File
@@ -0,0 +1,33 @@
<?php
require_once('include/headscript.php');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header("Location: options.php");
exit();
}
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
header("Location: options.php?message=error");
exit();
}
// Checkbox non spuntata = nessun invio -> default 'N'
$waitingEnabled = (isset($_POST['waitinglist_enabled']) && $_POST['waitinglist_enabled'] === 'Y') ? 'Y' : 'N';
$maxCancel = isset($_POST['maxbeforetimecancell']) ? (string) (int) $_POST['maxbeforetimecancell'] : '0';
// Aggiorna l'unico record di option (idption = 1)
$stmt = $conn->prepare("UPDATE `option` SET waitinglist_enabled = ?, maxbeforetimecancell = ? WHERE idption = 1");
$stmt->bind_param("ss", $waitingEnabled, $maxCancel);
if ($stmt->execute()) {
$stmt->close();
$conn->close();
header("Location: optionsschool.php?message=success");
exit();
} else {
$stmt->close();
$conn->close();
header("Location: optionsschool.php?message=error");
exit();
}
+190
View File
@@ -0,0 +1,190 @@
<?php
require_once('include/headscript.php');
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connessione fallita: " . $conn->connect_error);
}
function e($value): string
{
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
$message = $_GET['message'] ?? 'n';
// Carica l'unico record di option
$opt = $conn->query("SELECT * FROM `option` LIMIT 1")->fetch_assoc();
$conn->close();
$waitingEnabled = ($opt['waitinglist_enabled'] ?? 'N') === 'Y';
$maxCancel = $opt['maxbeforetimecancell'] ?? '0';
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8" />
<title>YogiBook - Opzioni</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
.admin-card {
border: none;
border-radius: 14px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.06);
}
.admin-card .card-body {
padding: 26px 30px;
}
.section-title {
font-size: 15px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: #6b7280;
margin-bottom: 4px;
}
.section-lead {
color: #9ca3af;
font-size: 14px;
margin-bottom: 22px;
}
.option-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 0;
border-bottom: 1px solid #f0f0f0;
}
.option-row:last-child {
border-bottom: none;
}
.option-label {
font-weight: 600;
color: #1f2937;
font-size: 15px;
}
.option-help {
color: #9ca3af;
font-size: 13px;
margin-top: 3px;
}
.form-switch .form-check-input {
width: 3em;
height: 1.5em;
cursor: pointer;
}
.save-bar {
margin-top: 24px;
text-align: right;
}
</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">Opzioni</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 ($message === 'success') : ?>
<div class="alert alert-success" role="alert">Impostazioni salvate con successo.</div>
<?php elseif ($message === 'error') : ?>
<div class="alert alert-danger" role="alert">Errore durante il salvataggio.</div>
<?php endif; ?>
<div class="row">
<div class="col-lg-8">
<div class="card admin-card">
<div class="card-body">
<div class="section-title">Impostazioni prenotazioni</div>
<p class="section-lead">Configura il comportamento del sistema di prenotazione.</p>
<form action="options_save.php" method="POST">
<div class="option-row">
<div>
<div class="option-label">Lista d'attesa</div>
<div class="option-help">Se attiva, quando una classe è piena gli utenti possono mettersi in coda. Alla liberazione di un posto verranno gestiti in ordine di arrivo.</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="waitinglist_enabled" name="waitinglist_enabled" value="Y" <?php echo $waitingEnabled ? 'checked' : ''; ?>>
</div>
</div>
<div class="option-row">
<div>
<div class="option-label">Ore minime per l'annullamento</div>
<div class="option-help">Quante ore prima dell'inizio di una lezione è ancora possibile annullarla.</div>
</div>
<div style="width:120px;">
<input type="number" min="0" class="form-control" name="maxbeforetimecancell" value="<?php echo e($maxCancel); ?>">
</div>
</div>
<div class="save-bar">
<button type="submit" class="btn btn-success"><i class="fas fa-save me-1"></i> Salva impostazioni</button>
</div>
</form>
</div>
</div>
</div>
</div>
</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>
</body>
</html>
+33 -2
View File
@@ -172,8 +172,23 @@ if ($result_dayoff->num_rows > 0) {
});
}
// Funzione per confermare la cancellazione
function confirmDelete(id, deletePageUrl) {
// Funzione per confermare la cancellazione di un ordine e delle sue prenotazioni
window.confirmDeleteOrder = function(id) {
Swal.fire({
title: "Sei sicuro?",
text: "Verranno cancellati l'ordine e TUTTE le prenotazioni collegate a questa persona. L'azione è irreversibile!",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d33",
cancelButtonColor: "#3085d6",
confirmButtonText: "Sì, cancella tutto!",
cancelButtonText: "Annulla"
}).then((result) => {
if (result.isConfirmed) {
window.location.href = `deleteorder.php?id=${id}`;
}
});
}
Swal.fire({
title: "Sei sicuro?",
text: "Questa prenotazione verrà cancellata definitivamente! Ricordati poi di riprogrammare la tua lezione!",
@@ -371,6 +386,7 @@ if ($result_dayoff->num_rows > 0) {
<th>Numero Ticket</th>
<th>Scadenza</th>
<th>Status</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@@ -388,6 +404,12 @@ if ($result_dayoff->num_rows > 0) {
<td><?php echo $order["nticket"]; ?></td>
<td class="expiry-date" data-order-id="<?php echo $order['idorderbook']; ?>"><?php echo $order["expireon"]; ?></td>
<td><?php echo $order["status"]; ?></td>
<td>
<button type="button" class="btn btn-sm btn-danger"
onclick="confirmDeleteOrder(<?php echo $order['idorderbook']; ?>)">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
<?php } ?>
</tbody>
@@ -407,6 +429,15 @@ if ($result_dayoff->num_rows > 0) {
<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>
<?php if ($message == 'success_delete') { ?>
<script>
Swal.fire('Fatto!', 'Ordine e prenotazioni cancellati.', 'success');
</script>
<?php } elseif ($message == 'error_delete') { ?>
<script>
Swal.fire('Errore!', 'Errore durante la cancellazione.', 'error');
</script>
<?php } ?>
</body>
</html>
+11 -5
View File
@@ -1,6 +1,7 @@
<?php
require_once('include/headscript.php');
require_once __DIR__ . '/class/mailer.php';
require_once('include/waitinglist.php');
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
@@ -53,12 +54,15 @@ $stmt->bind_param("i", $idpreviousbooking);
$stmt->execute();
$stmt->close();
// Inserisci la nuova prenotazione (pending)
// Decide lo stato: pending se c'è posto, waiting se la classe è piena
$bookingStatus = decideBookingStatus($conn, $idnewbooking);
// Inserisci la nuova prenotazione
$stmt = $conn->prepare(
"INSERT INTO bookingclass (idserviceschedule, iduser, prevbookingstart, idprevserviceschedule, idservice, idorder, bookingstart, status)
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')"
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->bind_param("iisiiss", $idnewbooking, $iduser, $prevBookingStart, $idPrevServiceSchedule, $idservicenew, $globalidorder, $newtime);
$stmt->bind_param("iisiisss", $idnewbooking, $iduser, $prevBookingStart, $idPrevServiceSchedule, $idservicenew, $globalidorder, $newtime, $bookingStatus);
$stmt->execute();
$stmt->close();
@@ -83,9 +87,11 @@ $stmtUser->close();
$newtimeformat = date("d-m-Y H:i", strtotime($newtime));
$redirectFlag = ($bookingStatus === 'waiting') ? 'waiting=Y' : 'reprogram=Y';
// Se manca l'email, completa comunque il flusso senza inviare
if ($emailuser === '') {
header("Location: userpanel.php?reprogram=Y");
header("Location: userpanel.php?$redirectFlag");
exit();
}
@@ -138,5 +144,5 @@ try {
error_log("Errore invio mail riprogrammazione (iduser $iduser): " . $mail->ErrorInfo);
}
header("Location: userpanel.php?reprogram=Y");
header("Location: userpanel.php?$redirectFlag");
exit();
+124
View File
@@ -75,6 +75,40 @@ $stmtPending->execute([
]);
$pendingRows = $stmtPending->fetchAll();
// Lezioni in lista d'attesa (waiting) dell'utente, nel mese visualizzato
$sqlWaiting = "SELECT bookingclass.*, service.servicename, service.colorclass, service.maxcapacity, serviceschedule.dateschedule
FROM bookingclass
LEFT JOIN service ON bookingclass.idservice = service.idservice
LEFT JOIN serviceschedule ON bookingclass.idserviceschedule = serviceschedule.idserviceschedule
WHERE bookingclass.iduser = :iduser
AND bookingclass.status = 'waiting'
AND serviceschedule.dateschedule BETWEEN :monthStart AND DATE_ADD(:monthEnd, INTERVAL 1 DAY)
ORDER BY serviceschedule.dateschedule";
$stmtWaiting = $pdo->prepare($sqlWaiting);
$stmtWaiting->execute([
':iduser' => $iduserlogin,
':monthStart' => $currentMonthStart,
':monthEnd' => $currentMonthEnd,
]);
$waitingRows = $stmtWaiting->fetchAll();
// Posizione in coda per ciascuna lezione waiting (1-based, ordine per created_at)
$waitingPositions = [];
foreach ($waitingRows as $wrow) {
$sqlPos = "SELECT COUNT(*) AS pos
FROM bookingclass
WHERE idserviceschedule = :sched
AND status = 'waiting'
AND created_at <= :mycreated";
$stmtPos = $pdo->prepare($sqlPos);
$stmtPos->execute([
':sched' => (int) $wrow['idserviceschedule'],
':mycreated' => $wrow['created_at'],
]);
$rowPos = $stmtPos->fetch();
$waitingPositions[(int) $wrow['idbookingclass']] = (int) ($rowPos['pos'] ?? 0);
}
/* -------------------------------------------------------------------------
* 3) Somma dei ticket acquistati dall'utente
* ---------------------------------------------------------------------- */
@@ -541,6 +575,27 @@ $italianMonths = [
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<?php if (isset($_GET['waiting'])) { ?>
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<i class="fas fa-hourglass-half me-2"></i>
La classe era piena: sei stata/o inserita/o in <strong>lista d'attesa</strong>. Se si libera un posto verrai avvisata/o.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<?php if (isset($_GET['leftwaiting'])) { ?>
<div class="alert alert-info alert-dismissible fade show" role="alert">
<i class="fas fa-check me-2"></i>
Sei uscita/o dalla lista d'attesa.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<?php if (isset($_GET['full'])) { ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="fas fa-ban me-2"></i>
La classe è al completo e la lista d'attesa non è attiva. Prova con un altro orario.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<?php if (isset($_GET['deleted'])) { ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="mdi mdi-check-all me-2"></i>
@@ -636,6 +691,58 @@ $italianMonths = [
</div>
<?php endif; ?>
<?php if (!empty($waitingRows)) : ?>
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-body">
<h5 style="color:#8a6d00;margin-bottom:14px;">
<i class="fas fa-hourglass-half me-1"></i> In lista d'attesa
</h5>
<?php foreach ($waitingRows as $wrow) :
$wDateObj = new DateTime($wrow['dateschedule']);
$wDay = $wDateObj->format("d");
$wMonth = $italianMonths[$wDateObj->format("F")];
$wWhen = $wDateObj->format("d-m-Y H:i");
$wColor = $wrow['colorclass'] ?? '#1ebf73';
$wName = $wrow['servicename'] ?? '';
$wPos = $waitingPositions[(int) $wrow['idbookingclass']] ?? 0;
?>
<div class="custom-card pending-card">
<div class="custom-date-box" style="background:#9C9C9C;">
<div class="custom-day"><?php echo e($wDay); ?></div>
<div class="custom-month"><?php echo e($wMonth); ?></div>
</div>
<div class="custom-event-details" style="border-left-color:<?php echo e($wColor); ?>">
<h2 class="custom-heading">
<?php echo e($wName); ?>
<span style="display:inline-block;background:#eee;color:#555;font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;margin-left:6px;vertical-align:middle;">In coda</span>
</h2>
<p class="custom-paragraph">Quando: <?php echo e($wWhen); ?></p>
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
<?php if ($wPos > 0) : ?>
<p class="custom-paragraph" style="font-weight:700;color:#8a6d00;">
<i class="fas fa-list-ol me-1"></i> La tua posizione in coda: <?php echo e($wPos); ?>
</p>
<?php endif; ?>
<p class="custom-paragraph" style="font-size:13px;color:#9ca3af;">
Se si libera un posto verrai avvisata/o. Non serve fare nulla.
</p>
<div style="margin-top:8px;">
<button class="custom-action-button" style="background:#fbeef3;color:#d1698a;"
onclick="confirmLeaveWaiting(<?php echo (int) $wrow['idbookingclass']; ?>)">
<i class="fas fa-times-circle"></i> Esci dalla lista d'attesa
</button>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<?php endif; ?>
<div class="row">
<div class="col-xl-12">
<div class="card">
@@ -741,6 +848,23 @@ $italianMonths = [
const outlookCalendarLink = `webcal://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&subject=${encodeURIComponent(eventName)}&startdt=${encodeURIComponent(eventDate)}`;
window.open(outlookCalendarLink, '_blank');
}
function confirmLeaveWaiting(id) {
Swal.fire({
title: "Uscire dalla lista d'attesa?",
text: "Verrai rimossa/o dalla coda per questa lezione.",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#d1698a",
cancelButtonColor: "#3085d6",
confirmButtonText: "Sì, esci",
cancelButtonText: "Annulla"
}).then((result) => {
if (result.isConfirmed) {
window.location.href = "leavewaiting.php?id=" + id;
}
});
}
</script>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>