Files
yogibook_aury_new/public/situationusers.php
T
2026-10-05 15:06:57 +02:00

949 lines
45 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,
* con 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; ?>';
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((xhr) => alertBox({
title: 'Errore',
text: 'Errore durante l\'invio: ' + (xhr.responseText || xhr.statusText || 'nessuna risposta').substring(0, 400),
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);
$('#emailBody').val('Ciao ' + nome.split(' ')[0] + ',\n\n');
$('#emailRecipientCount').text('Destinatario: ' + email);
$('#emailRecipients').val(JSON.stringify([{
id: id,
email: email,
nome: nome
}]));
$('#emailModal').modal('show');
});
// Email a TUTTI con routine di conferma robusta
$('#sendEmailToAll, #sendEmailCertExpired').on('click', function() {
var selector = this.id === 'sendEmailCertExpired' ?
'.send-email[data-attivo="1"][data-certscaduto="1"]' :
'.send-email[data-attivo="1"]';
var isCert = this.id === 'sendEmailCertExpired';
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
$('#sendEmailBtn').on('click', function() {
var subject = $('#emailSubject').val(),
body = $('#emailBody').val();
var recipients = $('#emailRecipients').val();
if (!subject || !body) {
alertBox({
title: 'Errore',
text: 'Compila oggetto e messaggio.',
icon: 'error'
});
return;
}
$.post('send_email.php', {
subject: subject,
body: body,
recipients: recipients
}, null, 'json')
.done(function(resp) {
if (resp.success) {
alertBox({
title: 'Fatto',
text: resp.message,
icon: 'success'
});
$('#emailModal').modal('hide');
} else alertBox({
title: 'Errore',
text: resp.message,
icon: 'error'
});
})
.fail(() => alertBox({
title: 'Errore',
text: 'Errore durante l\'invio.',
icon: 'error'
}));
});
});
</script>
</body>
</html>