From 83af2e2d17014a6c9f7dde0548745ad0b863a2a9 Mon Sep 17 00:00:00 2001 From: solocla Date: Fri, 4 Sep 2026 09:00:30 +0200 Subject: [PATCH] fixed pages --- ...0260904062405_add_propagation_tracking.php | 30 + ...260904063209_fix_propagation_collation.php | 22 + public/admin-services.php | 63 + public/adminpanel.php | 77 +- public/bookingpanel.php | 89 +- public/bookingpanelorder.php | 1132 +++++++++-------- public/cancelpropagation.php | 36 + public/nextdateclass.php | 28 +- public/userpanel.php | 172 ++- 9 files changed, 1008 insertions(+), 641 deletions(-) create mode 100644 db/migrations/20260904062405_add_propagation_tracking.php create mode 100644 db/migrations/20260904063209_fix_propagation_collation.php create mode 100644 public/cancelpropagation.php diff --git a/db/migrations/20260904062405_add_propagation_tracking.php b/db/migrations/20260904062405_add_propagation_tracking.php new file mode 100644 index 0000000..0aed58e --- /dev/null +++ b/db/migrations/20260904062405_add_propagation_tracking.php @@ -0,0 +1,30 @@ +table('propagation', ['id' => 'idpropagation']); + $table->addColumn('idservice', 'integer') + ->addColumn('token', 'string', ['limit' => 64]) + ->addColumn('startdate', 'date', ['null' => true]) + ->addColumn('enddate', 'date', ['null' => true]) + ->addColumn('created_at', 'datetime', ['default' => 'CURRENT_TIMESTAMP']) + ->addIndex(['token'], ['unique' => true]) + ->addIndex(['idservice']) + ->create(); + + // Campo token sulle singole occorrenze del calendario + $schedule = $this->table('serviceschedule'); + if (!$schedule->hasColumn('propagation_token')) { + $schedule->addColumn('propagation_token', 'string', ['limit' => 64, 'null' => true]) + ->addIndex(['propagation_token']) + ->update(); + } + } +} diff --git a/db/migrations/20260904063209_fix_propagation_collation.php b/db/migrations/20260904063209_fix_propagation_collation.php new file mode 100644 index 0000000..551e897 --- /dev/null +++ b/db/migrations/20260904063209_fix_propagation_collation.php @@ -0,0 +1,22 @@ +execute( + "ALTER TABLE propagation CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" + ); + } + + public function down(): void + { + $this->execute( + "ALTER TABLE propagation CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" + ); + } +} diff --git a/public/admin-services.php b/public/admin-services.php index 0c5307a..5fa0656 100644 --- a/public/admin-services.php +++ b/public/admin-services.php @@ -69,6 +69,20 @@ $services = $pdo->query( LEFT JOIN servicecategory ON service.category = servicecategory.idservicecategory ORDER BY service.servicename" )->fetchAll(); + +// Propagazioni già effettuate, raggruppate per servizio +$propagationsByService = []; +$propRows = $pdo->query( + "SELECT p.idpropagation, p.idservice, p.token, p.startdate, p.enddate, p.created_at, + COUNT(s.idserviceschedule) AS occorrenze + FROM propagation p + LEFT JOIN serviceschedule s ON s.propagation_token = p.token + GROUP BY p.idpropagation + ORDER BY p.created_at DESC" +)->fetchAll(); +foreach ($propRows as $pr) { + $propagationsByService[$pr['idservice']][] = $pr; +} ?> @@ -227,6 +241,21 @@ $services = $pdo->query( + + + + + + diff --git a/public/adminpanel.php b/public/adminpanel.php index be1e437..30f262f 100644 --- a/public/adminpanel.php +++ b/public/adminpanel.php @@ -53,6 +53,16 @@ $stmtSchedule = $pdo->prepare($sqlSchedule); $stmtSchedule->execute([':start' => $currentMonthStart, ':end' => $currentMonthEnd]); $scheduleRows = $stmtSchedule->fetchAll(); +// Tipologie di classe presenti nel mese (per la barra filtri), con relativo colore +$classTypes = []; +foreach ($scheduleRows as $r) { + $name = $r['servicename'] ?? ''; + if ($name !== '' && !isset($classTypes[$name])) { + $classTypes[$name] = $r['colorclass'] ?: '#1ebf73'; + } +} +ksort($classTypes); + $bookingsBySchedule = []; $scheduleIds = array_filter(array_map(fn($r) => (int) $r['idserviceschedule'], $scheduleRows)); @@ -114,6 +124,46 @@ $italianMonths = [ margin: 10px 0 26px; } + .class-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + margin: 0 0 26px; + } + + .filter-chip { + display: inline-flex; + align-items: center; + gap: 7px; + border: 1px solid #e2e4e9; + background: #fff; + color: #374151; + font-size: 13px; + font-weight: 600; + padding: 7px 14px; + border-radius: 20px; + cursor: pointer; + transition: all 0.15s; + } + + .filter-chip:hover { + border-color: #1ebf73; + } + + .filter-chip.active { + background: #1ebf73; + border-color: #1ebf73; + color: #fff; + } + + .filter-dot { + width: 12px; + height: 12px; + border-radius: 50%; + border: 1px solid rgba(0, 0, 0, 0.1); + } + .month-navigation h2 { margin: 0; font-size: 22px; @@ -497,6 +547,18 @@ $italianMonths = [ + +
+ + $typeColor) : ?> + + +
+ +
@@ -519,12 +581,12 @@ $italianMonths = [ $isFull = $maxcap > 0 && $countPersons >= $maxcap; $colorclass = $sch['colorclass'] ?: '#e6f3ff'; ?> -
+
-
+

@@ -692,6 +754,17 @@ $italianMonths = [ } $(document).ready(function() { + // Filtro per tipologia di classe + $(document).on('click', '.filter-chip', function() { + var filter = $(this).data('filter'); + $('.filter-chip').removeClass('active'); + $(this).addClass('active'); + $('.class-card').each(function() { + var show = (filter === '__all__') || ($(this).data('type') === filter); + $(this).toggle(show); + }); + }); + // Scroll automatico alla classe di oggi o alla prima futura (function scrollToToday() { var cards = document.querySelectorAll('.class-card[data-date]'); diff --git a/public/bookingpanel.php b/public/bookingpanel.php index ac5659e..6858ef3 100644 --- a/public/bookingpanel.php +++ b/public/bookingpanel.php @@ -177,66 +177,70 @@ $conn->close(); - - - - - - - + .custom-month { + font-size: 18px; + text-transform: uppercase; + letter-spacing: 0.05em; + } + + .custom-event-details { + flex: 1; + display: flex; + flex-direction: column; + padding: 18px 22px; + background: #fff; + border-left: 20px solid #ccc; + /* colore impostato inline per ogni card in base allo stato */ + } + + .custom-heading { + font-size: 19px; + font-weight: 700; + color: #1f2937; + margin: 0 0 4px; + } + + .custom-paragraph { + color: #6b7280; + font-size: 14px; + margin-bottom: 3px; + } + + .custom-actions { + display: none; + flex-direction: row; + gap: 8px; + flex-wrap: wrap; + margin-top: 10px; + } + + .custom-card.expanded .custom-actions { + display: flex; + } + + .custom-action-button { + border: none; + border-radius: 8px; + padding: 7px 14px; + font-size: 13px; + font-weight: 600; + background: #eef2f7; + color: #374151; + cursor: pointer; + transition: filter 0.15s; + } + + .custom-action-button:hover { + filter: brightness(0.94); + } + + @media (max-width: 768px) { + .custom-card { + flex-direction: column; + } + + .custom-date-box { + flex: 0 0 auto; + width: 100%; + flex-direction: row; + gap: 10px; + padding: 12px; + } + + .custom-day { + font-size: 30px; + } + + .custom-month { + font-size: 18px; + } + } + + + + + + + + - -
+ +
- -
-
+ + +
+
+
+ - - - -
-
-
- + + + +
+
+
+ + +
- - -
- -
-
-
-
-
-
-
-
- -
-
- -
-
- 0) { - echo ''; -} -?> - - -
- +
+ + +
+
+
+
+ +
+
+
+
+
-
- +
+
+ 0) { + echo ''; + } + ?> + + +
+ +
+
+ +
+
+ + - -
- +
- - - - - - "Gennaio", - "February" => "Febbraio", - "March" => "Marzo", - "April" => "Aprile", - "May" => "Maggio", - "June" => "Giugno", - "July" => "Luglio", - "August" => "Agosto", - "September" => "Settembre", - "October" => "Ottobre", - "November" => "Novembre", - "December" => "Dicembre" -]; - -?> -
- - - - - -

- - - -
- - - - - -TotalRows == 0) { - echo "

Classi non presenti per questo mese

"; -} else { - while (!$bookedclass->atEnd()) { - $wa_startindex = $bookedclass->Index; -?> - getColumnVal("dateschedule") -$dateschedule = $bookedclass->getColumnVal("dateschedule"); - -// Converti la data in un oggetto DateTime -$dateObj = new DateTime($dateschedule); - -// Estrai il giorno e il mese in italiano -$dayInItalian = $dateObj->format("d"); -$monthInItalian = $dateObj->format("F"); - -// Mappa dei nomi dei mesi in italiano -$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" -]; - -// Sostituisci il nome del mese con la versione italiana -$monthInItalian = $italianMonths[$monthInItalian]; -?> -getColumnVal("dateschedule") -$dateschedule = $bookedclass->getColumnVal("dateschedule"); - -// Converti la data in un oggetto DateTime -$dateObj = new DateTime($dateschedule); - -// Formatta la data nel nuovo formato desiderato -$newDateFormat = $dateObj->format("d-m-Y H:i"); - - // Calculate the time difference in hours - $currentTime = new DateTime(); - $classTime = new DateTime($dateschedule); - $timeDifference = $classTime->diff($currentTime); - $hoursDifference = $timeDifference->h + $timeDifference->days * 24; - - // Check if the time difference is less than 6 hours - $timetocancel=$optionquery->getColumnVal("maxbeforetimecancell"); - $canBeDeleted = ($hoursDifference >= $timetocancel); + -?> -getColumnVal("idserviceschedule"); - $bookingQuery = "SELECT iduser FROM bookingclass + + "Gennaio", + "February" => "Febbraio", + "March" => "Marzo", + "April" => "Aprile", + "May" => "Maggio", + "June" => "Giugno", + "July" => "Luglio", + "August" => "Agosto", + "September" => "Settembre", + "October" => "Ottobre", + "November" => "Novembre", + "December" => "Dicembre" + ]; + + ?> +
+ + + + + +

+ + + +
+ + + + + + TotalRows == 0) { + echo "

Classi non presenti per questo mese

"; + } else { + while (!$bookedclass->atEnd()) { + $wa_startindex = $bookedclass->Index; + ?> + getColumnVal("dateschedule") + $dateschedule = $bookedclass->getColumnVal("dateschedule"); + + // Converti la data in un oggetto DateTime + $dateObj = new DateTime($dateschedule); + + // Estrai il giorno e il mese in italiano + $dayInItalian = $dateObj->format("d"); + $monthInItalian = $dateObj->format("F"); + + // Mappa dei nomi dei mesi in italiano + $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" + ]; + + // Sostituisci il nome del mese con la versione italiana + $monthInItalian = $italianMonths[$monthInItalian]; + ?> + getColumnVal("dateschedule") + $dateschedule = $bookedclass->getColumnVal("dateschedule"); + + // Converti la data in un oggetto DateTime + $dateObj = new DateTime($dateschedule); + + // Formatta la data nel nuovo formato desiderato + $newDateFormat = $dateObj->format("d-m-Y H:i"); + + // Calculate the time difference in hours + $currentTime = new DateTime(); + $classTime = new DateTime($dateschedule); + $timeDifference = $classTime->diff($currentTime); + $hoursDifference = $timeDifference->h + $timeDifference->days * 24; + + // Check if the time difference is less than 6 hours + $timetocancel = $optionquery->getColumnVal("maxbeforetimecancell"); + $canBeDeleted = ($hoursDifference >= $timetocancel); + + + + ?> + getColumnVal("idserviceschedule"); + $bookingQuery = "SELECT iduser FROM bookingclass WHERE bookingclass.idserviceschedule = $eventId AND bookingclass.status='booked'"; - $bookingResult = $mysqli->query($bookingQuery); - - // Calcola il numero di persone prenotate - $countPersons = $bookingResult->num_rows; + $bookingResult = $mysqli->query($bookingQuery); -?> -getColumnVal("servicename") -$cardservice = $bookedclass->getColumnVal("idservice"); + // Calcola il numero di persone prenotate + $countPersons = $bookingResult->num_rows; -// Verifica se il valore di $cardservice è presente nell'array $idassociateservices -if (in_array($cardservice, $idassociateservices)) { - // Il valore è presente, esegui il codice successivo - - ?> - getColumnVal("maxcapacity"); - $freeplace=$maxcapacity-$countPersons; - ?> - - getColumnVal("idserviceschedule"); // Sostituisci con il tuo valore -$iduser = $iduserlog; // Sostituisci con il tuo valore + ?> + query($query); + // Esempio: Simula il valore di $bookedclass->getColumnVal("servicename") + $cardservice = $bookedclass->getColumnVal("idservice"); -if ($resultcheck->num_rows > 0) { - $alreadybooked='Y'; // record found -} else { - $alreadybooked='N'; -} + // Verifica se il valore di $cardservice è presente nell'array $idassociateservices + if (in_array($cardservice, $idassociateservices)) { + // Il valore è presente, esegui il codice successivo + + ?> + getColumnVal("maxcapacity"); + $freeplace = $maxcapacity - $countPersons; + ?> + + getColumnVal("idserviceschedule"); // Sostituisci con il tuo valore + $iduser = $iduserlog; // Sostituisci con il tuo valore + + // Query per verificare l'esistenza del record + $query = "SELECT * FROM bookingclass WHERE idserviceschedule = $idcheckservice AND iduser = $iduser"; + $resultcheck = $conn->query($query); + + if ($resultcheck->num_rows > 0) { + $alreadybooked = 'Y'; // record found + } else { + $alreadybooked = 'N'; + } -?> - - -
- - - - - -
- 0) { ?>
-
-
-
-
- -
- 0) { ?> -
"> -
-

getColumnVal("servicename")); ?> -/getColumnVal("maxcapacity")); ?>

-

Quando:

-

Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga

- getColumnVal("idserviceschedule"); ?> -getColumnVal("idservice"); ?> - - -
- - - 0) { - ?> - - - - -
- - - -
- - -
- - - - - moveNext(); - } -} - -$bookedclass->moveFirst(); // Ritorna all'inizio del recordset -unset($wa_startindex); -unset($wa_repeatcount); -?> - - + ?> - - - -
-
- +
- -
- -
- - - + +
+ 0) { ?>
+
+
+
+
+ +
+ 0) { ?> +
"> +
+

getColumnVal("servicename")); ?> -/getColumnVal("maxcapacity")); ?>

+

Quando:

+

Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga

+ getColumnVal("idserviceschedule"); ?> + getColumnVal("idservice"); ?> + - +
+ + + 0) { + ?> + + + + +
- - - - - - - +
+ + +
+ + + + + moveNext(); + } + } + + $bookedclass->moveFirst(); // Ritorna all'inizio del recordset + unset($wa_startindex); + unset($wa_repeatcount); + ?> + + + + + + + +
+
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/cancelpropagation.php b/public/cancelpropagation.php new file mode 100644 index 0000000..4db6925 --- /dev/null +++ b/public/cancelpropagation.php @@ -0,0 +1,36 @@ +getConnection(); + +$token = $_GET['token'] ?? ''; +$confirm = ($_GET['confirm'] ?? '') === 'Y'; + +if ($token === '') { + header('Location: admin-services.php'); + exit; +} + +// Conta le prenotazioni utente collegate alle lezioni di questa propagazione +$stmt = $pdo->prepare( + "SELECT COUNT(*) FROM bookingclass b + JOIN serviceschedule s ON b.idserviceschedule = s.idserviceschedule + WHERE s.propagation_token = :token" +); +$stmt->execute([':token' => $token]); +$prenotazioni = (int) $stmt->fetchColumn(); + +// Se ci sono prenotazioni e non è ancora arrivata la conferma esplicita -> chiedi +if ($prenotazioni > 0 && !$confirm) { + header('Location: admin-services.php?message=confirmdelete&token=' . urlencode($token) . '&count=' . $prenotazioni); + exit; +} + +// Procedi con la cancellazione +$stmt = $pdo->prepare("DELETE FROM serviceschedule WHERE propagation_token = :token"); +$stmt->execute([':token' => $token]); + +$stmt = $pdo->prepare("DELETE FROM propagation WHERE token = :token"); +$stmt->execute([':token' => $token]); + +header('Location: admin-services.php?message=propdeleted'); +exit; diff --git a/public/nextdateclass.php b/public/nextdateclass.php index e21bfd1..c255599 100644 --- a/public/nextdateclass.php +++ b/public/nextdateclass.php @@ -55,6 +55,19 @@ $daysUntilNext = ($desiredDayValue + 7 - $dayOfWeekStart) % 7; $currentTs = $startTs + $daysUntilNext * 86400; $dateEnd = date('Y-m-d', $endTs); +// Token univoco della propagazione + record riepilogativo +$token = bin2hex(random_bytes(16)); +$insertPropagation = $pdo->prepare( + "INSERT INTO propagation (idservice, token, startdate, enddate) + VALUES (:idservice, :token, :startdate, :enddate)" +); +$insertPropagation->execute([ + ':idservice' => $idservice, + ':token' => $token, + ':startdate' => date('Y-m-d', $startTs), + ':enddate' => $dateEnd, +]); + // --- Statement preparati una sola volta e riusati nel loop --- $checkClass = $pdo->prepare( "SELECT idserviceschedule FROM serviceschedule @@ -65,8 +78,8 @@ $checkDayoff = $pdo->prepare( "SELECT iddayoff FROM dayoff WHERE dayoffdate = :dayoffdate LIMIT 1" ); $insertSchedule = $pdo->prepare( - "INSERT INTO serviceschedule (idservice, dateschedule, scheduleday, startingtime, durationtime) - VALUES (:idservice, :dateschedule, :scheduleday, :startingtime, :durationtime)" + "INSERT INTO serviceschedule (idservice, dateschedule, scheduleday, startingtime, durationtime, propagation_token) + VALUES (:idservice, :dateschedule, :scheduleday, :startingtime, :durationtime, :propagation_token)" ); $inserted = 0; @@ -88,11 +101,12 @@ while (date('Y-m-d', $currentTs) <= $dateEnd) { if (!$alreadyScheduled && !$isDayoff) { $insertSchedule->execute([ - ':idservice' => $idservice, - ':dateschedule' => $datetimeSchedule, - ':scheduleday' => $dayclass, - ':startingtime' => $timeclass, - ':durationtime' => $durationtime, + ':idservice' => $idservice, + ':dateschedule' => $datetimeSchedule, + ':scheduleday' => $dayclass, + ':startingtime' => $timeclass, + ':durationtime' => $durationtime, + ':propagation_token' => $token, ]); $inserted++; } diff --git a/public/userpanel.php b/public/userpanel.php index f36ba87..6463217 100644 --- a/public/userpanel.php +++ b/public/userpanel.php @@ -58,6 +58,23 @@ $stmtBooked->execute([ ]); $bookedRows = $stmtBooked->fetchAll(); +// Lezioni in attesa di conferma (pending) dell'utente, nel mese visualizzato +$sqlPending = "SELECT bookingclass.*, service.servicename, service.colorclass, 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 = 'pending' + AND serviceschedule.dateschedule BETWEEN :monthStart AND DATE_ADD(:monthEnd, INTERVAL 1 DAY) + ORDER BY serviceschedule.dateschedule"; +$stmtPending = $pdo->prepare($sqlPending); +$stmtPending->execute([ + ':iduser' => $iduserlogin, + ':monthStart' => $currentMonthStart, + ':monthEnd' => $currentMonthEnd, +]); +$pendingRows = $stmtPending->fetchAll(); + /* ------------------------------------------------------------------------- * 3) Somma dei ticket acquistati dall'utente * ---------------------------------------------------------------------- */ @@ -175,67 +192,97 @@ $italianMonths = [