1026 lines
48 KiB
PHP
1026 lines
48 KiB
PHP
<?php
|
||
require_once('include/headscript.php');
|
||
|
||
/**
|
||
* Statistiche utenti: lezioni acquistate/praticate/programmate/perse/da programmare,
|
||
* abbonamento attivo, certificato medico, dettaglio lezioni per utente,
|
||
* riprogrammazione e invio email (singola/massa).
|
||
*/
|
||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||
|
||
function e($value): string
|
||
{
|
||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||
}
|
||
|
||
$serverCurrentDateTime = date('Y-m-d H:i:s');
|
||
|
||
$sql = "
|
||
SELECT
|
||
auth_users.id AS id_utente,
|
||
auth_users.first_name AS nome_utente,
|
||
auth_users.last_name AS cognome_utente,
|
||
auth_users.email AS email_utente,
|
||
COALESCE(orderbook_lessons.lezioni_acquistate, 0) AS lezioni_acquistate,
|
||
MAX(CASE WHEN active_orders.iduser IS NOT NULL THEN 1 ELSE 0 END) AS ha_ordine_attivo,
|
||
MAX(cert.ultima_scadenza) AS ultima_scadenza_cert,
|
||
MAX(CASE WHEN cert.ultima_scadenza IS NULL OR cert.ultima_scadenza < CURDATE() THEN 1 ELSE 0 END) AS certificato_scaduto,
|
||
COUNT(CASE WHEN bookingclass.bookingstart <= CURDATE() AND bookingclass.lostlesson = 'N' THEN 1 END) AS lezioni_praticate,
|
||
COUNT(CASE WHEN bookingclass.bookingstart > CURDATE() THEN 1 END) AS lezioni_programmate,
|
||
COUNT(CASE WHEN bookingclass.lostlesson = 'Y' THEN 1 END) AS lezioni_perse,
|
||
COALESCE(orderbook_lessons.lezioni_acquistate, 0)
|
||
- COUNT(CASE WHEN bookingclass.bookingstart > CURDATE() THEN 1 END)
|
||
- COUNT(CASE WHEN bookingclass.bookingstart <= CURDATE() AND bookingclass.lostlesson = 'N' THEN 1 END)
|
||
- COUNT(CASE WHEN bookingclass.lostlesson = 'Y' THEN 1 END) AS lezioni_da_programmare
|
||
FROM auth_users
|
||
LEFT JOIN (
|
||
SELECT iduser, SUM(quantityclass) AS lezioni_acquistate
|
||
FROM orderbook
|
||
GROUP BY iduser
|
||
) AS orderbook_lessons ON auth_users.id = orderbook_lessons.iduser
|
||
LEFT JOIN (
|
||
SELECT DISTINCT iduser
|
||
FROM orderbook
|
||
WHERE expireon >= CURDATE()
|
||
) AS active_orders ON auth_users.id = active_orders.iduser
|
||
LEFT JOIN (
|
||
SELECT iduser, MAX(expirydatedocument) AS ultima_scadenza
|
||
FROM certificateuserprofile
|
||
GROUP BY iduser
|
||
) AS cert ON auth_users.id = cert.iduser
|
||
LEFT JOIN bookingclass ON auth_users.id = bookingclass.iduser
|
||
GROUP BY auth_users.id, auth_users.last_name
|
||
ORDER BY auth_users.last_name";
|
||
|
||
$users = $pdo->query($sql)->fetchAll();
|
||
$activeUsersCount = count(array_filter($users, fn($u) => (int) $u['ha_ordine_attivo'] === 1));
|
||
$expiredCertCount = count(array_filter($users, fn($u) => (int) $u['ha_ordine_attivo'] === 1 && (int) $u['certificato_scaduto'] === 1));
|
||
?>
|
||
<!doctype html>
|
||
<html lang="it">
|
||
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>YogiBook - Statistiche utenti</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta content="YogiBook - Prenotazione facile YogaSoul" name="description" />
|
||
<meta content="Advanced Creative Solutions" name="author" />
|
||
<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">
|
||
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/jquery.dataTables.min.css">
|
||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></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;
|
||
}
|
||
|
||
#userStatsTable thead th {
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.03em;
|
||
color: #6b7280;
|
||
}
|
||
|
||
#userStatsTable td {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.stat-num {
|
||
display: inline-block;
|
||
min-width: 28px;
|
||
text-align: center;
|
||
font-weight: 700;
|
||
border-radius: 6px;
|
||
padding: 2px 6px;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.stat-acq {
|
||
background: #e2d3f5;
|
||
color: #4c2c92;
|
||
}
|
||
|
||
.stat-prat {
|
||
background: #c4e1fb;
|
||
color: #1a4f7a;
|
||
}
|
||
|
||
.stat-prog {
|
||
background: #cdfbc4;
|
||
color: #1a7f2e;
|
||
}
|
||
|
||
.stat-perse {
|
||
background: #fbc7c4;
|
||
color: #a12820;
|
||
}
|
||
|
||
.stat-daprog {
|
||
background: #fbe4c4;
|
||
color: #8a5a1a;
|
||
}
|
||
|
||
.btn-email-all {
|
||
background: #007bff;
|
||
border: none;
|
||
color: #fff;
|
||
border-radius: 8px;
|
||
padding: 9px 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.btn-email-all:hover {
|
||
filter: brightness(0.93);
|
||
}
|
||
|
||
.active-counter {
|
||
background: #cdfbc4;
|
||
color: #1a7f2e;
|
||
border-radius: 8px;
|
||
padding: 8px 14px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.btn-email-cert {
|
||
background: #dc3545;
|
||
}
|
||
|
||
.cert-counter {
|
||
background: #fbc7c4;
|
||
color: #a12820;
|
||
}
|
||
|
||
.cert-badge {
|
||
display: inline-block;
|
||
border-radius: 6px;
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cert-ok {
|
||
background: #cdfbc4;
|
||
color: #1a7f2e;
|
||
}
|
||
|
||
.cert-scaduto {
|
||
background: #fbc7c4;
|
||
color: #a12820;
|
||
}
|
||
|
||
.lost-lesson {
|
||
background-color: #ffcc80 !important;
|
||
}
|
||
|
||
.past-lesson {
|
||
background-color: #c6d9f0 !important;
|
||
}
|
||
|
||
.past-lesson-reprogrammed {
|
||
background-color: #a9bcd8 !important;
|
||
}
|
||
|
||
.future-lesson {
|
||
background-color: #b3ffb3 !important;
|
||
}
|
||
|
||
.future-lesson-reprogrammed {
|
||
background-color: #a3e6a3 !important;
|
||
}
|
||
|
||
.status-reprogrammed {
|
||
background-color: #fffacd !important;
|
||
}
|
||
|
||
.btn-warning {
|
||
background-color: #ff8c00 !important;
|
||
border-color: #ff8c00 !important;
|
||
color: #fff !important;
|
||
}
|
||
|
||
.btn-reprogram {
|
||
background-color: #28a745 !important;
|
||
border-color: #28a745 !important;
|
||
color: #fff !important;
|
||
}
|
||
|
||
.action-cell {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.action-icon {
|
||
width: 30px;
|
||
height: 30px;
|
||
padding: 0;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 7px;
|
||
margin: 0 1px;
|
||
}
|
||
|
||
.action-icon i {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.modal-xl-custom {
|
||
max-width: 1200px;
|
||
}
|
||
|
||
#emailBody {
|
||
width: 100%;
|
||
height: 200px;
|
||
resize: vertical;
|
||
}
|
||
|
||
.recipient-preview {
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
text-align: left;
|
||
font-size: 13px;
|
||
border: 1px solid #eef0f3;
|
||
border-radius: 8px;
|
||
padding: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.recipient-preview div {
|
||
padding: 3px 0;
|
||
border-bottom: 1px solid #f5f6f8;
|
||
}
|
||
</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">Statistiche utenti</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">
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card admin-card">
|
||
<div class="card-body">
|
||
<div class="section-title">Statistiche utenti</div>
|
||
<p class="section-lead">Ciao <?php echo e($firstname); ?>, clicca sul nome di un utente per il dettaglio delle lezioni.</p>
|
||
|
||
<div class="d-flex align-items-center gap-3 flex-wrap mb-3">
|
||
<button class="btn-email-all mb-0" id="sendEmailToAll"><i class="fas fa-envelope me-1"></i> Invia email agli abbonati attivi</button>
|
||
<span class="active-counter"><i class="fas fa-user-check me-1"></i> Utenti con abbonamento attivo: <strong><?php echo $activeUsersCount; ?></strong> su <?php echo count($users); ?></span>
|
||
<button class="btn-email-all btn-email-cert mb-0" id="sendEmailCertExpired"><i class="fas fa-file-medical me-1"></i> Invia email certificato scaduto</button>
|
||
<span class="active-counter cert-counter"><i class="fas fa-triangle-exclamation me-1"></i> Abbonati con certificato scaduto: <strong><?php echo $expiredCertCount; ?></strong></span>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table id="userStatsTable" class="table table-striped">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Nome</th>
|
||
<th>Cognome</th>
|
||
<th>Acquist.</th>
|
||
<th>Pratic.</th>
|
||
<th>Program.</th>
|
||
<th>Perse</th>
|
||
<th>Da progr.</th>
|
||
<th>Certificato</th>
|
||
<th>Email</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($users as $u) : ?>
|
||
<tr>
|
||
<td><?php echo (int) $u['id_utente']; ?></td>
|
||
<td>
|
||
<a href="#" class="user-link" data-id="<?php echo (int) $u['id_utente']; ?>"
|
||
data-nome="<?php echo e($u['nome_utente'] . ' ' . $u['cognome_utente']); ?>"
|
||
data-bs-toggle="modal" data-bs-target="#classModal">
|
||
<?php echo e($u['nome_utente']); ?>
|
||
</a>
|
||
</td>
|
||
<td><?php echo e($u['cognome_utente']); ?></td>
|
||
<td><span class="stat-num stat-acq"><?php echo (int) $u['lezioni_acquistate']; ?></span></td>
|
||
<td><span class="stat-num stat-prat"><?php echo (int) $u['lezioni_praticate']; ?></span></td>
|
||
<td><span class="stat-num stat-prog"><?php echo (int) $u['lezioni_programmate']; ?></span></td>
|
||
<td><span class="stat-num stat-perse"><?php echo (int) $u['lezioni_perse']; ?></span></td>
|
||
<td><span class="stat-num stat-daprog"><?php echo (int) $u['lezioni_da_programmare']; ?></span></td>
|
||
<td>
|
||
<?php $scad = $u['ultima_scadenza_cert']; ?>
|
||
<?php if (empty($scad)) : ?>
|
||
<span class="cert-badge cert-scaduto">Mancante</span>
|
||
<?php elseif ($scad === '0000-00-00') : ?>
|
||
<span class="cert-badge cert-scaduto">Data non valida</span>
|
||
<?php elseif ((int) $u['certificato_scaduto'] === 1) : ?>
|
||
<span class="cert-badge cert-scaduto">Scaduto <?php echo e(date('d/m/Y', strtotime($scad))); ?></span>
|
||
<?php else : ?>
|
||
<span class="cert-badge cert-ok">Valido fino al <?php echo e(date('d/m/Y', strtotime($scad))); ?></span>
|
||
<?php endif; ?>
|
||
</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-secondary send-email"
|
||
data-id="<?php echo (int) $u['id_utente']; ?>"
|
||
data-nome="<?php echo e($u['nome_utente']); ?>"
|
||
data-email="<?php echo e($u['email_utente']); ?>"
|
||
data-attivo="<?php echo (int) $u['ha_ordine_attivo']; ?>"
|
||
data-certscaduto="<?php echo (int) $u['certificato_scaduto']; ?>">
|
||
<i class="fas fa-envelope"></i>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php include('include/footer.php'); ?>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal dettaglio classi -->
|
||
<div class="modal fade" id="classModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl modal-xl-custom">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title">Dettagli lezioni per <span id="userName"></span></h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-bordered">
|
||
<thead>
|
||
<tr>
|
||
<th>Data</th>
|
||
<th>Lezione</th>
|
||
<th>Stato</th>
|
||
<th>Data prec.</th>
|
||
<th>Ordine</th>
|
||
<th>Data ordine</th>
|
||
<th>Azione</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="classDetailsBody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal email -->
|
||
<div class="modal fade" id="emailModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="emailModalLabel">Invia Email</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="emailForm">
|
||
<div class="mb-3">
|
||
<label for="emailSubject" class="form-label">Oggetto</label>
|
||
<input type="text" class="form-control" id="emailSubject" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="emailBody" class="form-label">Messaggio</label>
|
||
<textarea class="form-control" id="emailBody" required></textarea>
|
||
</div>
|
||
<div class="text-muted mb-2" style="font-size:12px;">Scrivi <code>{nome}</code> per il nome del destinatario e <code>**testo**</code> per il grassetto.</div>
|
||
<div id="emailRecipientCount" class="text-muted" style="font-size:13px;"></div>
|
||
<input type="hidden" id="emailRecipients" name="recipients">
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||
<button type="button" class="btn btn-primary" id="sendEmailBtn">Invia Email</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Modal riprogramma -->
|
||
<div class="modal fade" id="reprogramModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="reprogramModalLabel">Riprogramma Lezione</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="reprogramForm">
|
||
<div class="mb-3">
|
||
<label for="classSelect" class="form-label">Seleziona classe</label>
|
||
<select class="form-control" id="classSelect" required></select>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label for="reprogramDate" class="form-label">Seleziona data</label>
|
||
<input type="text" class="form-control" id="reprogramDate" readonly required>
|
||
</div>
|
||
<input type="hidden" id="reprogramIdBookingClass">
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||
<button type="button" class="btn btn-primary" id="reprogramBtn">Riprogramma</button>
|
||
</div>
|
||
</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>
|
||
|
||
<script>
|
||
const serverNow = '<?php echo $serverCurrentDateTime; ?>';
|
||
|
||
// Testo predefinito per l'email "certificato scaduto".
|
||
// Il .replace finale toglie gli spazi a inizio riga se l'editor li rientra.
|
||
const TESTO_CERTIFICATO = `Gentile {nome},
|
||
|
||
ti ricordiamo che il certificato di idoneità alla pratica è **obbligatorio** per la partecipazione alle attività Yoga svolte presso YogaSoul.
|
||
Come riportato nella descrizione di ogni pacchetto da 4 o 12 lezioni acquistato, è necessario munirsi del documento di buona salute e 👉 **caricarlo sulla piattaforma YogiBOOK** ➡️ https://yogibook.yogasoul.it/ per partecipare alle lezioni.
|
||
|
||
👉 **Come fare:**
|
||
|
||
• Accedi alla tua **area personale** (le credenziali ti sono state fornite per mail)
|
||
• Seleziona la sezione **Certificati**
|
||
• Carica il file in formato **PDF o JPG**
|
||
• La data di scadenza che dovrà essere da voi inserita sarà **la stessa riportata sul documento +364 giorni** ➡️ anno 2027
|
||
• Il documento verrà da noi controllato e archiviato
|
||
|
||
👉 **Se il tuo documento è in scadenza o già scaduto** ti chiediamo di controllare e provvedere ad inserire quello nuovo.
|
||
|
||
Il certificato è indispensabile per garantire una pratica sicura e conforme alle normative vigenti ed essere in regola qualora ci fossero dei controlli interni.
|
||
**In assenza del certificato medico, la partecipazione alle attività non sarà consentita** e YogaSoul declina ogni responsabilità per documenti non caricati o non consegnati.
|
||
Ti invitiamo pertanto a procedere quanto prima, al fine di evitare sospensioni dei pacchetti o limitazioni nella partecipazione alle lezioni.
|
||
|
||
Ti ringraziamo per la collaborazione e per l’attenzione alla sicurezza di tutti.
|
||
|
||
**TEAM YogaSoul**`.replace(/^[ \t]+/gm, '');
|
||
|
||
// Helper Swal che rimuove aria-hidden (fix focus modali Bootstrap)
|
||
function alertBox(opts) {
|
||
return Swal.fire(Object.assign({
|
||
didOpen: () => document.body.removeAttribute('aria-hidden')
|
||
}, opts));
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
if (!dateStr || dateStr === '-') return '-';
|
||
const months = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'];
|
||
const parts = dateStr.split(' ');
|
||
const d = parts[0].split('-');
|
||
const time = parts[1] || '';
|
||
if (d.length < 3) return dateStr;
|
||
return `${d[2].padStart(2,'0')} ${months[parseInt(d[1],10)-1]} ${d[0]} ${time}`.trim();
|
||
}
|
||
|
||
$('#userStatsTable').DataTable({
|
||
paging: false,
|
||
searching: true,
|
||
ordering: true,
|
||
language: {
|
||
url: "//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json"
|
||
}
|
||
});
|
||
|
||
$(document).ready(function() {
|
||
function refreshTable(idUtente, nomeUtente) {
|
||
$.ajax({
|
||
url: 'fetch_class_details.php',
|
||
method: 'POST',
|
||
data: {
|
||
id_utente: idUtente
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
var tbody = $('#classDetailsBody').empty();
|
||
if (!Array.isArray(data)) data = data.error ? [] : [data];
|
||
if (data.length === 0) {
|
||
tbody.append('<tr><td colspan="7" class="text-center">Nessuna classe trovata.</td></tr>');
|
||
return;
|
||
}
|
||
var now = new Date(serverNow);
|
||
$.each(data, function(i, row) {
|
||
var isPast = new Date(row.bookingstart) < now;
|
||
var isReprogrammed = row.prevbookingstart !== null;
|
||
var rowClass = '',
|
||
statusText = '';
|
||
if (row.lostlesson === 'Y') {
|
||
rowClass = 'lost-lesson';
|
||
statusText = 'Persa';
|
||
} else if (isReprogrammed) {
|
||
statusText = 'Riprogrammata';
|
||
rowClass = isPast ? 'past-lesson-reprogrammed' : 'future-lesson-reprogrammed';
|
||
} else if (isPast) {
|
||
statusText = 'Praticata';
|
||
rowClass = 'past-lesson';
|
||
} else {
|
||
statusText = 'Programmata';
|
||
rowClass = 'future-lesson';
|
||
}
|
||
|
||
var tr = $('<tr>').addClass(rowClass);
|
||
tr.append('<td>' + formatDate(row.bookingstart) + '</td>');
|
||
tr.append('<td>' + row.servicename + ' (' + row.day + ' ' + row.time + ')</td>');
|
||
var statusTd = $('<td>').text(statusText);
|
||
if (isReprogrammed) statusTd.addClass('status-reprogrammed');
|
||
tr.append(statusTd);
|
||
tr.append('<td>' + formatDate(row.prevbookingstart) + '</td>');
|
||
tr.append('<td>' + (row.idorder || '-') + '</td>');
|
||
tr.append('<td>' + (row.order_date_created || '-') + '</td>');
|
||
|
||
var actionTd = $('<td>').addClass('action-cell');
|
||
if (row.lostlesson !== 'Y') {
|
||
// Segna come persa
|
||
actionTd.append($('<button>').addClass('btn btn-sm btn-warning mark-lost action-icon')
|
||
.attr('title', 'Segna come persa')
|
||
.html('<i class="fas fa-wind"></i>')
|
||
.data('id', row.idbookingclass));
|
||
actionTd.append(' ');
|
||
// Riprogramma
|
||
actionTd.append($('<button>').addClass('btn btn-sm btn-reprogram reprogram-class action-icon')
|
||
.attr('title', 'Riprogramma')
|
||
.html('<i class="fas fa-calendar-day"></i>')
|
||
.data('id', row.idbookingclass).data('servicename', row.servicename).data('day', row.day).data('time', row.time));
|
||
actionTd.append(' ');
|
||
// Rimuovi
|
||
actionTd.append($('<button>').addClass('btn btn-sm btn-danger remove-class action-icon')
|
||
.attr('title', 'Rimuovi')
|
||
.html('<i class="fas fa-trash"></i>')
|
||
.data('id', row.idbookingclass));
|
||
} else {
|
||
actionTd.text('-');
|
||
}
|
||
tr.append(actionTd);
|
||
tbody.append(tr);
|
||
});
|
||
},
|
||
error: function() {
|
||
$('#classDetailsBody').html('<tr><td colspan="7" class="text-center">Errore nel caricamento.</td></tr>');
|
||
}
|
||
});
|
||
}
|
||
|
||
$('.user-link').on('click', function() {
|
||
var id = $(this).data('id'),
|
||
nome = $(this).data('nome');
|
||
$('#classModal').data('current-id', id);
|
||
$('#userName').text(nome);
|
||
refreshTable(id, nome);
|
||
});
|
||
|
||
// Rimuovi lezione
|
||
$(document).on('click', '.remove-class', function() {
|
||
var id = $(this).data('id');
|
||
var idU = $('#classModal').data('current-id'),
|
||
nomeU = $('#userName').text();
|
||
alertBox({
|
||
title: 'Sei sicuro?',
|
||
text: 'Questa lezione verrà rimossa definitivamente.',
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#d33',
|
||
confirmButtonText: 'Sì, rimuovi',
|
||
cancelButtonText: 'Annulla'
|
||
}).then((r) => {
|
||
if (r.isConfirmed) {
|
||
$.post('deleteclass.php', {
|
||
id: id
|
||
}, null, 'json')
|
||
.done(function(resp) {
|
||
if (resp.success) {
|
||
alertBox({
|
||
title: 'Fatto',
|
||
text: 'Lezione rimossa.',
|
||
icon: 'success'
|
||
});
|
||
refreshTable(idU, nomeU);
|
||
} else alertBox({
|
||
title: 'Errore',
|
||
text: resp.message || 'Errore durante la rimozione.',
|
||
icon: 'error'
|
||
});
|
||
})
|
||
.fail(() => alertBox({
|
||
title: 'Errore',
|
||
text: 'Errore durante la rimozione.',
|
||
icon: 'error'
|
||
}));
|
||
}
|
||
});
|
||
});
|
||
|
||
// Segna persa
|
||
$(document).on('click', '.mark-lost', function() {
|
||
var id = $(this).data('id');
|
||
var idU = $('#classModal').data('current-id'),
|
||
nomeU = $('#userName').text();
|
||
alertBox({
|
||
title: 'Sei sicuro?',
|
||
text: 'Questa lezione verrà segnata come persa.',
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#d33',
|
||
confirmButtonText: 'Sì',
|
||
cancelButtonText: 'Annulla'
|
||
}).then((r) => {
|
||
if (r.isConfirmed) {
|
||
$.post('mark_lost.php', {
|
||
id: id
|
||
}, null, 'json')
|
||
.done(function(resp) {
|
||
if (resp && resp.success) {
|
||
alertBox({
|
||
title: 'Fatto',
|
||
text: 'Lezione segnata come persa.',
|
||
icon: 'success'
|
||
});
|
||
refreshTable(idU, nomeU);
|
||
} else alertBox({
|
||
title: 'Errore',
|
||
text: (resp.message || 'Errore sconosciuto'),
|
||
icon: 'error'
|
||
});
|
||
})
|
||
.fail(() => alertBox({
|
||
title: 'Errore',
|
||
text: 'Errore durante l\'aggiornamento.',
|
||
icon: 'error'
|
||
}));
|
||
}
|
||
});
|
||
});
|
||
|
||
// Riprogramma: apri modale
|
||
$(document).on('click', '.reprogram-class', function() {
|
||
var id = $(this).data('id'),
|
||
servicename = $(this).data('servicename'),
|
||
day = $(this).data('day'),
|
||
time = $(this).data('time');
|
||
$('#reprogramIdBookingClass').val(id);
|
||
$('#reprogramModalLabel').text('Riprogramma: ' + servicename);
|
||
$.ajax({
|
||
url: 'fetch_available_classes.php',
|
||
method: 'GET',
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
var sel = $('#classSelect').empty();
|
||
$.each(data, function(i, c) {
|
||
var opt = $('<option>').val(c.id).text(c.servicename + ' (' + c.day + ' ' + c.time + ')');
|
||
if (c.servicename === servicename && c.day === day && c.time === time) opt.prop('selected', true);
|
||
sel.append(opt);
|
||
});
|
||
$('#reprogramDate').datepicker({
|
||
dateFormat: 'yy-mm-dd',
|
||
changeMonth: true,
|
||
changeYear: true
|
||
});
|
||
loadAvailableDates(sel.val());
|
||
$('#reprogramModal').modal('show');
|
||
},
|
||
error: () => alertBox({
|
||
title: 'Errore',
|
||
text: 'Errore nel caricamento delle classi.',
|
||
icon: 'error'
|
||
})
|
||
});
|
||
});
|
||
|
||
$('#classSelect').on('change', function() {
|
||
loadAvailableDates($(this).val());
|
||
});
|
||
|
||
function loadAvailableDates(classId) {
|
||
$.ajax({
|
||
url: 'fetch_available_dates.php',
|
||
method: 'POST',
|
||
data: {
|
||
class_id: classId
|
||
},
|
||
dataType: 'json',
|
||
success: function(data) {
|
||
$('#reprogramDate').datepicker('option', 'beforeShowDay', function(date) {
|
||
var s = $.datepicker.formatDate('yy-mm-dd', date);
|
||
return [data.availableDates.includes(s), ''];
|
||
});
|
||
},
|
||
error: () => alertBox({
|
||
title: 'Errore',
|
||
text: 'Errore nel caricamento delle date.',
|
||
icon: 'error'
|
||
})
|
||
});
|
||
}
|
||
|
||
$('#reprogramBtn').on('click', function() {
|
||
var id = $('#reprogramIdBookingClass').val(),
|
||
classId = $('#classSelect').val(),
|
||
newDate = $('#reprogramDate').val();
|
||
var idU = $('#classModal').data('current-id'),
|
||
nomeU = $('#userName').text();
|
||
if (!newDate || !classId) {
|
||
alertBox({
|
||
title: 'Errore',
|
||
text: 'Seleziona classe e data.',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
$.post('reprogram_class.php', {
|
||
id: id,
|
||
class_id: classId,
|
||
new_date: newDate
|
||
}, null, 'json')
|
||
.done(function(resp) {
|
||
if (resp.success) {
|
||
alertBox({
|
||
title: 'Fatto',
|
||
text: 'Lezione riprogrammata.',
|
||
icon: 'success'
|
||
});
|
||
$('#reprogramModal').modal('hide');
|
||
refreshTable(idU, nomeU);
|
||
} else alertBox({
|
||
title: 'Errore',
|
||
text: resp.message || 'Errore durante la riprogrammazione.',
|
||
icon: 'error'
|
||
});
|
||
})
|
||
.fail(() => alertBox({
|
||
title: 'Errore',
|
||
text: 'Errore durante la riprogrammazione.',
|
||
icon: 'error'
|
||
}));
|
||
});
|
||
|
||
// Email singola
|
||
$('.send-email').on('click', function() {
|
||
var id = $(this).data('id'),
|
||
nome = $(this).data('nome'),
|
||
email = $(this).data('email');
|
||
$('#emailModalLabel').text('Invia email a ' + nome);
|
||
$('#emailSubject').val('');
|
||
$('#emailBody').val('Ciao {nome},\n\n');
|
||
$('#emailRecipientCount').text('Destinatario: ' + email);
|
||
$('#emailRecipients').val(JSON.stringify([{
|
||
id: id,
|
||
email: email,
|
||
nome: nome
|
||
}]));
|
||
$('#emailModal').modal('show');
|
||
});
|
||
|
||
// Email di massa: abbonati attivi oppure abbonati con certificato scaduto
|
||
$('#sendEmailToAll, #sendEmailCertExpired').on('click', function() {
|
||
var isCert = this.id === 'sendEmailCertExpired';
|
||
var selector = isCert ?
|
||
'.send-email[data-attivo="1"][data-certscaduto="1"]' :
|
||
'.send-email[data-attivo="1"]';
|
||
var recipients = [];
|
||
$(selector).each(function() {
|
||
recipients.push({
|
||
id: $(this).data('id'),
|
||
email: $(this).data('email'),
|
||
nome: $(this).data('nome')
|
||
});
|
||
});
|
||
|
||
if (recipients.length === 0) {
|
||
alertBox({
|
||
title: 'Attenzione',
|
||
text: 'Nessun destinatario disponibile.',
|
||
icon: 'warning'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// Anteprima destinatari + doppia conferma
|
||
var previewHtml = '<p>Stai per inviare un\'email a <strong>' + recipients.length + ' utenti</strong>.</p>';
|
||
previewHtml += '<div class="recipient-preview">';
|
||
recipients.forEach(function(r) {
|
||
previewHtml += '<div>' + r.nome + ' <' + r.email + '></div>';
|
||
});
|
||
previewHtml += '</div>';
|
||
previewHtml += '<p class="mt-3" style="font-size:13px;">Per confermare, scrivi <strong>INVIA</strong> qui sotto:</p>';
|
||
|
||
alertBox({
|
||
title: 'Conferma invio di massa',
|
||
html: previewHtml,
|
||
icon: 'warning',
|
||
input: 'text',
|
||
inputPlaceholder: 'Scrivi INVIA',
|
||
showCancelButton: true,
|
||
confirmButtonText: 'Procedi',
|
||
cancelButtonText: 'Annulla',
|
||
confirmButtonColor: '#007bff',
|
||
preConfirm: (val) => {
|
||
if (val !== 'INVIA') {
|
||
Swal.showValidationMessage('Devi scrivere INVIA per confermare');
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
$('#emailModalLabel').text('Invia email a ' + recipients.length + ' utenti');
|
||
if (isCert) {
|
||
$('#emailSubject').val('Promemoria - Certificato idoneità alla pratica Yoga');
|
||
$('#emailBody').val(TESTO_CERTIFICATO);
|
||
} else {
|
||
$('#emailSubject').val('');
|
||
$('#emailBody').val('Ciao {nome},\n\n');
|
||
}
|
||
$('#emailRecipientCount').text(recipients.length + ' destinatari');
|
||
$('#emailRecipients').val(JSON.stringify(recipients));
|
||
$('#emailModal').modal('show');
|
||
}
|
||
});
|
||
});
|
||
|
||
// Invio effettivo email: a gruppi, con barra di avanzamento
|
||
var invioInCorso = false;
|
||
|
||
window.addEventListener('beforeunload', function(e) {
|
||
if (invioInCorso) {
|
||
e.preventDefault();
|
||
e.returnValue = '';
|
||
}
|
||
});
|
||
|
||
$('#sendEmailBtn').on('click', function() {
|
||
if (invioInCorso) return;
|
||
|
||
var subject = $('#emailSubject').val(),
|
||
body = $('#emailBody').val();
|
||
var recipients = JSON.parse($('#emailRecipients').val() || '[]');
|
||
|
||
if (!subject || !body) {
|
||
alertBox({
|
||
title: 'Errore',
|
||
text: 'Compila oggetto e messaggio.',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
if (recipients.length === 0) {
|
||
alertBox({
|
||
title: 'Errore',
|
||
text: 'Nessun destinatario.',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
invioInCorso = true;
|
||
$('#sendEmailBtn').prop('disabled', true);
|
||
$('#emailModal').modal('hide');
|
||
|
||
var BATCH = 5; // email per ogni richiesta
|
||
var total = recipients.length,
|
||
done = 0,
|
||
sent = 0,
|
||
failed = [];
|
||
|
||
alertBox({
|
||
title: 'Invio in corso…',
|
||
html: '<p id="sendProgressText">0 di ' + total + '</p>' +
|
||
'<div class="progress" style="height:22px;">' +
|
||
'<div id="sendProgressBar" class="progress-bar progress-bar-striped progress-bar-animated" style="width:0%">0%</div>' +
|
||
'</div>' +
|
||
'<p class="mt-3 mb-0 text-muted" style="font-size:13px;">Non chiudere questa pagina finché l\'invio non è terminato.</p>',
|
||
allowOutsideClick: false,
|
||
allowEscapeKey: false,
|
||
showConfirmButton: false
|
||
});
|
||
|
||
function aggiorna() {
|
||
var pct = Math.round(done / total * 100);
|
||
$('#sendProgressBar').css('width', pct + '%').text(pct + '%');
|
||
$('#sendProgressText').text(done + ' di ' + total);
|
||
}
|
||
|
||
function fine() {
|
||
invioInCorso = false;
|
||
$('#sendEmailBtn').prop('disabled', false);
|
||
var html = 'Email inviate: <strong>' + sent + '</strong> su ' + total + '.';
|
||
if (failed.length) {
|
||
html += '<br><br>Non inviate (' + failed.length + '):<br>' +
|
||
$('<div>').text(failed.join(', ')).html();
|
||
}
|
||
alertBox({
|
||
title: failed.length ? 'Invio completato con errori' : 'Invio completato',
|
||
html: html,
|
||
icon: failed.length ? 'warning' : 'success'
|
||
});
|
||
}
|
||
|
||
function inviaGruppo(start) {
|
||
if (start >= total) {
|
||
fine();
|
||
return;
|
||
}
|
||
var gruppo = recipients.slice(start, start + BATCH);
|
||
$.post('send_email.php', {
|
||
subject: subject,
|
||
body: body,
|
||
recipients: JSON.stringify(gruppo)
|
||
}, null, 'json')
|
||
.done(function(resp) {
|
||
if (resp && resp.success) {
|
||
sent += resp.sent || 0;
|
||
failed = failed.concat(resp.failed || []);
|
||
} else {
|
||
failed = failed.concat(gruppo.map(function(r) {
|
||
return r.email;
|
||
}));
|
||
}
|
||
})
|
||
.fail(function() {
|
||
failed = failed.concat(gruppo.map(function(r) {
|
||
return r.email;
|
||
}));
|
||
})
|
||
.always(function() {
|
||
done += gruppo.length;
|
||
aggiorna();
|
||
inviaGruppo(start + BATCH);
|
||
});
|
||
}
|
||
|
||
inviaGruppo(0);
|
||
});
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|