diff --git a/public/userarea/future_sessions.php b/public/userarea/future_sessions.php index 1c7fcb7..7e32d99 100644 --- a/public/userarea/future_sessions.php +++ b/public/userarea/future_sessions.php @@ -22,6 +22,19 @@ if (!$school) { } $school_id = $school['id']; $school_name = $school['name']; +// === LISTA UTENTI DELLA SCUOLA (per dropdown "Aggiungi partecipante") === +// NOTA: uso user_schools (come nel tuo progetto). Se la tabella si chiama diversamente, cambia qui. +$stmtUsers = $pdo->prepare(" + SELECT au.id, au.first_name, au.last_name, au.email + FROM user_schools us + JOIN auth_users au ON au.id = us.user_id + WHERE us.school_id = ? + AND us.status = 'active' + ORDER BY au.first_name, au.last_name +"); +$stmtUsers->execute([$school_id]); +$schoolUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC); + // === GESTIONE AZIONI === $feedback = ''; @@ -34,7 +47,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Cancellazione lezione if ($action === 'delete_session') { $session_id = (int)($_POST['session_id'] ?? 0); - $send_email = !empty($_POST['send_email']); + $send_email = isset($_POST['send_email']) && $_POST['send_email'] === '1'; + error_log("DELETE_SESSION: session_id={$session_id} send_email_POST=" . var_export($_POST['send_email'] ?? null, true)); + error_log("DELETE_SESSION: send_email_BOOL=" . var_export($send_email, true)); + $stmt = $pdo->prepare(" SELECT cs.id, cs.session_date, cs.start_time, cs.end_time, @@ -54,7 +70,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { SELECT au.email, au.first_name FROM session_bookings sb JOIN auth_users au ON sb.user_id = au.id - WHERE sb.session_id = ? AND sb.status IN ('booked','attended','rescheduled') + WHERE sb.session_id = ? "); $stmt2->execute([$session_id]); $recipients = $stmt2->fetchAll(PDO::FETCH_ASSOC); @@ -114,7 +130,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { SELECT au.email, au.first_name FROM session_bookings sb JOIN auth_users au ON sb.user_id = au.id - WHERE sb.session_id = ? AND sb.status IN ('booked','attended','rescheduled') + WHERE sb.session_id = ? "); $stmt->execute([$session_id]); $recipients = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -160,11 +176,93 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { "; } + // Aggiungi partecipante alla lezione + elseif ($action === 'add_booking') { + $session_id = (int)($_POST['session_id'] ?? 0); + $user_id = (int)($_POST['user_id'] ?? 0); + + if ($session_id <= 0 || $user_id <= 0) { + $feedback = '
Dati non validi.
'; + } else { + // 1) Verifica sessione appartiene alla scuola + capienza + $stmt = $pdo->prepare(" + SELECT cs.id, ct.max_capacity, + (SELECT COUNT(*) FROM session_bookings sb + WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled') + ) AS booked_count + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE cs.id = ? AND c.school_id = ? + LIMIT 1 + "); + $stmt->execute([$session_id, $school_id]); + $info = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$info) { + $feedback = '
Lezione non trovata o non autorizzata.
'; + } else { + // 2) Verifica utente appartiene alla scuola + $stmt = $pdo->prepare(" + SELECT 1 + FROM user_schools + WHERE user_id = ? AND school_id = ? AND status = 'active' + LIMIT 1 + "); + $stmt->execute([$user_id, $school_id]); + $isMember = (bool)$stmt->fetchColumn(); + + if (!$isMember) { + $feedback = '
Utente non associato a questa scuola.
'; + } else { + // 3) Verifica già prenotato (qualsiasi status) + $stmt = $pdo->prepare("SELECT 1 FROM session_bookings WHERE session_id = ? AND user_id = ? LIMIT 1"); + $stmt->execute([$session_id, $user_id]); + $already = (bool)$stmt->fetchColumn(); + + if ($already) { + $feedback = '
Questo utente è già presente nella lezione.
'; + } else { + // 4) Verifica capienza (se max_capacity valorizzato) + $max = (int)($info['max_capacity'] ?? 0); + $cnt = (int)($info['booked_count'] ?? 0); + + if ($max > 0 && $cnt >= $max) { + $feedback = '
Lezione piena: capienza massima raggiunta.
'; + } else { + // 5) Inserimento (con fallback se non hai created_at/updated_at) + try { + $stmtIns = $pdo->prepare(" + INSERT INTO session_bookings (session_id, user_id, status, created_at, updated_at) + VALUES (?, ?, 'booked', NOW(), NOW()) + "); + $stmtIns->execute([$session_id, $user_id]); + } catch (PDOException $e) { + // fallback SOLO se l'errore è "Unknown column" + if (stripos($e->getMessage(), 'Unknown column') !== false) { + $stmtIns = $pdo->prepare(" + INSERT INTO session_bookings (session_id, user_id, status) + VALUES (?, ?, 'booked') + "); + $stmtIns->execute([$session_id, $user_id]); + } else { + throw $e; + } + } + + $feedback = '
Partecipante aggiunto alla lezione!
'; + } + } + } + } + } + } + // Marca come persa elseif ($action === 'mark_lost') { $booking_id = (int)($_POST['booking_id'] ?? 0); - $stmt = $pdo->prepare("UPDATE session_bookings SET status = 'lost' WHERE id = ? AND session_id IN (SELECT cs.id FROM class_sessions cs JOIN class_types ct ON cs.class_type_id = ct.id JOIN classes c ON ct.class_id = c.id WHERE c.school_id = ?)"); + $stmt = $pdo->prepare("UPDATE session_bookings SET status = 'missed' WHERE id = ? AND session_id IN (SELECT cs.id FROM class_sessions cs JOIN class_types ct ON cs.class_type_id = ct.id JOIN classes c ON ct.class_id = c.id WHERE c.school_id = ?)"); $stmt->execute([$booking_id, $school_id]); $feedback = '
Presenza segnata come persa.
'; } @@ -192,9 +290,9 @@ $stmt = $pdo->prepare(" t.first_name, t.last_name, (SELECT COUNT(*) FROM session_bookings sb WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled')) AS booked_count, - (SELECT GROUP_CONCAT(CONCAT(sb.id,'|||',au.first_name,' ',au.last_name,'|||',au.email,'|||',COALESCE(au.phone,''),'|||',sb.status) SEPARATOR ';;;') - FROM session_bookings sb JOIN auth_users au ON sb.user_id = au.id - WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled','lost')) AS booked_students_details + (SELECT GROUP_CONCAT(CONCAT(sb.id,'|||',au.id,'|||',au.first_name,' ',au.last_name,'|||',au.email,'|||',COALESCE(au.phone,''),'|||',sb.status) SEPARATOR ';;;') + FROM session_bookings sb JOIN auth_users au ON sb.user_id = au.id + WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled','missed')) AS booked_students_details FROM class_sessions cs JOIN class_types ct ON cs.class_type_id = ct.id JOIN classes c ON ct.class_id = c.id @@ -224,6 +322,9 @@ foreach ($sessions as $s) { Lezioni Future - <?php echo htmlspecialchars($school_name); ?> + + + @@ -291,11 +392,20 @@ foreach ($sessions as $s) { Non assegnato'; ?> - + - - + - + + @@ -538,6 +708,28 @@ Grazie per la comprensione, + + + \ No newline at end of file diff --git a/public/userarea/orders.php b/public/userarea/orders.php index 177ffaa..1bcc114 100644 --- a/public/userarea/orders.php +++ b/public/userarea/orders.php @@ -11,7 +11,212 @@ $stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ?"); $stmt->execute([$iduserlogin]); $school = $stmt->fetch(); if (!$school) die("Scuola non trovata."); -$school_id = $school['id']; +$school_id = (int)$school['id']; + + +// === UTENTI DELLA SCUOLA (dropdown cercabile) === +$stmtUsers = $pdo->prepare(" + SELECT au.id, au.first_name, au.last_name, au.email + FROM user_schools us + JOIN auth_users au ON au.id = us.user_id + WHERE us.school_id = ? + AND us.status = 'active' + ORDER BY au.first_name, au.last_name +"); +$stmtUsers->execute([$school_id]); +$schoolUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC); + +// === PRODOTTI ATTIVI === +$stmtProd = $pdo->prepare(" + SELECT id, name, type + FROM products + WHERE school_id = ? AND status = 'active' + ORDER BY name +"); +$stmtProd->execute([$school_id]); +$products = $stmtProd->fetchAll(PDO::FETCH_ASSOC); + +// === VARIAZIONI ATTIVE (per filtro lato JS) === +$variations = []; +if (!empty($products)) { + $prodIds = array_column($products, 'id'); + $in = implode(',', array_fill(0, count($prodIds), '?')); + + $stmtVar = $pdo->prepare(" + SELECT id, product_id, name, price, duration_days, max_entries, max_recoveries + FROM product_variations + WHERE product_id IN ($in) + AND status = 'active' + ORDER BY product_id, name + "); + $stmtVar->execute($prodIds); + $variations = $stmtVar->fetchAll(PDO::FETCH_ASSOC); +} + +// === CLASSI ATTIVE === +$stmtClasses = $pdo->prepare(" + SELECT id, name + FROM classes + WHERE school_id = ? AND status = 'active' + ORDER BY name +"); +$stmtClasses->execute([$school_id]); +$classes = $stmtClasses->fetchAll(PDO::FETCH_ASSOC); + +// === CLASS TYPES (filtrati lato JS per class_id) === +$stmtCT = $pdo->prepare(" + SELECT ct.id, ct.class_id, ct.level, ct.day_of_week + FROM class_types ct + JOIN classes c ON ct.class_id = c.id + WHERE c.school_id = ? + ORDER BY c.name, ct.day_of_week, ct.level +"); +$stmtCT->execute([$school_id]); +$classTypes = $stmtCT->fetchAll(PDO::FETCH_ASSOC); + +$feedback = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'add_order_manual') { + $user_id = (int)($_POST['user_id'] ?? 0); + $product_id = (int)($_POST['product_id'] ?? 0); + $variation_id = (int)($_POST['variation_id'] ?? 0); + $payment_method = trim($_POST['payment_method'] ?? ''); + $status = trim($_POST['status'] ?? 'completed'); + + $price = isset($_POST['price']) ? (float)str_replace(',', '.', $_POST['price']) : 0.0; + $total_entries = ($_POST['total_entries'] ?? '') !== '' ? (int)$_POST['total_entries'] : null; + $available_entries = ($_POST['available_entries'] ?? '') !== '' ? (int)$_POST['available_entries'] : null; + + $activation_date = $_POST['activation_date'] ?? date('Y-m-d'); + $expiration_date = $_POST['expiration_date'] ?? null; + $expiration_date = ($expiration_date === '') ? null : $expiration_date; + + $class_id = ($_POST['class_id'] ?? '') !== '' ? (int)$_POST['class_id'] : null; + $class_type_id = ($_POST['class_type_id'] ?? '') !== '' ? (int)$_POST['class_type_id'] : null; + + // Validazioni base + if ($user_id <= 0 || $product_id <= 0 || $payment_method === '') { + $feedback = '
Compila utente, prodotto e metodo di pagamento.
'; + } else { + // 1) utente appartiene alla scuola + $stmt = $pdo->prepare("SELECT 1 FROM user_schools WHERE school_id = ? AND user_id = ? AND status='active' LIMIT 1"); + $stmt->execute([$school_id, $user_id]); + if (!$stmt->fetchColumn()) { + $feedback = '
Utente non associato alla scuola.
'; + } else { + // 2) prodotto appartiene alla scuola + $stmt = $pdo->prepare("SELECT id FROM products WHERE id=? AND school_id=? AND status='active' LIMIT 1"); + $stmt->execute([$product_id, $school_id]); + if (!$stmt->fetchColumn()) { + $feedback = '
Prodotto non valido.
'; + } else { + // 3) se variation_id valorizzato, deve appartenere al prodotto + $varMeta = null; + if ($variation_id > 0) { + $stmt = $pdo->prepare("SELECT id, price, duration_days, max_entries, max_recoveries FROM product_variations WHERE id=? AND product_id=? AND status='active' LIMIT 1"); + $stmt->execute([$variation_id, $product_id]); + $varMeta = $stmt->fetch(PDO::FETCH_ASSOC); + if (!$varMeta) { + $feedback = '
Variazione non valida.
'; + } + } + + if ($feedback === '') { + // 4) class / class_type se presenti devono essere coerenti e della scuola + if ($class_id) { + $stmt = $pdo->prepare("SELECT 1 FROM classes WHERE id=? AND school_id=? LIMIT 1"); + $stmt->execute([$class_id, $school_id]); + if (!$stmt->fetchColumn()) { + $feedback = '
Classe non valida.
'; + } + } + if ($class_type_id) { + $stmt = $pdo->prepare(" + SELECT 1 + FROM class_types ct + JOIN classes c ON ct.class_id = c.id + WHERE ct.id=? AND c.school_id=? + LIMIT 1 + "); + $stmt->execute([$class_type_id, $school_id]); + if (!$stmt->fetchColumn()) { + $feedback = '
Class type non valido.
'; + } + } + } + + if ($feedback === '') { + // Autocomplete intelligente da variazione se non inserito + if ($varMeta) { + if ($price <= 0) $price = (float)$varMeta['price']; + if ($total_entries === null && $varMeta['max_entries'] !== null) $total_entries = (int)$varMeta['max_entries']; + if ($available_entries === null && $total_entries !== null) $available_entries = $total_entries; + + if (!$expiration_date && !empty($varMeta['duration_days'])) { + $d = new DateTime($activation_date); + $d->modify('+' . (int)$varMeta['duration_days'] . ' days'); + $expiration_date = $d->format('Y-m-d'); + } + $available_recoveries = ($varMeta['max_recoveries'] !== null) ? (int)$varMeta['max_recoveries'] : null; + } else { + $available_recoveries = null; + } + + // order_number progressivo per scuola + try { + $pdo->beginTransaction(); + + $stmt = $pdo->prepare("SELECT COALESCE(MAX(order_number),0) + 1 FROM orders WHERE school_id=? FOR UPDATE"); + $stmt->execute([$school_id]); + $nextOrderNumber = (int)$stmt->fetchColumn(); + + $stmtIns = $pdo->prepare(" + INSERT INTO orders + (order_number, school_id, user_id, product_id, variation_id, class_id, class_type_id, + created_at, payment_method, price, status, + total_entries, available_entries, available_recoveries, + expiration_date, activation_date) + VALUES + (?, ?, ?, ?, ?, ?, ?, + NOW(), ?, ?, ?, + ?, ?, ?, + ?, ?) + "); + + $stmtIns->execute([ + $nextOrderNumber, + $school_id, + $user_id, + $product_id, + ($variation_id > 0 ? $variation_id : null), + $class_id, + $class_type_id, + $payment_method, + $price, + $status, + $total_entries, + $available_entries, + $available_recoveries, + $expiration_date, + $activation_date + ]); + + $pdo->commit(); + + $feedback = '
Ordine manuale inserito con successo!
'; + } catch (Exception $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + $feedback = '
Errore inserimento ordine: ' . htmlspecialchars($e->getMessage()) . '
'; + } + } + } + } + } + } +} // Recupera tutti gli ordini con tutti i dati necessari $stmt = $pdo->prepare(" @@ -55,6 +260,7 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); Ordini - <?php echo htmlspecialchars($school['name']); ?> + @@ -77,13 +283,15 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); Gestione Ordini -
-
+ +
@@ -170,11 +378,154 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
+ + @@ -222,6 +573,101 @@ $orders = $stmt->fetchAll(PDO::FETCH_ASSOC); }); }); + + \ No newline at end of file diff --git a/public/userarea/propagation_manager.php b/public/userarea/propagation_manager.php new file mode 100644 index 0000000..cea7fd1 --- /dev/null +++ b/public/userarea/propagation_manager.php @@ -0,0 +1,404 @@ +getConnection(); + +if (!isset($iduserlogin)) { + die("Errore: ID utente non definito."); +} + +/* +|-------------------------------------------------------------------------- +| 1) Load school owned by current user +|-------------------------------------------------------------------------- +*/ +$stmt = $pdo->prepare("SELECT id, name, logo FROM schools WHERE owner_id = ? LIMIT 1"); +$stmt->execute([(int)$iduserlogin]); +$school = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$school) { + die("Errore: Nessuna scuola trovata per questo account."); +} + +$school_id = (int)$school['id']; +$school_name = $school['name'] ?? 'School'; + +/* +|-------------------------------------------------------------------------- +| 2) Helpers +|-------------------------------------------------------------------------- +*/ +function itDayLabel(string $dow): string +{ + $map = [ + 'monday' => 'Lunedì', + 'tuesday' => 'Martedì', + 'wednesday' => 'Mercoledì', + 'thursday' => 'Giovedì', + 'friday' => 'Venerdì', + 'saturday' => 'Sabato', + 'sunday' => 'Domenica' + ]; + return $map[$dow] ?? ucfirst($dow); +} + +function safeTime($t): string +{ + // expects "HH:MM:SS" or "HH:MM" + if (!$t) return '—'; + return substr((string)$t, 0, 5); +} + +/* +|-------------------------------------------------------------------------- +| 3) Handle delete block propagation (bookings + sessions for that block) +|-------------------------------------------------------------------------- +| Block key = propagation_id + class_type_id + start_time + end_time +|-------------------------------------------------------------------------- +*/ +$feedback = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = $_POST['action'] ?? ''; + + if ($action === 'delete_propagation_block') { + $propagation_id = trim($_POST['propagation_id'] ?? ''); + $class_type_id = (int)($_POST['class_type_id'] ?? 0); + $start_time = trim($_POST['start_time'] ?? ''); + $end_time = trim($_POST['end_time'] ?? ''); + + if ($propagation_id === '' || $class_type_id <= 0 || $start_time === '' || $end_time === '') { + $feedback = '
+ Parametri non validi per la cancellazione della propagazione. + +
'; + } else { + try { + $pdo->beginTransaction(); + + // Delete bookings for sessions matching this block (only this school) + $stmtDelBookings = $pdo->prepare(" + DELETE sb + FROM session_bookings sb + JOIN class_sessions cs ON sb.session_id = cs.id + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE c.school_id = ? + AND cs.propagation_id = ? + AND cs.class_type_id = ? + AND cs.start_time = ? + AND cs.end_time = ? + "); + $stmtDelBookings->execute([$school_id, $propagation_id, $class_type_id, $start_time, $end_time]); + $deletedBookings = $stmtDelBookings->rowCount(); + + // Delete sessions for this block (only this school) + $stmtDelSessions = $pdo->prepare(" + DELETE cs + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + WHERE c.school_id = ? + AND cs.propagation_id = ? + AND cs.class_type_id = ? + AND cs.start_time = ? + AND cs.end_time = ? + "); + $stmtDelSessions->execute([$school_id, $propagation_id, $class_type_id, $start_time, $end_time]); + $deletedSessions = $stmtDelSessions->rowCount(); + + $pdo->commit(); + + $feedback = "
+ Propagazione rimossa con successo. +
+ Codice: " . htmlspecialchars($propagation_id) . " | + Sessioni eliminate: {$deletedSessions} | + Prenotazioni eliminate: {$deletedBookings} + + +
"; + } catch (Throwable $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + $feedback = "
+ Errore durante la cancellazione: " . htmlspecialchars($e->getMessage()) . " + +
"; + } + } + } +} + +/* +|-------------------------------------------------------------------------- +| 4) Load propagation blocks (GROUPED - no single sessions list) +|-------------------------------------------------------------------------- +| One row per propagation_id + class_type + time range. +|-------------------------------------------------------------------------- +*/ +$stmt = $pdo->prepare(" + SELECT + cs.propagation_id, + ct.id AS class_type_id, + c.name AS class_name, + ct.level, + ct.day_of_week, + cs.start_time, + cs.end_time, + MIN(cs.session_date) AS range_start, + MAX(cs.session_date) AS range_end, + COUNT(DISTINCT cs.id) AS sessions_count, + SUM(CASE WHEN sb.id IS NULL THEN 0 ELSE 1 END) AS bookings_count + FROM class_sessions cs + JOIN class_types ct ON cs.class_type_id = ct.id + JOIN classes c ON ct.class_id = c.id + LEFT JOIN session_bookings sb ON sb.session_id = cs.id + WHERE c.school_id = ? + AND cs.propagation_id IS NOT NULL + AND cs.propagation_id <> '' + GROUP BY + cs.propagation_id, + ct.id, + c.name, + ct.level, + ct.day_of_week, + cs.start_time, + cs.end_time + ORDER BY range_start DESC, c.name ASC, cs.start_time ASC +"); +$stmt->execute([$school_id]); +$blocks = $stmt->fetchAll(PDO::FETCH_ASSOC); + +/* +|-------------------------------------------------------------------------- +| 5) Optional quick counters for a nicer header +|-------------------------------------------------------------------------- +*/ +$totalBlocks = count($blocks); +$totalSessions = 0; +$totalBookings = 0; +foreach ($blocks as $b) { + $totalSessions += (int)($b['sessions_count'] ?? 0); + $totalBookings += (int)($b['bookings_count'] ?? 0); +} +?> + + + + + + + + Propagazioni - <?php echo htmlspecialchars($school_name); ?> + + + + + +
+ + + +
+
+ + +
+
+
+
+ Logo +
+
+

Propagazioni -

+

+ Vista “a blocchi”: codice propagazione + classe + giorno/ora + range (da–a). + Nessun elenco di singole sessioni. +

+
+ +
+ + +
+ Blocchi: + Sessioni generate: + Prenotazioni totali: +
+
+
+ + + + +
+
+
Nessuna propagazione trovata.
+

Quando generi lezioni propagate, appariranno qui raggruppate per blocchi.

+
+
+ + + +
+
+
Blocchi di Propagazione
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodiceClasseGiornoOraDaASessioniPrenotazioniAzioni
+ +
ID tipo:
+
+
+ + Livello: + +
+ +
+
+
+
+ + + + + + +
+
+ + +
+ + + + + \ No newline at end of file diff --git a/public/userarea/school_dashboard.php b/public/userarea/school_dashboard.php index 5f9949c..75674ba 100644 --- a/public/userarea/school_dashboard.php +++ b/public/userarea/school_dashboard.php @@ -829,6 +829,7 @@ $daily_sessions = $stmt->fetchAll();
Calendario Lezioni + Lista Propagazioni Modifica Profilo
@@ -983,7 +984,6 @@ $daily_sessions = $stmt->fetchAll(); Sala Insegnante Stato - Prenotati Azioni