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 FROM bookingclass
LEFT JOIN auth_users ON bookingclass.iduser = auth_users.id LEFT JOIN auth_users ON bookingclass.iduser = auth_users.id
WHERE bookingclass.idserviceschedule IN ($ph) WHERE bookingclass.idserviceschedule IN ($ph)
AND bookingclass.status IN ('booked', 'pending') AND bookingclass.status IN ('booked', 'pending', 'waiting')
ORDER BY auth_users.last_name, auth_users.first_name"; ORDER BY bookingclass.created_at, auth_users.last_name, auth_users.first_name";
$stmtBookings = $pdo->prepare($sqlBookings); $stmtBookings = $pdo->prepare($sqlBookings);
$stmtBookings->execute(array_values($scheduleIds)); $stmtBookings->execute(array_values($scheduleIds));
foreach ($stmtBookings->fetchAll() as $b) { foreach ($stmtBookings->fetchAll() as $b) {
@@ -575,8 +575,10 @@ $italianMonths = [
// separa confermati (booked) da in attesa (pending) // separa confermati (booked) da in attesa (pending)
$bookings = array_filter($allRows, fn($b) => $b['status'] === 'booked'); $bookings = array_filter($allRows, fn($b) => $b['status'] === 'booked');
$pendings = array_filter($allRows, fn($b) => $b['status'] === 'pending'); $pendings = array_filter($allRows, fn($b) => $b['status'] === 'pending');
$waitings = array_filter($allRows, fn($b) => $b['status'] === 'waiting');
$countPersons = count($bookings); // contatore = solo confermati $countPersons = count($bookings); // contatore = solo confermati
$countPending = count($pendings); $countPending = count($pendings);
$countWaiting = count($waitings);
$maxcap = (int) $sch['maxcapacity']; $maxcap = (int) $sch['maxcapacity'];
$isFull = $maxcap > 0 && $countPersons >= $maxcap; $isFull = $maxcap > 0 && $countPersons >= $maxcap;
$colorclass = $sch['colorclass'] ?: '#e6f3ff'; $colorclass = $sch['colorclass'] ?: '#e6f3ff';
@@ -599,6 +601,12 @@ $italianMonths = [
<?php echo $countPending; ?> in attesa <?php echo $countPending; ?> in attesa
</span> </span>
<?php endif; ?> <?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> </h3>
<p class="class-meta"><i class="far fa-clock me-1"></i><?php echo e($whenFmt); ?></p> <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> <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> </div>
<?php endif; ?> <?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"> <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> <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"> <form class="add-booking-form" action="inserisci_record.php" method="post">
+17 -4
View File
@@ -1,6 +1,7 @@
<?php <?php
require_once('include/headscript.php'); require_once('include/headscript.php');
require_once __DIR__ . '/class/mailer.php'; require_once __DIR__ . '/class/mailer.php';
require_once('include/waitinglist.php');
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\Exception;
@@ -55,11 +56,20 @@ if ($rowUser = $resUser->fetch_assoc()) {
} }
$stmtUser->close(); $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 // Inserisce la nuova prenotazione in bookingclass
$insertQuery = "INSERT INTO bookingclass (idserviceschedule, iduser, prevbookingstart, idprevserviceschedule, idservice, idorder, bookingstart, status) $insertQuery = "INSERT INTO bookingclass (idserviceschedule, iduser, prevbookingstart, idprevserviceschedule, idservice, idorder, bookingstart, status)
VALUES (?, ?, NULL, '0', ?, ?, ?, 'pending')"; VALUES (?, ?, NULL, '0', ?, ?, ?, ?)";
$stmt = $conn->prepare($insertQuery); $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()) { if (!$stmt->execute()) {
$stmt->close(); $stmt->close();
die("Errore durante l'inserimento della nuova prenotazione: " . $conn->error); die("Errore durante l'inserimento della nuova prenotazione: " . $conn->error);
@@ -78,9 +88,12 @@ $stmt->close();
// Connessione $bkngstm gestita globalmente: non la chiudiamo qui // 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 // Se non abbiamo un'email valida, salta l'invio ma completa comunque il flusso
if ($emailuser === '') { if ($emailuser === '') {
header("Location: userpanel.php?reprogram=Y"); header("Location: userpanel.php?$redirectFlag");
exit(); exit();
} }
@@ -168,5 +181,5 @@ try {
} }
// Esegui il reindirizzamento // Esegui il reindirizzamento
header("Location: userpanel.php?reprogram=Y"); header("Location: userpanel.php?$redirectFlag");
exit(); exit();
+7 -3
View File
@@ -507,7 +507,7 @@ $conn->close();
$mysqli = new mysqli($servername, $username, $password, $dbname); $mysqli = new mysqli($servername, $username, $password, $dbname);
$eventId = $bookedclass->getColumnVal("idserviceschedule"); $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 = $mysqli->prepare($bookingQuery);
$stmt->bind_param("i", $eventId); $stmt->bind_param("i", $eventId);
$stmt->execute(); $stmt->execute();
@@ -538,7 +538,7 @@ $conn->close();
<?php } elseif ($freeplace > 0) { ?> <?php } elseif ($freeplace > 0) { ?>
<div class="custom-date-box" style="background-color:#1ebf73"> <div class="custom-date-box" style="background-color:#1ebf73">
<?php } else { ?> <?php } else { ?>
<div class="custom-date-box" style="background-color:#9C9C9C"> <div class="custom-date-box" style="background-color:#d1698a">
<?php } ?> <?php } ?>
<div class="custom-day"><?php echo $dayInItalian; ?></div> <div class="custom-day"><?php echo $dayInItalian; ?></div>
<div class="custom-month"><?php echo $monthInItalian; ?></div> <div class="custom-month"><?php echo $monthInItalian; ?></div>
@@ -548,7 +548,7 @@ $conn->close();
<?php } elseif ($freeplace > 0) { ?> <?php } elseif ($freeplace > 0) { ?>
<div class="custom-event-details" style="border-left-color:<?php echo ($bookedclass->getColumnVal("colorclass")); ?>"> <div class="custom-event-details" style="border-left-color:<?php echo ($bookedclass->getColumnVal("colorclass")); ?>">
<?php } else { ?> <?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 } ?> <?php } ?>
<h2 class="custom-heading"><?php echo ($bookedclass->getColumnVal("servicename")); ?> -<?php echo $countPersons; ?>/<?php echo ($bookedclass->getColumnVal("maxcapacity")); ?></h2> <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> <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> <button class="booking-button"><i class="fa fa-check"></i> Sei già prenotata/o</button>
<?php } elseif ($freeplace > 0) { ?> <?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> <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 { ?> <?php } else { ?>
<button class="booking-button"><i class="fa fa-stop"></i> Classe Piena</button> <button class="booking-button"><i class="fa fa-stop"></i> Classe Piena</button>
<?php } ?> <?php } ?>
<?php } ?>
</div> </div>
</div> </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> <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 { ?> <?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 } ?> <?php } ?>
</div> </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> <span class="menu-item" data-key="t-email">Sommario Ordini</span>
</a> </a>
</li> </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> <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 // Funzione per confermare la cancellazione di un ordine e delle sue prenotazioni
function confirmDelete(id, deletePageUrl) { 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({ Swal.fire({
title: "Sei sicuro?", title: "Sei sicuro?",
text: "Questa prenotazione verrà cancellata definitivamente! Ricordati poi di riprogrammare la tua lezione!", 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>Numero Ticket</th>
<th>Scadenza</th> <th>Scadenza</th>
<th>Status</th> <th>Status</th>
<th>Azioni</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -388,6 +404,12 @@ if ($result_dayoff->num_rows > 0) {
<td><?php echo $order["nticket"]; ?></td> <td><?php echo $order["nticket"]; ?></td>
<td class="expiry-date" data-order-id="<?php echo $order['idorderbook']; ?>"><?php echo $order["expireon"]; ?></td> <td class="expiry-date" data-order-id="<?php echo $order['idorderbook']; ?>"><?php echo $order["expireon"]; ?></td>
<td><?php echo $order["status"]; ?></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> </tr>
<?php } ?> <?php } ?>
</tbody> </tbody>
@@ -407,6 +429,15 @@ if ($result_dayoff->num_rows > 0) {
<script src="assets/libs/simplebar/simplebar.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/libs/eva-icons/eva.min.js"></script>
<script src="assets/js/app.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> </body>
</html> </html>
+11 -5
View File
@@ -1,6 +1,7 @@
<?php <?php
require_once('include/headscript.php'); require_once('include/headscript.php');
require_once __DIR__ . '/class/mailer.php'; require_once __DIR__ . '/class/mailer.php';
require_once('include/waitinglist.php');
use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception; use PHPMailer\PHPMailer\Exception;
@@ -53,12 +54,15 @@ $stmt->bind_param("i", $idpreviousbooking);
$stmt->execute(); $stmt->execute();
$stmt->close(); $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( $stmt = $conn->prepare(
"INSERT INTO bookingclass (idserviceschedule, iduser, prevbookingstart, idprevserviceschedule, idservice, idorder, bookingstart, status) "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->execute();
$stmt->close(); $stmt->close();
@@ -83,9 +87,11 @@ $stmtUser->close();
$newtimeformat = date("d-m-Y H:i", strtotime($newtime)); $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 // Se manca l'email, completa comunque il flusso senza inviare
if ($emailuser === '') { if ($emailuser === '') {
header("Location: userpanel.php?reprogram=Y"); header("Location: userpanel.php?$redirectFlag");
exit(); exit();
} }
@@ -138,5 +144,5 @@ try {
error_log("Errore invio mail riprogrammazione (iduser $iduser): " . $mail->ErrorInfo); error_log("Errore invio mail riprogrammazione (iduser $iduser): " . $mail->ErrorInfo);
} }
header("Location: userpanel.php?reprogram=Y"); header("Location: userpanel.php?$redirectFlag");
exit(); exit();
+124
View File
@@ -75,6 +75,40 @@ $stmtPending->execute([
]); ]);
$pendingRows = $stmtPending->fetchAll(); $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 * 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> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
<?php } ?> <?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'])) { ?> <?php if (isset($_GET['deleted'])) { ?>
<div class="alert alert-success alert-dismissible fade show" role="alert"> <div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="mdi mdi-check-all me-2"></i> <i class="mdi mdi-check-all me-2"></i>
@@ -636,6 +691,58 @@ $italianMonths = [
</div> </div>
<?php endif; ?> <?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="row">
<div class="col-xl-12"> <div class="col-xl-12">
<div class="card"> <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)}`; const outlookCalendarLink = `webcal://outlook.live.com/calendar/0/deeplink/compose?path=/calendar/action/compose&subject=${encodeURIComponent(eventName)}&startdt=${encodeURIComponent(eventDate)}`;
window.open(outlookCalendarLink, '_blank'); 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>
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>