added teacher and option teacher in school
This commit is contained in:
+166
-53
@@ -1,13 +1,19 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* YogiBook - Riepilogo classi giornaliero per l'insegnante.
|
||||
* Invia UNA mail per ogni classe del giorno.
|
||||
* YogiBook - Riepilogo classi giornaliero.
|
||||
* Invia UNA mail per ogni classe del giorno:
|
||||
* - all'insegnante della classe (se notifiche attive, attivo e con email)
|
||||
* -> versione riservata: nomi "Mario R.", senza email dei partecipanti
|
||||
* - al titolare, se diverso dall'insegnante (se notifiche attive e con email)
|
||||
* -> copia separata con i dati completi
|
||||
* - se l'insegnante È il titolare: una sola mail completa
|
||||
*
|
||||
* Uso:
|
||||
* php cron/class_report.php -> classi di oggi
|
||||
* php cron/class_report.php 2026-09-25 -> classi di una data specifica
|
||||
* php cron/class_report.php --dry-run -> NON invia, salva le anteprime HTML in cron/preview/
|
||||
* php cron/class_report.php --only-at=17 -> esce senza fare nulla se in Italia non sono le 17
|
||||
*/
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
@@ -56,12 +62,35 @@ function chip(string $label, string $bg, string $fg): string
|
||||
return '<span style="display:inline-block;background:' . $bg . ';color:' . $fg . ';font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;white-space:nowrap;">' . h($label) . '</span>';
|
||||
}
|
||||
|
||||
/** Nome completo (versione titolare) */
|
||||
function fullName(array $p): string
|
||||
{
|
||||
$n = trim(($p['first_name'] ?? '') . ' ' . ($p['last_name'] ?? ''));
|
||||
return $n !== '' ? $n : 'Utente #' . (int) ($p['iduser'] ?? 0);
|
||||
}
|
||||
|
||||
/** Nome + iniziale del cognome (versione insegnante): "Mario R." */
|
||||
function shortName(array $p): string
|
||||
{
|
||||
$first = trim((string) ($p['first_name'] ?? ''));
|
||||
$last = trim((string) ($p['last_name'] ?? ''));
|
||||
if ($first === '' && $last === '') {
|
||||
return 'Utente #' . (int) ($p['iduser'] ?? 0);
|
||||
}
|
||||
$ini = $last !== '' ? ' ' . mb_strtoupper(mb_substr($last, 0, 1)) . '.' : '';
|
||||
return ($first !== '' ? $first : '') . $ini;
|
||||
}
|
||||
|
||||
function personName(array $p, bool $full): string
|
||||
{
|
||||
return $full ? fullName($p) : shortName($p);
|
||||
}
|
||||
|
||||
function teacherName(?array $t): string
|
||||
{
|
||||
return $t ? trim($t['nameemployee'] . ' ' . $t['surnameemployee']) : '';
|
||||
}
|
||||
|
||||
function italianDate(DateTime $d): string
|
||||
{
|
||||
$days = ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'];
|
||||
@@ -69,6 +98,27 @@ function italianDate(DateTime $d): string
|
||||
return $days[(int) $d->format('w')] . ' ' . (int) $d->format('j') . ' ' . $months[(int) $d->format('n')] . ' ' . $d->format('Y');
|
||||
}
|
||||
|
||||
/** Motivo per cui non può ricevere ('' = può ricevere) */
|
||||
function whyNot(?array $t): string
|
||||
{
|
||||
if (!$t) return 'nessun insegnante assegnato';
|
||||
if (($t['active'] ?? 'N') !== 'Y') return 'insegnante non attivo';
|
||||
if (($t['notifications'] ?? 'N') !== 'Y') return 'notifiche disattivate';
|
||||
if (empty($t['email'])) return 'email mancante';
|
||||
if (!filter_var($t['email'], FILTER_VALIDATE_EMAIL)) return 'email non valida';
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Può ricevere mail? notifiche attive, attivo, email valida */
|
||||
function canReceive(?array $t): bool
|
||||
{
|
||||
return $t
|
||||
&& ($t['notifications'] ?? 'N') === 'Y'
|
||||
&& ($t['active'] ?? 'N') === 'Y'
|
||||
&& !empty($t['email'])
|
||||
&& filter_var($t['email'], FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------- argomenti */
|
||||
|
||||
$dryRun = in_array('--dry-run', $argv, true);
|
||||
@@ -79,18 +129,27 @@ foreach (array_slice($argv, 1) as $a) {
|
||||
}
|
||||
}
|
||||
|
||||
$recipients = array_values(array_filter(array_map('trim', explode(',', (string) cfg('TEACHER_EMAIL', '')))));
|
||||
if (!$recipients && !$dryRun) {
|
||||
logmsg('ERRORE: TEACHER_EMAIL non impostato nel .env');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$location = cfg('CLASS_LOCATION', 'via Valassina 62/B Seregno - Sala Contesto Yoga');
|
||||
|
||||
// Le mail agli insegnanti (non titolari) partono solo se l'opzione "Mostra insegnante" è attiva
|
||||
$optRow = $pdo->query("SELECT show_teacher FROM `option` LIMIT 1")->fetch();
|
||||
$teacherMailsEnabled = ($optRow['show_teacher'] ?? 'N') === 'Y';
|
||||
|
||||
/* ---------------------------------------------------------- insegnanti */
|
||||
|
||||
$employees = [];
|
||||
$owner = null;
|
||||
foreach ($pdo->query("SELECT idemployee, nameemployee, surnameemployee, email, is_owner, notifications, active FROM employee")->fetchAll() as $t) {
|
||||
$employees[(int) $t['idemployee']] = $t;
|
||||
if ($t['is_owner'] === 'Y' && $owner === null) {
|
||||
$owner = $t;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------ classi del giorno */
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT ss.idserviceschedule, ss.dateschedule, s.servicename, s.colorclass, s.maxcapacity
|
||||
"SELECT ss.idserviceschedule, ss.dateschedule, s.servicename, s.colorclass, s.maxcapacity, s.idteacher
|
||||
FROM serviceschedule ss
|
||||
LEFT JOIN service s ON ss.idservice = s.idservice
|
||||
WHERE ss.dateschedule >= :d1 AND ss.dateschedule < :d2
|
||||
@@ -127,7 +186,7 @@ foreach ($stmtB->fetchAll() as $b) {
|
||||
|
||||
/* ------------------------------------------------------------ template */
|
||||
|
||||
function personRows(array $list, string $type): string
|
||||
function personRows(array $list, string $type, bool $full): string
|
||||
{
|
||||
if (!$list) {
|
||||
return '<tr><td style="padding:16px;color:#9ca3af;font-size:14px;text-align:center;">Nessun partecipante</td></tr>';
|
||||
@@ -145,12 +204,14 @@ function personRows(array $list, string $type): string
|
||||
$chipHtml = chip('In coda', '#fbeef3', '#d1698a');
|
||||
}
|
||||
$nameColor = $isLost ? '#9ca3af' : '#1f2937';
|
||||
$email = !empty($p['email']) ? '<br><span style="font-size:12px;color:#6b7280;font-weight:400;">' . h($p['email']) . '</span>' : '';
|
||||
$email = ($full && !empty($p['email']))
|
||||
? '<br><span style="font-size:12px;color:#6b7280;font-weight:400;">' . h($p['email']) . '</span>'
|
||||
: '';
|
||||
|
||||
$html .= '<tr><td style="padding:10px 14px;border-bottom:1px solid #eef0f3;">'
|
||||
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>'
|
||||
. '<td width="34" valign="top" style="font-weight:800;color:' . $numColor . ';font-size:14px;padding-top:1px;">' . $i . ($type === 'waiting' ? '°' : '.') . '</td>'
|
||||
. '<td valign="top" style="font-size:15px;color:' . $nameColor . ';font-weight:600;">' . h(fullName($p)) . $email . '</td>'
|
||||
. '<td valign="top" style="font-size:15px;color:' . $nameColor . ';font-weight:600;">' . h(personName($p, $full)) . $email . '</td>'
|
||||
. '<td valign="top" align="right" width="100">' . $chipHtml . '</td>'
|
||||
. '</tr></table></td></tr>';
|
||||
$i++;
|
||||
@@ -174,7 +235,12 @@ function statBox(string $value, string $label, string $color): string
|
||||
. '</td>';
|
||||
}
|
||||
|
||||
function renderEmail(array $cls, array $booked, array $pending, array $waiting, string $location): string
|
||||
/**
|
||||
* @param bool $full true = versione titolare (nomi completi + email)
|
||||
* @param string $teacherFull nome insegnante da mostrare in testata
|
||||
* @param string|null $copyNote nota "copia per il titolare" (solo se il titolare non è l'insegnante)
|
||||
*/
|
||||
function renderEmail(array $cls, array $booked, array $pending, array $waiting, string $location, bool $full, string $teacherFull, ?string $copyNote): string
|
||||
{
|
||||
$d = new DateTime($cls['dateschedule']);
|
||||
$accent = $cls['colorclass'] ?: '#1ebf73';
|
||||
@@ -195,42 +261,54 @@ function renderEmail(array $cls, array $booked, array $pending, array $waiting,
|
||||
? '<div style="font-size:13px;color:#6b7280;margin-top:6px;">di cui ' . $nLost . ' segnat' . ($nLost === 1 ? 'a' : 'e') . ' come pers' . ($nLost === 1 ? 'a' : 'e') . '</div>'
|
||||
: '';
|
||||
|
||||
$body = section('Partecipanti confermati (' . $nBooked . ')', '#374151', personRows($booked, 'booked'));
|
||||
$body = section('Partecipanti confermati (' . $nBooked . ')', '#374151', personRows($booked, 'booked', $full));
|
||||
if ($pending) {
|
||||
$body .= section('In attesa di conferma (' . count($pending) . ')', '#8a6d00', personRows($pending, 'pending'));
|
||||
$body .= section('In attesa di conferma (' . count($pending) . ')', '#8a6d00', personRows($pending, 'pending', $full));
|
||||
}
|
||||
if ($waiting) {
|
||||
$body .= section('Lista d\'attesa (' . count($waiting) . ')', '#d1698a', personRows($waiting, 'waiting'));
|
||||
$body .= section('Lista d\'attesa (' . count($waiting) . ')', '#d1698a', personRows($waiting, 'waiting', $full));
|
||||
}
|
||||
|
||||
$copyBanner = $copyNote
|
||||
? '<tr><td style="padding:14px 28px 0;"><div style="background:#f5eee4;color:#5a4630;border-radius:10px;padding:10px 14px;font-size:13px;">' . $copyNote . '</div></td></tr>'
|
||||
: '';
|
||||
|
||||
$teacherLine = $teacherFull !== ''
|
||||
? '<div style="font-size:13px;color:#6b7280;margin-top:4px;">Insegnante: <strong style="color:#374151;">' . h($teacherFull) . '</strong></div>'
|
||||
: '';
|
||||
|
||||
return '<!doctype html><html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' . h($cls['servicename']) . '</title></head>'
|
||||
. '<body style="margin:0;padding:0;background:#f3f5f7;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;">'
|
||||
. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f5f7;padding:24px 12px;"><tr><td align="center">'
|
||||
. '<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 3px 16px rgba(0,0,0,0.06);">'
|
||||
// barra colore classe
|
||||
. '<tr><td style="height:8px;background:' . h($accent) . ';font-size:0;line-height:0;"> </td></tr>'
|
||||
// intestazione
|
||||
. $copyBanner
|
||||
. '<tr><td style="padding:26px 28px 6px;">'
|
||||
. '<div style="font-size:12px;font-weight:700;color:#1ebf73;text-transform:uppercase;letter-spacing:0.06em;">YogiBook · Riepilogo classe</div>'
|
||||
. '<h1 style="margin:8px 0 6px;font-size:24px;color:#1f2937;">' . h($cls['servicename']) . '</h1>'
|
||||
. '<div style="font-size:15px;color:#374151;font-weight:600;">' . h(italianDate($d)) . ' · ore ' . $d->format('H:i') . '</div>'
|
||||
. '<div style="font-size:13px;color:#6b7280;margin-top:4px;">' . h($location) . '</div>'
|
||||
. $teacherLine
|
||||
. '</td></tr>'
|
||||
// statistiche
|
||||
. '<tr><td style="padding:16px 22px 0;">' . $stats . '<div style="padding:0 6px;">' . $lostNote . '</div></td></tr>'
|
||||
// elenchi
|
||||
. $body
|
||||
// footer
|
||||
. '<tr><td style="padding:26px 28px;font-size:12px;color:#9ca3af;text-align:center;">Generato automaticamente da YogiBook il ' . date('d/m/Y \a\l\l\e H:i') . '</td></tr>'
|
||||
. '</table></td></tr></table></body></html>';
|
||||
}
|
||||
|
||||
function renderText(array $cls, array $booked, array $pending, array $waiting, string $location): string
|
||||
function renderText(array $cls, array $booked, array $pending, array $waiting, string $location, bool $full, string $teacherFull, ?string $copyNote): string
|
||||
{
|
||||
$d = new DateTime($cls['dateschedule']);
|
||||
$max = (int) $cls['maxcapacity'];
|
||||
$t = $cls['servicename'] . "\n" . italianDate($d) . ' ore ' . $d->format('H:i') . "\n" . $location . "\n\n";
|
||||
$t .= 'Iscritti: ' . count($booked) . '/' . $max . ' | In attesa: ' . count($pending) . ' | In coda: ' . count($waiting) . "\n\n";
|
||||
$t = '';
|
||||
if ($copyNote) {
|
||||
$t .= strip_tags($copyNote) . "\n\n";
|
||||
}
|
||||
$t .= $cls['servicename'] . "\n" . italianDate($d) . ' ore ' . $d->format('H:i') . "\n" . $location . "\n";
|
||||
if ($teacherFull !== '') {
|
||||
$t .= 'Insegnante: ' . $teacherFull . "\n";
|
||||
}
|
||||
$t .= "\nIscritti: " . count($booked) . '/' . $max . ' | In attesa: ' . count($pending) . ' | In coda: ' . count($waiting) . "\n\n";
|
||||
$blocks = ['CONFERMATI' => $booked, 'IN ATTESA DI CONFERMA' => $pending, "LISTA D'ATTESA" => $waiting];
|
||||
foreach ($blocks as $title => $list) {
|
||||
if ($title !== 'CONFERMATI' && !$list) {
|
||||
@@ -243,7 +321,7 @@ function renderText(array $cls, array $booked, array $pending, array $waiting, s
|
||||
$i = 1;
|
||||
foreach ($list as $p) {
|
||||
$lost = ($p['status'] === 'booked' && ($p['lostlesson'] ?? '') === 'Y') ? ' (PERSA)' : '';
|
||||
$t .= ' ' . $i++ . '. ' . fullName($p) . $lost . "\n";
|
||||
$t .= ' ' . $i++ . '. ' . personName($p, $full) . $lost . "\n";
|
||||
}
|
||||
$t .= "\n";
|
||||
}
|
||||
@@ -276,6 +354,7 @@ function makeMailer(): PHPMailer
|
||||
$mailer = $dryRun ? null : makeMailer();
|
||||
$sent = 0;
|
||||
$failed = 0;
|
||||
$skipped = 0;
|
||||
|
||||
foreach ($classes as $cls) {
|
||||
$sid = (int) $cls['idserviceschedule'];
|
||||
@@ -284,51 +363,85 @@ foreach ($classes as $cls) {
|
||||
$pending = array_values(array_filter($all, fn($b) => $b['status'] === 'pending'));
|
||||
$waiting = array_values(array_filter($all, fn($b) => $b['status'] === 'waiting'));
|
||||
|
||||
$teacher = $employees[(int) $cls['idteacher']] ?? null;
|
||||
$teacherIsOwner = $teacher && $owner && (int) $teacher['idemployee'] === (int) $owner['idemployee'];
|
||||
$tName = teacherName($teacher);
|
||||
|
||||
$d = new DateTime($cls['dateschedule']);
|
||||
$subject = $cls['servicename'] . ' · ' . $d->format('d/m') . ' ore ' . $d->format('H:i')
|
||||
$baseSubject = $cls['servicename'] . ' · ' . $d->format('d/m') . ' ore ' . $d->format('H:i')
|
||||
. ' · ' . count($booked) . '/' . (int) $cls['maxcapacity'] . ' iscritti';
|
||||
if ($pending) {
|
||||
$subject .= ' · ' . count($pending) . ' in attesa';
|
||||
$baseSubject .= ' · ' . count($pending) . ' in attesa';
|
||||
}
|
||||
if ($waiting) {
|
||||
$subject .= ' · ' . count($waiting) . ' in coda';
|
||||
$baseSubject .= ' · ' . count($waiting) . ' in coda';
|
||||
}
|
||||
|
||||
$html = renderEmail($cls, $booked, $pending, $waiting, $location);
|
||||
$text = renderText($cls, $booked, $pending, $waiting, $location);
|
||||
|
||||
if ($dryRun) {
|
||||
$dir = __DIR__ . '/preview';
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
// Chi riceve cosa
|
||||
$deliveries = [];
|
||||
if ($teacherIsOwner) {
|
||||
if (canReceive($owner)) {
|
||||
$deliveries[] = ['to' => $owner, 'full' => true, 'copy' => null, 'tag' => 'titolare'];
|
||||
}
|
||||
$file = $dir . '/classe_' . $sid . '.html';
|
||||
file_put_contents($file, $html);
|
||||
logmsg("[DRY-RUN] $subject -> $file");
|
||||
} else {
|
||||
if (!$teacherMailsEnabled) {
|
||||
logmsg("Classe #$sid ({$cls['servicename']}): mail all'insegnante non inviata (opzione insegnanti disattivata)");
|
||||
} elseif (canReceive($teacher)) {
|
||||
$deliveries[] = ['to' => $teacher, 'full' => false, 'copy' => null, 'tag' => 'insegnante'];
|
||||
} else {
|
||||
logmsg("Classe #$sid ({$cls['servicename']}): insegnante " . ($tName ?: '-') . " escluso: " . whyNot($teacher));
|
||||
}
|
||||
if (canReceive($owner)) {
|
||||
$note = $tName !== ''
|
||||
? '<strong>Copia per il titolare</strong> · classe di ' . h($tName)
|
||||
: '<strong>Copia per il titolare</strong> · classe senza insegnante assegnato';
|
||||
$deliveries[] = ['to' => $owner, 'full' => true, 'copy' => $note, 'tag' => 'titolare'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$deliveries) {
|
||||
$skipped++;
|
||||
logmsg("SALTATA classe #$sid ({$cls['servicename']}): nessun destinatario (notifiche disattivate o email mancante).");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$mailer->clearAddresses();
|
||||
foreach ($recipients as $r) {
|
||||
$mailer->addAddress($r, cfg('TEACHER_NAME', ''));
|
||||
foreach ($deliveries as $dv) {
|
||||
$subject = ($dv['copy'] ? '[Copia] ' : '') . $baseSubject;
|
||||
$html = renderEmail($cls, $booked, $pending, $waiting, $location, $dv['full'], $tName, $dv['copy']);
|
||||
$text = renderText($cls, $booked, $pending, $waiting, $location, $dv['full'], $tName, $dv['copy']);
|
||||
$toName = teacherName($dv['to']);
|
||||
|
||||
if ($dryRun) {
|
||||
$dir = __DIR__ . '/preview';
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0775, true);
|
||||
}
|
||||
$file = $dir . '/classe_' . $sid . '_' . $dv['tag'] . '.html';
|
||||
file_put_contents($file, $html);
|
||||
logmsg("[DRY-RUN] a {$toName} <{$dv['to']['email']}> ({$dv['tag']}): $subject -> $file");
|
||||
continue;
|
||||
}
|
||||
$mailer->Subject = $subject;
|
||||
$mailer->Body = $html;
|
||||
$mailer->AltBody = $text;
|
||||
$mailer->send();
|
||||
$sent++;
|
||||
logmsg("OK: $subject");
|
||||
} catch (MailException $e) {
|
||||
$failed++;
|
||||
logmsg("ERRORE classe #$sid: " . $mailer->ErrorInfo);
|
||||
|
||||
try {
|
||||
$mailer->clearAddresses();
|
||||
$mailer->addAddress($dv['to']['email'], $toName);
|
||||
$mailer->Subject = $subject;
|
||||
$mailer->Body = $html;
|
||||
$mailer->AltBody = $text;
|
||||
$mailer->send();
|
||||
$sent++;
|
||||
logmsg("OK a {$toName} ({$dv['tag']}): $subject");
|
||||
} catch (MailException $e) {
|
||||
$failed++;
|
||||
logmsg("ERRORE classe #$sid a {$dv['to']['email']}: " . $mailer->ErrorInfo);
|
||||
}
|
||||
sleep(1); // evita limiti di invio del server SMTP
|
||||
}
|
||||
sleep(1); // evita limiti di invio del server SMTP
|
||||
}
|
||||
|
||||
if ($mailer) {
|
||||
$mailer->smtpClose();
|
||||
}
|
||||
|
||||
logmsg("Fine: $sent inviate, $failed errori (" . count($classes) . " classi il $day).");
|
||||
logmsg("Fine: $sent inviate, $failed errori, $skipped classi senza destinatari (" . count($classes) . " classi il $day).");
|
||||
exit($failed > 0 ? 1 : 0);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Hatha Yoga</title></head><body style="margin:0;padding:0;background:#f3f5f7;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f5f7;padding:24px 12px;"><tr><td align="center"><table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 3px 16px rgba(0,0,0,0.06);"><tr><td style="height:8px;background:#fa9b9b;font-size:0;line-height:0;"> </td></tr><tr><td style="padding:26px 28px 6px;"><div style="font-size:12px;font-weight:700;color:#1ebf73;text-transform:uppercase;letter-spacing:0.06em;">YogiBook · Riepilogo classe</div><h1 style="margin:8px 0 6px;font-size:24px;color:#1f2937;">Hatha Yoga</h1><div style="font-size:15px;color:#374151;font-weight:600;">Lunedì 5 Ottobre 2026 · ore 19:30</div><div style="font-size:13px;color:#6b7280;margin-top:4px;">via Valassina 62/B Seregno - Sala Contesto Yoga</div></td></tr><tr><td style="padding:16px 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#c0392b;line-height:1.1;">1/1</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Iscritti</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#374151;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Posti liberi</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#8a6d00;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In attesa</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#d1698a;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In coda</div></td></tr></table><div style="padding:0 6px;"></div></td></tr><tr><td style="padding:22px 28px 0;"><div style="font-size:13px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:0.04em;margin-bottom:8px;">Partecipanti confermati (1)</div><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc;border:1px solid #eef0f3;border-radius:10px;"><tr><td style="padding:10px 14px;border-bottom:1px solid #eef0f3;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td width="34" valign="top" style="font-weight:800;color:#1ebf73;font-size:14px;padding-top:1px;">1.</td><td valign="top" style="font-size:15px;color:#1f2937;font-weight:600;">Amelia Benucci<br><span style="font-size:12px;color:#6b7280;font-weight:400;">info@claudiosironi.com</span></td><td valign="top" align="right" width="100"><span style="display:inline-block;background:#e3f2ec;color:#1a7f52;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;white-space:nowrap;">Confermata</span></td></tr></table></td></tr></table></td></tr><tr><td style="padding:26px 28px;font-size:12px;color:#9ca3af;text-align:center;">Generato automaticamente da YogiBook il 01/10/2026 alle 11:15</td></tr></table></td></tr></table></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Hatha Yoga</title></head><body style="margin:0;padding:0;background:#f3f5f7;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f5f7;padding:24px 12px;"><tr><td align="center"><table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 3px 16px rgba(0,0,0,0.06);"><tr><td style="height:8px;background:#fa9b9b;font-size:0;line-height:0;"> </td></tr><tr><td style="padding:26px 28px 6px;"><div style="font-size:12px;font-weight:700;color:#1ebf73;text-transform:uppercase;letter-spacing:0.06em;">YogiBook · Riepilogo classe</div><h1 style="margin:8px 0 6px;font-size:24px;color:#1f2937;">Hatha Yoga</h1><div style="font-size:15px;color:#374151;font-weight:600;">Lunedì 5 Ottobre 2026 · ore 19:30</div><div style="font-size:13px;color:#6b7280;margin-top:4px;">via Valassina 62/B Seregno - Sala Contesto Yoga</div><div style="font-size:13px;color:#6b7280;margin-top:4px;">Insegnante: <strong style="color:#374151;">Francesca -</strong></div></td></tr><tr><td style="padding:16px 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#c0392b;line-height:1.1;">1/1</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Iscritti</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#374151;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Posti liberi</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#8a6d00;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In attesa</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#d1698a;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In coda</div></td></tr></table><div style="padding:0 6px;"></div></td></tr><tr><td style="padding:22px 28px 0;"><div style="font-size:13px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:0.04em;margin-bottom:8px;">Partecipanti confermati (1)</div><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc;border:1px solid #eef0f3;border-radius:10px;"><tr><td style="padding:10px 14px;border-bottom:1px solid #eef0f3;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td width="34" valign="top" style="font-weight:800;color:#1ebf73;font-size:14px;padding-top:1px;">1.</td><td valign="top" style="font-size:15px;color:#1f2937;font-weight:600;">Amelia B.</td><td valign="top" align="right" width="100"><span style="display:inline-block;background:#e3f2ec;color:#1a7f52;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;white-space:nowrap;">Confermata</span></td></tr></table></td></tr></table></td></tr><tr><td style="padding:26px 28px;font-size:12px;color:#9ca3af;text-align:center;">Generato automaticamente da YogiBook il 01/10/2026 alle 11:30</td></tr></table></td></tr></table></body></html>
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Hatha Yoga</title></head><body style="margin:0;padding:0;background:#f3f5f7;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f5f7;padding:24px 12px;"><tr><td align="center"><table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 3px 16px rgba(0,0,0,0.06);"><tr><td style="height:8px;background:#fa9b9b;font-size:0;line-height:0;"> </td></tr><tr><td style="padding:14px 28px 0;"><div style="background:#f5eee4;color:#5a4630;border-radius:10px;padding:10px 14px;font-size:13px;"><strong>Copia per il titolare</strong> · classe di Francesca -</div></td></tr><tr><td style="padding:26px 28px 6px;"><div style="font-size:12px;font-weight:700;color:#1ebf73;text-transform:uppercase;letter-spacing:0.06em;">YogiBook · Riepilogo classe</div><h1 style="margin:8px 0 6px;font-size:24px;color:#1f2937;">Hatha Yoga</h1><div style="font-size:15px;color:#374151;font-weight:600;">Lunedì 5 Ottobre 2026 · ore 19:30</div><div style="font-size:13px;color:#6b7280;margin-top:4px;">via Valassina 62/B Seregno - Sala Contesto Yoga</div><div style="font-size:13px;color:#6b7280;margin-top:4px;">Insegnante: <strong style="color:#374151;">Francesca -</strong></div></td></tr><tr><td style="padding:16px 22px 0;"><table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#c0392b;line-height:1.1;">1/1</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Iscritti</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#374151;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">Posti liberi</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#8a6d00;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In attesa</div></td><td align="center" style="padding:12px 6px;background:#f8fafc;border-radius:10px;"><div style="font-size:22px;font-weight:800;color:#d1698a;line-height:1.1;">0</div><div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.04em;margin-top:4px;">In coda</div></td></tr></table><div style="padding:0 6px;"></div></td></tr><tr><td style="padding:22px 28px 0;"><div style="font-size:13px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:0.04em;margin-bottom:8px;">Partecipanti confermati (1)</div><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc;border:1px solid #eef0f3;border-radius:10px;"><tr><td style="padding:10px 14px;border-bottom:1px solid #eef0f3;"><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td width="34" valign="top" style="font-weight:800;color:#1ebf73;font-size:14px;padding-top:1px;">1.</td><td valign="top" style="font-size:15px;color:#1f2937;font-weight:600;">Amelia Benucci<br><span style="font-size:12px;color:#6b7280;font-weight:400;">info@claudiosironi.com</span></td><td valign="top" align="right" width="100"><span style="display:inline-block;background:#e3f2ec;color:#1a7f52;font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;white-space:nowrap;">Confermata</span></td></tr></table></td></tr></table></td></tr><tr><td style="padding:26px 28px;font-size:12px;color:#9ca3af;text-align:center;">Generato automaticamente da YogiBook il 01/10/2026 alle 11:30</td></tr></table></td></tr></table></body></html>
|
||||
Reference in New Issue
Block a user