Files
yogibook_aury_new/public/situationusers.php
T
2026-10-14 15:56:09 +02:00

1055 lines
51 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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() AND bookingclass.lostlesson = 'N' 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() AND bookingclass.lostlesson = 'N' 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="lessonSelect" class="form-label">Nuova lezione</label>
<select class="form-select" id="lessonSelect"></select>
<div class="form-text">Lezioni della stessa classe e delle classi associate, nei prossimi 90 giorni.</div>
</div>
<div id="reprogramInfo" class="text-muted mb-3" style="font-size:13px;"></div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="rpSendEmail" checked>
<label class="form-check-label" for="rpSendEmail">Invia email all'allieva con la nuova data</label>
</div>
<div class="form-check" id="rpCountWrap">
<input class="form-check-input" type="checkbox" id="rpCountReprog">
<label class="form-check-label" for="rpCountReprog">Conta come riprogrammazione</label>
</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)
.data('order', row.idorder || ''));
} 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: l'insegnante sceglie email e conteggio riprogrammazione
$(document).on('click', '.remove-class', function() {
var id = $(this).data('id');
var hasOrder = String($(this).data('order') || '') !== '';
var idU = $('#classModal').data('current-id'),
nomeU = $('#userName').text();
var html = '<p>La lezione verrà rimossa e salvata nello storico delle cancellazioni.</p>' +
'<div class="text-start" style="max-width:360px;margin:0 auto;">' +
'<div class="form-check mb-2">' +
'<input class="form-check-input" type="checkbox" id="swSendEmail" checked>' +
'<label class="form-check-label" for="swSendEmail">Invia email di conferma all\'allieva</label>' +
'</div>' +
(hasOrder ?
'<div class="form-check">' +
'<input class="form-check-input" type="checkbox" id="swCountReprog">' +
'<label class="form-check-label" for="swCountReprog">Conta come riprogrammazione</label>' +
'</div>' :
'<div class="text-muted" style="font-size:13px;">Lezione omaggio: nessuna riprogrammazione da conteggiare.</div>') +
'</div>';
alertBox({
title: 'Cancellare la lezione?',
html: html,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
confirmButtonText: 'Sì, cancella',
cancelButtonText: 'Annulla',
preConfirm: () => ({
send: $('#swSendEmail').is(':checked') ? 'Y' : 'N',
count: $('#swCountReprog').is(':checked') ? 'Y' : 'N'
})
}).then((r) => {
if (!r.isConfirmed) return;
$.post('admin_cancel_booking.php', {
id: id,
send_email: r.value.send,
count_reprog: r.value.count
}, null, 'json')
.done(function(resp) {
if (resp && resp.success) {
alertBox({
title: 'Fatto',
text: resp.message,
icon: 'success'
});
refreshTable(idU, nomeU);
} else {
alertBox({
title: 'Errore',
text: (resp && resp.message) || 'Errore durante la cancellazione.',
icon: 'error'
});
}
})
.fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.message) || 'Errore durante la cancellazione.';
alertBox({
title: 'Errore',
text: msg,
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 (scuola): scegli una lezione già in calendario
$(document).on('click', '.reprogram-class', function() {
var id = $(this).data('id');
$('#reprogramIdBookingClass').val(id);
$('#reprogramModalLabel').text('Riprogramma: ' + $(this).data('servicename'));
$('#lessonSelect').html('<option>Caricamento…</option>').prop('disabled', true);
$('#reprogramInfo').text('');
$('#rpSendEmail').prop('checked', true);
$('#rpCountReprog').prop('checked', false);
$('#reprogramModal').data('hasorder', false).modal('show');
$.getJSON('admin_reprogram_lessons.php', {
id: id
})
.done(function(data) {
var sel = $('#lessonSelect').empty();
if (!data.success) {
sel.append($('<option>').text(data.message || 'Errore'));
return;
}
sel.prop('disabled', false)
.append($('<option value="">').text('— Scegli la nuova lezione —'));
if (!data.lessons.length) {
sel.append($('<option disabled>').text('Nessuna lezione nei prossimi 90 giorni'));
}
$.each(data.lessons, function(i, l) {
var txt = l.label + ' · ' + l.servicename + ' · ' + l.occupied + (l.max !== null ? '/' + l.max : '') + ' posti';
if (l.mine) txt += ' (già iscritta)';
else if (l.full) txt += ' (piena)';
else if (l.after_expiry) txt += ' (oltre la scadenza del pacchetto)';
sel.append($('<option>').val(l.id).text(txt).prop('disabled', l.mine || l.full));
});
var o = data.order;
$('#reprogramModal').data('hasorder', o.has_order);
$('#rpCountWrap').toggle(o.has_order);
$('#reprogramInfo').text(o.has_order ?
'Riprogrammazioni usate: ' + o.reprogrammed + ' su ' + o.maxreschedule +
(o.expireon ? ' · Scadenza pacchetto: ' + o.expireon : '') :
'Lezione omaggio: nessuna riprogrammazione da conteggiare.');
})
.fail(function() {
$('#lessonSelect').html('<option>Errore nel caricamento delle lezioni</option>');
});
});
$('#reprogramBtn').on('click', function() {
var btn = $(this);
var id = $('#reprogramIdBookingClass').val(),
sched = $('#lessonSelect').val();
var idU = $('#classModal').data('current-id'),
nomeU = $('#userName').text();
if (!sched) {
alertBox({
title: 'Attenzione',
text: 'Scegli la nuova lezione.',
icon: 'warning'
});
return;
}
btn.prop('disabled', true);
$.post('admin_reprogram_booking.php', {
id: id,
idschedule: sched,
send_email: $('#rpSendEmail').is(':checked') ? 'Y' : 'N',
count_reprog: ($('#reprogramModal').data('hasorder') && $('#rpCountReprog').is(':checked')) ? 'Y' : 'N'
}, null, 'json')
.done(function(resp) {
if (resp && resp.success) {
$('#reprogramModal').modal('hide');
alertBox({
title: 'Fatto',
text: resp.message,
icon: 'success'
});
refreshTable(idU, nomeU);
} else {
alertBox({
title: 'Errore',
text: (resp && resp.message) || 'Errore durante la riprogrammazione.',
icon: 'error'
});
}
})
.fail(function(xhr) {
var msg = (xhr.responseJSON && xhr.responseJSON.message) || 'Errore durante la riprogrammazione.';
alertBox({
title: 'Errore',
text: msg,
icon: 'error'
});
})
.always(function() {
btn.prop('disabled', false);
});
});
// 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 + ' &lt;' + r.email + '&gt;</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>