added statistic and remove froms school

This commit is contained in:
Claudio 2026-01-16 21:40:19 +01:00
parent 39fb15c649
commit f911b82716
10 changed files with 371 additions and 63 deletions

View File

@ -0,0 +1,51 @@
<?php
session_start();
include('include/headscript.php');
header('Content-Type: application/json');
if (!isset($_SESSION['iduserlogin'])) {
echo json_encode(['success' => false, 'message' => 'Non autorizzato']);
exit;
}
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$booking_id = (int)($_POST['booking_id'] ?? 0);
$user_id = (int)$_SESSION['iduserlogin'];
if ($booking_id <= 0) {
echo json_encode(['success' => false, 'message' => 'ID prenotazione non valido']);
exit;
}
// Verifica che la prenotazione appartenga all'utente e sia cancellabile
$stmt = $pdo->prepare("
SELECT sb.id
FROM session_bookings sb
JOIN class_sessions cs ON sb.session_id = cs.id
WHERE sb.id = ?
AND sb.user_id = ?
AND sb.status = 'booked'
AND cs.session_date > DATE_ADD(NOW(), INTERVAL 24 HOUR)
");
$stmt->execute([$booking_id, $user_id]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'Prenotazione non trovata, non tua, già annullata o non più cancellabile (entro 24 ore)']);
exit;
}
// Aggiorna status a 'cancelled'
$stmt = $pdo->prepare("
UPDATE session_bookings
SET status = 'cancelled',
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
");
$success = $stmt->execute([$booking_id]);
echo json_encode([
'success' => $success,
'message' => $success ? 'Prenotazione annullata con successo' : 'Errore durante l\'aggiornamento'
]);

View File

@ -50,10 +50,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['certificate']) && $_
(user_id, filename, stored_path, document_name, expiry_date, notes, uploaded_at) (user_id, filename, stored_path, document_name, expiry_date, notes, uploaded_at)
VALUES (?, ?, ?, ?, ?, ?, NOW()) VALUES (?, ?, ?, ?, ?, ?, NOW())
"); ");
$stmt->execute([ $stmt->execute([
$iduserlogin, $iduserlogin,
$file['name'], $file['name'],
'certificate/' . $new_filename, 'userarea/certificate/' . $new_filename, // ← solo questo
$document_name, $document_name,
$expiry_date, $expiry_date,
$notes $notes
@ -151,6 +152,21 @@ $user = $stmt->fetch();
.file-link:hover { .file-link:hover {
color: #0056b3; color: #0056b3;
} }
/* Riga rosa tenue per scaduti */
tr.expired-row {
background-color: #ffebee !important;
/* rosa molto chiaro / rosso tenue */
}
/* Badge scaduto */
.badge-expired {
background-color: #dc3545;
color: white;
font-size: 0.8rem;
padding: 0.4em 0.8em;
border-radius: 50px;
}
</style> </style>
</head> </head>
@ -238,10 +254,11 @@ $user = $stmt->fetch();
</thead> </thead>
<tbody> <tbody>
<?php foreach ($certificates as $cert): <?php foreach ($certificates as $cert):
$file_url = '../' . $cert['stored_path']; $file_url = '/' . $cert['stored_path']; // usa percorso root-relative come consigliato prima
$expired = $cert['expiry_date'] && strtotime($cert['expiry_date']) < time(); $expired = $cert['expiry_date'] && strtotime($cert['expiry_date']) < time();
$row_class = $expired ? 'expired-row' : '';
?> ?>
<tr> <tr class="<?= $row_class ?>">
<td><?= date('d/m/Y H:i', strtotime($cert['uploaded_at'])) ?></td> <td><?= date('d/m/Y H:i', strtotime($cert['uploaded_at'])) ?></td>
<td> <td>
<a href="<?= htmlspecialchars($file_url) ?>" target="_blank" class="file-link"> <a href="<?= htmlspecialchars($file_url) ?>" target="_blank" class="file-link">
@ -252,7 +269,9 @@ $user = $stmt->fetch();
</td> </td>
<td class="<?= $expired ? 'expired' : '' ?>"> <td class="<?= $expired ? 'expired' : '' ?>">
<?= $cert['expiry_date'] ? date('d/m/Y', strtotime($cert['expiry_date'])) : '—' ?> <?= $cert['expiry_date'] ? date('d/m/Y', strtotime($cert['expiry_date'])) : '—' ?>
<?= $expired ? '<br><small>SCADUTO</small>' : '' ?> <?php if ($expired): ?>
<span class="badge-expired ms-2">SCADUTO</span>
<?php endif; ?>
</td> </td>
<td><?= $cert['notes'] ? nl2br(htmlspecialchars(substr($cert['notes'], 0, 100))) . (strlen($cert['notes']) > 100 ? '...' : '') : '—' ?></td> <td><?= $cert['notes'] ? nl2br(htmlspecialchars(substr($cert['notes'], 0, 100))) . (strlen($cert['notes']) > 100 ? '...' : '') : '—' ?></td>
<td class="text-center"> <td class="text-center">

View File

@ -46,12 +46,26 @@ $stmt = $pdo->prepare("
JOIN orders o ON sb.order_id = o.id JOIN orders o ON sb.order_id = o.id
WHERE sb.user_id = ? WHERE sb.user_id = ?
AND cs.school_id = ? AND cs.school_id = ?
AND sb.status = 'booked'
AND cs.session_date >= ? AND cs.session_date >= ?
AND cs.session_date < ? AND cs.session_date < ?
ORDER BY cs.session_date ASC, cs.start_time ASC ORDER BY cs.session_date ASC, cs.start_time ASC
"); ");
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]); $stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
$bookings = $stmt->fetchAll(); $bookings = $stmt->fetchAll();
// === CONTROLLA CERTIFICATI VALIDI ===
$stmt_cert = $pdo->prepare("
SELECT COUNT(*) AS valid_count
FROM user_medical_certificates
WHERE user_id = ?
AND expiry_date IS NOT NULL
AND expiry_date >= CURDATE()
AND is_valid = 1
");
$stmt_cert->execute([$iduserlogin]);
$cert_result = $stmt_cert->fetch(PDO::FETCH_ASSOC);
$has_valid_cert = ($cert_result['valid_count'] > 0);
?> ?>
<!doctype html> <!doctype html>
@ -207,7 +221,16 @@ $bookings = $stmt->fetchAll();
</div> </div>
<div class="container-fluid px-0"> <div class="container-fluid px-0">
<?php if (!$has_valid_cert): ?>
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow" role="alert">
<strong>Attenzione!</strong> Non hai un certificato medico valido caricato.
<br>Ti potrebbe essere vietato l'accesso alle lezioni/pratiche.
<a href="my_certificates.php" class="alert-link fw-bold ms-2">
Caricalo subito qui
</a>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<?php if (empty($bookings)): ?> <?php if (empty($bookings)): ?>
<div class="text-center py-5 px-3"> <div class="text-center py-5 px-3">
<i class="bx bx-calendar-x" style="font-size:4.5rem;color:#e0e0e0;"></i> <i class="bx bx-calendar-x" style="font-size:4.5rem;color:#e0e0e0;"></i>
@ -264,7 +287,7 @@ $bookings = $stmt->fetchAll();
<button class="btn-custom btn-primary-custom" onclick="reschedule(<?= $b['booking_id'] ?>)"> <button class="btn-custom btn-primary-custom" onclick="reschedule(<?= $b['booking_id'] ?>)">
Riprogramma Riprogramma
</button> </button>
<button class="btn-custom btn-outline-custom" onclick="cancelBooking(<?= $b['booking_id'] ?>)"> <button class="btn-custom btn-outline-custom" onclick="confirmCancel(<?= $b['booking_id'] ?>)">
Cancella Cancella
</button> </button>
</div> </div>
@ -288,29 +311,52 @@ $bookings = $stmt->fetchAll();
<?php include('jsinclude.php'); ?> <?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script> <script>
const reschedule = id => Swal.fire({ const confirmCancel = id => {
title: 'Riprogrammare?', Swal.fire({
text: 'Scegli un nuovo orario', title: 'Annullare questa prenotazione?',
icon: 'question', html: 'Cancellando la lezione:<br>' +
showCancelButton: true, '<ul style="text-align:left; margin:15px 0 0 20px; font-size:0.95rem;">' +
confirmButtonText: 'Sì', '<li>Il ticket/ingresso resterà disponibile nella tua area utente</li>' +
cancelButtonText: 'No' '<li>Potrai riprogrammarlo su un\'altra data (se non hai superato il limite di recuperi o la scadenza del pacchetto)</li>' +
}).then(r => r.isConfirmed && (location = 'reschedule.php?booking=' + id)); '<li>L\'annullamento è irreversibile</li>' +
const cancelBooking = id => Swal.fire({ '</ul>',
title: 'Annullare prenotazione?',
text: 'Riceverai un recupero',
icon: 'warning', icon: 'warning',
showCancelButton: true, showCancelButton: true,
confirmButtonColor: '#ef4444', confirmButtonColor: '#ef4444',
confirmButtonText: 'Sì, annulla', cancelButtonColor: '#6c757d',
cancelButtonText: 'No' confirmButtonText: 'Sì, annulla la lezione',
}).then(r => r.isConfirmed && fetch('cancel_booking.php', { cancelButtonText: 'No, mantienila',
reverseButtons: true
}).then((result) => {
if (result.isConfirmed) {
fetch('cancel_booking.php', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
}, },
body: 'booking_id=' + id body: 'booking_id=' + id
}).then(r => r.json()).then(d => d.success ? Swal.fire('Annullata!', 'Hai un recupero', 'success').then(() => location.reload()) : Swal.fire('Errore', d.message, 'error'))); })
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
title: 'Prenotazione annullata!',
text: 'Il tuo ingresso è tornato disponibile per un recupero/riprogrammazione.',
icon: 'success',
timer: 2500
}).then(() => {
location.reload();
});
} else {
Swal.fire('Errore', data.message || 'Impossibile annullare la prenotazione.', 'error');
}
})
.catch(error => {
Swal.fire('Errore di connessione', 'Impossibile comunicare con il server.', 'error');
});
}
});
};
</script> </script>
</body> </body>

View File

@ -0,0 +1,59 @@
<?php
session_start();
include('include/headscript.php'); // ← adatta il path se necessario (da userarea/ sale di due livelli)
header('Content-Type: application/json');
if (!isset($_SESSION['iduserlogin'])) {
echo json_encode(['success' => false, 'message' => 'Non autorizzato']);
exit;
}
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
$school_id = (int)($_POST['school_id'] ?? 0);
$user_id = (int)$_SESSION['iduserlogin'];
if ($school_id <= 0) {
echo json_encode(['success' => false, 'message' => 'Scuola non valida']);
exit;
}
// Verifica iscrizione attiva
$stmt = $pdo->prepare("
SELECT id FROM user_schools
WHERE user_id = ? AND school_id = ? AND status = 'active'
");
$stmt->execute([$user_id, $school_id]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'message' => 'Non sei iscritto a questa scuola']);
exit;
}
// Soft-delete: imposta status = 'inactive'
$stmt = $pdo->prepare("
UPDATE user_schools
SET status = 'inactive', updated_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND school_id = ?
");
$stmt->execute([$user_id, $school_id]);
// Opzionale: resetta scuola corrente in sessione
if (isset($_SESSION['school_id']) && $_SESSION['school_id'] == $school_id) {
unset($_SESSION['school_id'], $_SESSION['school_name'], $_SESSION['school_selected']);
}
// (Opzionale) cancella dati associati - commenta se NON vuoi eliminare
// Esempio cancellazione prenotazioni:
$pdo->prepare("
DELETE sb FROM session_bookings sb
JOIN class_sessions cs ON sb.session_id = cs.id
WHERE sb.user_id = ? AND cs.school_id = ?
")->execute([$user_id, $school_id]);
// Esempio cancellazione ordini:
$pdo->prepare("DELETE FROM orders WHERE user_id = ? AND school_id = ?")
->execute([$user_id, $school_id]);
echo json_encode(['success' => true]);

View File

@ -126,8 +126,18 @@ if ($school_id) {
if ($school) { if ($school) {
$school_name = $school['name']; $school_name = $school['name'];
if (!empty($school['logo']) && file_exists("photoschool/" . $school['logo'])) { $logoRaw = trim($school['logo'] ?? '');
$school_logo_path = "photoschool/" . $school['logo']; if (!empty($logoRaw)) {
// Percorso fisico per verificare esistenza
$physicalPath = __DIR__ . '/../' . $logoRaw; // da userarea/ sale a public/ + photoschool/...
if (file_exists($physicalPath)) {
// Percorso web corretto (root-relative)
$school_logo_path = '/' . $logoRaw;
} else {
// Debug: scrivi nel log se il file non esiste
error_log("LOGO SCUOLA NON TROVATO - school_id: $school_id | path fisico: $physicalPath");
}
} }
} }
} }
@ -147,6 +157,54 @@ $stmt = $pdo->prepare("
$stmt->execute([$iduserlogin, $school_id]); $stmt->execute([$iduserlogin, $school_id]);
$orders = $stmt->fetchAll(); $orders = $stmt->fetchAll();
// 1. Lezioni acquistate totali (somma total_entries da ordini completati)
$stmt_total_lessons = $pdo->prepare("
SELECT COALESCE(SUM(total_entries), 0) AS total_lessons
FROM orders
WHERE user_id = ? AND school_id = ? AND status = 'completed'
");
$stmt_total_lessons->execute([$iduserlogin, $school_id]);
$total_lessons = $stmt_total_lessons->fetchColumn() ?: 0;
// 2. Lezioni da praticare (booked + data futura)
$stmt_to_practice = $pdo->prepare("
SELECT COUNT(sb.id) AS to_practice
FROM session_bookings sb
JOIN class_sessions cs ON sb.session_id = cs.id
WHERE sb.user_id = ?
AND cs.school_id = ?
AND sb.status = 'booked'
AND cs.session_date >= CURDATE()
");
$stmt_to_practice->execute([$iduserlogin, $school_id]);
$to_practice = $stmt_to_practice->fetchColumn() ?: 0;
// 3. Lezioni perse (missed + data passata)
$stmt_missed = $pdo->prepare("
SELECT COUNT(sb.id) AS missed
FROM session_bookings sb
JOIN class_sessions cs ON sb.session_id = cs.id
WHERE sb.user_id = ?
AND cs.school_id = ?
AND sb.status = 'missed'
AND cs.session_date < CURDATE()
");
$stmt_missed->execute([$iduserlogin, $school_id]);
$missed = $stmt_missed->fetchColumn() ?: 0;
// === CONTROLLA CERTIFICATI VALIDI ===
$stmt_cert = $pdo->prepare("
SELECT COUNT(*) AS valid_count
FROM user_medical_certificates
WHERE user_id = ?
AND expiry_date IS NOT NULL
AND expiry_date >= CURDATE()
AND is_valid = 1
");
$stmt_cert->execute([$iduserlogin]);
$cert_result = $stmt_cert->fetch(PDO::FETCH_ASSOC);
$has_valid_cert = ($cert_result['valid_count'] > 0);
// === STATISTICHE RAPIDE === // === STATISTICHE RAPIDE ===
$total_spent = array_sum(array_column($orders, 'price')); $total_spent = array_sum(array_column($orders, 'price'));
$total_entries = array_sum(array_column($orders, 'total_entries')); $total_entries = array_sum(array_column($orders, 'total_entries'));
@ -228,8 +286,9 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
<div class="card-body text-center py-5"> <div class="card-body text-center py-5">
<!-- Logo solo se esiste --> <!-- Logo solo se esiste -->
<?php if ($school_logo_path): ?> <?php
<img src="<?php echo htmlspecialchars($school_logo_path); ?>" if ($logoRaw): ?>
<img src="<?php echo htmlspecialchars($logoRaw); ?>"
alt="Logo <?php echo htmlspecialchars($school_name); ?>" alt="Logo <?php echo htmlspecialchars($school_name); ?>"
class="mb-4 rounded-3 shadow" class="mb-4 rounded-3 shadow"
style="height: 90px; object-fit: contain;"> style="height: 90px; object-fit: contain;">
@ -256,42 +315,53 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</div> </div>
</div> </div>
<!-- PROFILO + STATISTICHE --> <?php if (!$has_valid_cert): ?>
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow" role="alert">
<strong>Attenzione!</strong> Non hai un certificato medico valido caricato.
<br>Ti potrebbe essere vietato l'accesso alle lezioni/pratiche.
<a href="my_certificates.php" class="alert-link fw-bold ms-2">
Caricalo subito qui
</a>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php endif; ?>
<!-- STATISTICHE PASTELLO - NUOVA VERSIONE -->
<div class="row g-4 mb-5"> <div class="row g-4 mb-5">
<div class="col-lg-4"> <!-- Box 1: Lezioni acquistate totali -->
<div class="card stat-card text-center h-100"> <div class="col-md-3">
<div class="card-body"> <div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #d1fae5, #a7f3d0);">
<img src="<?php echo $avatar; ?>" alt="Avatar" class="rounded-circle avatar-circle mb-3"> <div class="card-body py-4">
<h5><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h5> <h3 class="fw-bold text-success mb-1"><?= number_format($total_lessons) ?></h3>
<p class="text-muted"><?php echo htmlspecialchars($user['email']); ?></p> <p class="text-dark mb-0">Lezioni acquistate totali</p>
</div> </div>
</div> </div>
</div> </div>
<div class="col-lg-8">
<div class="row g-4"> <!-- Box 2: Lezioni da praticare -->
<div class="col-md-4"> <div class="col-md-3">
<div class="card stat-card text-center h-100"> <div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #dbeafe, #bfdbfe);">
<div class="card-body"> <div class="card-body py-4">
<h3 class="text-primary fw-bold"><?php echo count($orders); ?></h3> <h3 class="fw-bold text-primary mb-1"><?= number_format($to_practice) ?></h3>
<p class="mb-0">Ordini totali</p> <p class="text-dark mb-0">Lezioni da praticare</p>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="card stat-card text-center h-100"> <!-- Box 3: Lezioni perse -->
<div class="card-body"> <div class="col-md-3">
<h3 class="text-success fw-bold"><?php echo number_format($total_spent, 2); ?></h3> <div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #fee2e2, #fecaca);">
<p class="mb-0">Speso in totale</p> <div class="card-body py-4">
<h3 class="fw-bold text-danger mb-1"><?= number_format($missed) ?></h3>
<p class="text-dark mb-0">Lezioni perse</p>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="card stat-card text-center h-100"> <!-- Box 4: Vuoto (pronto per futuro) -->
<div class="card-body"> <div class="col-md-3">
<h3 class="text-info fw-bold"><?php echo $available_entries; ?></h3> <div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #fef3c7, #fde68a);">
<p class="mb-0">Ingressi disponibili</p> <div class="card-body py-4 d-flex align-items-center justify-content-center">
</div> <p class="text-muted mb-0 fst-italic">Prossimamente...</p>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -370,6 +440,18 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</div> </div>
</div> </div>
<!-- PULSANTE RIMUOVI SCUOLA (in fondo, evidente) -->
<!-- Link discreto per rimuoversi dalla scuola (in fondo a destra) -->
<div class="text-end mt-5 mb-5 pe-4">
<a href="#" class="text-danger small text-decoration-none"
onclick="confirmRemoveSchool(<?= $school_id ?>, '<?= addslashes(htmlspecialchars($school_name)) ?>'); return false;">
<i class="bx bx-log-out bx-sm me-1"></i>
Rimuovimi da questa scuola
</a>
<p class="text-muted fst-italic small mt-1" style="font-size:0.8rem;">
(azione irreversibile: perderai lezioni, crediti e storico associato)
</p>
</div>
</div> </div>
</div> </div>
@ -377,6 +459,57 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</div> </div>
<?php include('jsinclude.php'); ?> <?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const confirmRemoveSchool = (schoolId, schoolName) => {
Swal.fire({
title: `Abbandonare ${schoolName}?`,
html: `Stai per rimuoverti completamente da <strong>${schoolName}</strong>.<br><br>` +
`<strong class="text-danger">Attenzione:</strong> perderai irreversibilmente:<br>` +
`<ul style="text-align:left; margin:15px 0 0 30px; font-size:0.95rem;">` +
`<li>Tutte le tue lezioni prenotate</li>` +
`<li>Crediti, ingressi e recuperi residui</li>` +
`<li>Storico ordini e archivio personale legato a questa scuola</li>` +
`</ul><br>` +
`Questa azione <u>non può essere annullata</u>.`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Sì, rimuovimi definitivamente',
cancelButtonText: 'No, mantienimi iscritto',
reverseButtons: true
}).then((result) => {
if (result.isConfirmed) {
fetch('remove_school.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'school_id=' + schoolId
})
.then(response => response.json())
.then(data => {
if (data.success) {
Swal.fire({
title: 'Rimozione completata',
text: `Non fai più parte di ${schoolName}.`,
icon: 'success',
timer: 3000
}).then(() => {
location.reload(); // o redirect a select_school.php
});
} else {
Swal.fire('Errore', data.message || 'Impossibile rimuoverti dalla scuola.', 'error');
}
})
.catch(() => {
Swal.fire('Errore', 'Problema di connessione con il server.', 'error');
});
}
});
};
</script>
<!-- Modal Cambia Scuola --> <!-- Modal Cambia Scuola -->
<div class="modal fade" id="changeSchoolModal" tabindex="-1"> <div class="modal fade" id="changeSchoolModal" tabindex="-1">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">