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)
VALUES (?, ?, ?, ?, ?, ?, NOW())
");
$stmt->execute([
$iduserlogin,
$file['name'],
'certificate/' . $new_filename,
'userarea/certificate/' . $new_filename, // ← solo questo
$document_name,
$expiry_date,
$notes
@ -151,6 +152,21 @@ $user = $stmt->fetch();
.file-link:hover {
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>
</head>
@ -238,10 +254,11 @@ $user = $stmt->fetch();
</thead>
<tbody>
<?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();
$row_class = $expired ? 'expired-row' : '';
?>
<tr>
<tr class="<?= $row_class ?>">
<td><?= date('d/m/Y H:i', strtotime($cert['uploaded_at'])) ?></td>
<td>
<a href="<?= htmlspecialchars($file_url) ?>" target="_blank" class="file-link">
@ -252,7 +269,9 @@ $user = $stmt->fetch();
</td>
<td class="<?= $expired ? 'expired' : '' ?>">
<?= $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><?= $cert['notes'] ? nl2br(htmlspecialchars(substr($cert['notes'], 0, 100))) . (strlen($cert['notes']) > 100 ? '...' : '') : '—' ?></td>
<td class="text-center">

View File

@ -46,12 +46,26 @@ $stmt = $pdo->prepare("
JOIN orders o ON sb.order_id = o.id
WHERE sb.user_id = ?
AND cs.school_id = ?
AND sb.status = 'booked'
AND cs.session_date >= ?
AND cs.session_date < ?
ORDER BY cs.session_date ASC, cs.start_time ASC
");
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
$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>
@ -207,7 +221,16 @@ $bookings = $stmt->fetchAll();
</div>
<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)): ?>
<div class="text-center py-5 px-3">
<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'] ?>)">
Riprogramma
</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
</button>
</div>
@ -288,29 +311,52 @@ $bookings = $stmt->fetchAll();
<?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
const reschedule = id => Swal.fire({
title: 'Riprogrammare?',
text: 'Scegli un nuovo orario',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Sì',
cancelButtonText: 'No'
}).then(r => r.isConfirmed && (location = 'reschedule.php?booking=' + id));
const cancelBooking = id => Swal.fire({
title: 'Annullare prenotazione?',
text: 'Riceverai un recupero',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
confirmButtonText: 'Sì, annulla',
cancelButtonText: 'No'
}).then(r => r.isConfirmed && fetch('cancel_booking.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
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')));
const confirmCancel = id => {
Swal.fire({
title: 'Annullare questa prenotazione?',
html: 'Cancellando la lezione:<br>' +
'<ul style="text-align:left; margin:15px 0 0 20px; font-size:0.95rem;">' +
'<li>Il ticket/ingresso resterà disponibile nella tua area utente</li>' +
'<li>Potrai riprogrammarlo su un\'altra data (se non hai superato il limite di recuperi o la scadenza del pacchetto)</li>' +
'<li>L\'annullamento è irreversibile</li>' +
'</ul>',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#ef4444',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Sì, annulla la lezione',
cancelButtonText: 'No, mantienila',
reverseButtons: true
}).then((result) => {
if (result.isConfirmed) {
fetch('cancel_booking.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'booking_id=' + id
})
.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>
</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) {
$school_name = $school['name'];
if (!empty($school['logo']) && file_exists("photoschool/" . $school['logo'])) {
$school_logo_path = "photoschool/" . $school['logo'];
$logoRaw = trim($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]);
$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 ===
$total_spent = array_sum(array_column($orders, 'price'));
$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">
<!-- Logo solo se esiste -->
<?php if ($school_logo_path): ?>
<img src="<?php echo htmlspecialchars($school_logo_path); ?>"
<?php
if ($logoRaw): ?>
<img src="<?php echo htmlspecialchars($logoRaw); ?>"
alt="Logo <?php echo htmlspecialchars($school_name); ?>"
class="mb-4 rounded-3 shadow"
style="height: 90px; object-fit: contain;">
@ -256,42 +315,53 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</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="col-lg-4">
<div class="card stat-card text-center h-100">
<div class="card-body">
<img src="<?php echo $avatar; ?>" alt="Avatar" class="rounded-circle avatar-circle mb-3">
<h5><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h5>
<p class="text-muted"><?php echo htmlspecialchars($user['email']); ?></p>
<!-- Box 1: Lezioni acquistate totali -->
<div class="col-md-3">
<div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #d1fae5, #a7f3d0);">
<div class="card-body py-4">
<h3 class="fw-bold text-success mb-1"><?= number_format($total_lessons) ?></h3>
<p class="text-dark mb-0">Lezioni acquistate totali</p>
</div>
</div>
</div>
<div class="col-lg-8">
<div class="row g-4">
<div class="col-md-4">
<div class="card stat-card text-center h-100">
<div class="card-body">
<h3 class="text-primary fw-bold"><?php echo count($orders); ?></h3>
<p class="mb-0">Ordini totali</p>
</div>
</div>
<!-- Box 2: Lezioni da praticare -->
<div class="col-md-3">
<div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #dbeafe, #bfdbfe);">
<div class="card-body py-4">
<h3 class="fw-bold text-primary mb-1"><?= number_format($to_practice) ?></h3>
<p class="text-dark mb-0">Lezioni da praticare</p>
</div>
<div class="col-md-4">
<div class="card stat-card text-center h-100">
<div class="card-body">
<h3 class="text-success fw-bold"><?php echo number_format($total_spent, 2); ?></h3>
<p class="mb-0">Speso in totale</p>
</div>
</div>
</div>
</div>
<!-- Box 3: Lezioni perse -->
<div class="col-md-3">
<div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #fee2e2, #fecaca);">
<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 class="col-md-4">
<div class="card stat-card text-center h-100">
<div class="card-body">
<h3 class="text-info fw-bold"><?php echo $available_entries; ?></h3>
<p class="mb-0">Ingressi disponibili</p>
</div>
</div>
</div>
</div>
<!-- Box 4: Vuoto (pronto per futuro) -->
<div class="col-md-3">
<div class="card stat-card text-center h-100" style="background: linear-gradient(135deg, #fef3c7, #fde68a);">
<div class="card-body py-4 d-flex align-items-center justify-content-center">
<p class="text-muted mb-0 fst-italic">Prossimamente...</p>
</div>
</div>
</div>
@ -370,6 +440,18 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</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>
@ -377,6 +459,57 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
</div>
<?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 -->
<div class="modal fade" id="changeSchoolModal" tabindex="-1">
<div class="modal-dialog modal-lg">