added teacher and option teacher in school
This commit is contained in:
+151
-38
@@ -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);
|
||||
// Chi riceve cosa
|
||||
$deliveries = [];
|
||||
if ($teacherIsOwner) {
|
||||
if (canReceive($owner)) {
|
||||
$deliveries[] = ['to' => $owner, 'full' => true, 'copy' => null, 'tag' => 'titolare'];
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
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 . '.html';
|
||||
$file = $dir . '/classe_' . $sid . '_' . $dv['tag'] . '.html';
|
||||
file_put_contents($file, $html);
|
||||
logmsg("[DRY-RUN] $subject -> $file");
|
||||
logmsg("[DRY-RUN] a {$toName} <{$dv['to']['email']}> ({$dv['tag']}): $subject -> $file");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$mailer->clearAddresses();
|
||||
foreach ($recipients as $r) {
|
||||
$mailer->addAddress($r, cfg('TEACHER_NAME', ''));
|
||||
}
|
||||
$mailer->addAddress($dv['to']['email'], $toName);
|
||||
$mailer->Subject = $subject;
|
||||
$mailer->Body = $html;
|
||||
$mailer->AltBody = $text;
|
||||
$mailer->send();
|
||||
$sent++;
|
||||
logmsg("OK: $subject");
|
||||
logmsg("OK a {$toName} ({$dv['tag']}): $subject");
|
||||
} catch (MailException $e) {
|
||||
$failed++;
|
||||
logmsg("ERRORE classe #$sid: " . $mailer->ErrorInfo);
|
||||
logmsg("ERRORE classe #$sid a {$dv['to']['email']}: " . $mailer->ErrorInfo);
|
||||
}
|
||||
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>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class ExtendEmployeeForTeachers extends AbstractMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->table('employee')
|
||||
->addColumn('email', 'string', ['limit' => 150, 'null' => true, 'after' => 'surnameemployee'])
|
||||
->addColumn('phone', 'string', ['limit' => 40, 'null' => true, 'after' => 'email'])
|
||||
->addColumn('photo', 'string', ['limit' => 191, 'null' => true, 'after' => 'phone'])
|
||||
->addColumn('is_owner', 'char', [
|
||||
'limit' => 1,
|
||||
'null' => false,
|
||||
'default' => 'N',
|
||||
'comment' => 'Y = titolare della scuola (uno solo)',
|
||||
'after' => 'photo',
|
||||
])
|
||||
->addColumn('notifications', 'char', [
|
||||
'limit' => 1,
|
||||
'null' => false,
|
||||
'default' => 'Y',
|
||||
'comment' => 'Y = riceve le email',
|
||||
'after' => 'is_owner',
|
||||
])
|
||||
->addColumn('active', 'char', ['limit' => 1, 'null' => false, 'default' => 'Y', 'after' => 'notifications'])
|
||||
->addColumn('created_at', 'timestamp', ['null' => true, 'default' => 'CURRENT_TIMESTAMP'])
|
||||
->addColumn('updated_at', 'timestamp', ['null' => true, 'default' => null, 'update' => 'CURRENT_TIMESTAMP'])
|
||||
->update();
|
||||
|
||||
// L'insegnante già presente (id più basso) diventa il titolare
|
||||
$this->execute("UPDATE `employee` SET `is_owner` = 'Y' ORDER BY `idemployee` LIMIT 1");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->table('employee')
|
||||
->removeColumn('email')
|
||||
->removeColumn('phone')
|
||||
->removeColumn('photo')
|
||||
->removeColumn('is_owner')
|
||||
->removeColumn('notifications')
|
||||
->removeColumn('active')
|
||||
->removeColumn('created_at')
|
||||
->removeColumn('updated_at')
|
||||
->update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class AddShowTeacherToOption extends AbstractMigration
|
||||
{
|
||||
public function change(): void
|
||||
{
|
||||
$this->table('option')
|
||||
->addColumn('show_teacher', 'char', [
|
||||
'limit' => 1,
|
||||
'null' => false,
|
||||
'default' => 'N',
|
||||
'comment' => 'Y = mostra l\'insegnante nelle card delle classi',
|
||||
'after' => 'waitinglist_enabled',
|
||||
])
|
||||
->update();
|
||||
}
|
||||
}
|
||||
+173
-6
@@ -11,9 +11,56 @@ function e($value): string
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function teacherInitials(string $first, string $last): string
|
||||
{
|
||||
$ini = mb_strtoupper(mb_substr(trim($first), 0, 1) . mb_substr(trim($last), 0, 1));
|
||||
return $ini !== '' ? $ini : '?';
|
||||
}
|
||||
|
||||
function teacherColor(string $seed): string
|
||||
{
|
||||
$palette = ['#1ebf73', '#2980b9', '#8e44ad', '#e67e22', '#16a085', '#c0392b', '#2c3e50', '#b08d57'];
|
||||
return $palette[abs(crc32($seed)) % count($palette)];
|
||||
}
|
||||
|
||||
$message = $_GET['message'] ?? 'N';
|
||||
$insertFeedback = null;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Insegnanti (titolare per primo, poi attivi, poi alfabetico)
|
||||
* ---------------------------------------------------------------------- */
|
||||
$teachers = $pdo->query(
|
||||
"SELECT idemployee, nameemployee, surnameemployee, photo, is_owner, active
|
||||
FROM employee
|
||||
ORDER BY is_owner DESC, active DESC, surnameemployee, nameemployee"
|
||||
)->fetchAll();
|
||||
|
||||
$teachersById = [];
|
||||
$ownerId = 0;
|
||||
foreach ($teachers as $t) {
|
||||
$teachersById[(int) $t['idemployee']] = $t;
|
||||
if ($t['is_owner'] === 'Y' && !$ownerId) {
|
||||
$ownerId = (int) $t['idemployee'];
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Assegnazione insegnante a una classe esistente (POST)
|
||||
* ---------------------------------------------------------------------- */
|
||||
if (($_POST['action'] ?? '') === 'assignteacher') {
|
||||
$idservice = (int) ($_POST['idservice'] ?? 0);
|
||||
$idteacher = (int) ($_POST['idteacher'] ?? 0);
|
||||
|
||||
if ($idservice > 0 && isset($teachersById[$idteacher])) {
|
||||
$stmt = $pdo->prepare("UPDATE service SET idteacher = :t WHERE idservice = :s");
|
||||
$stmt->execute([':t' => $idteacher, ':s' => $idservice]);
|
||||
header('Location: ' . $_SERVER['PHP_SELF'] . '?message=teacherassigned');
|
||||
} else {
|
||||
header('Location: ' . $_SERVER['PHP_SELF'] . '?message=teachererror');
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Inserimento nuova classe (POST) con prepared statement
|
||||
* ---------------------------------------------------------------------- */
|
||||
@@ -26,14 +73,17 @@ if (isset($_POST['classinsert']) && $_POST['classinsert'] === 'Y') {
|
||||
$maxcapacity = $_POST['maxcapacity'] ?? '';
|
||||
$colorclass = trim($_POST['colorclass'] ?? '');
|
||||
$classduration = $_POST['classduration'] ?? '';
|
||||
$idteacher = (int) ($_POST['idteacher'] ?? 0);
|
||||
|
||||
if ($servicename === '') {
|
||||
$insertFeedback = ['type' => 'error', 'text' => 'La descrizione della classe è obbligatoria.'];
|
||||
} elseif (!isset($teachersById[$idteacher])) {
|
||||
$insertFeedback = ['type' => 'error', 'text' => 'Seleziona l\'insegnante della classe.'];
|
||||
} else {
|
||||
$sql = "INSERT INTO service
|
||||
(servicename, wpcatalognumber, day, time, category, maxcapacity, colorclass, classduration)
|
||||
(servicename, wpcatalognumber, day, time, category, maxcapacity, colorclass, classduration, idteacher)
|
||||
VALUES
|
||||
(:servicename, :wpcatalog, :day, :time, :category, :maxcapacity, :colorclass, :classduration)";
|
||||
(:servicename, :wpcatalog, :day, :time, :category, :maxcapacity, :colorclass, :classduration, :idteacher)";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$ok = $stmt->execute([
|
||||
':servicename' => $servicename,
|
||||
@@ -44,6 +94,7 @@ if (isset($_POST['classinsert']) && $_POST['classinsert'] === 'Y') {
|
||||
':maxcapacity' => $maxcapacity !== '' ? (int) $maxcapacity : null,
|
||||
':colorclass' => $colorclass,
|
||||
':classduration' => $classduration !== '' ? (float) $classduration : null,
|
||||
':idteacher' => $idteacher,
|
||||
]);
|
||||
|
||||
if ($ok) {
|
||||
@@ -83,6 +134,32 @@ $propRows = $pdo->query(
|
||||
foreach ($propRows as $pr) {
|
||||
$propagationsByService[$pr['idservice']][] = $pr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opzioni del select insegnanti.
|
||||
* Gli insegnanti non attivi compaiono solo se già assegnati a quella classe.
|
||||
*/
|
||||
function teacherOptions(array $teachers, int $selectedId): string
|
||||
{
|
||||
$html = '';
|
||||
$found = false;
|
||||
foreach ($teachers as $t) {
|
||||
$tid = (int) $t['idemployee'];
|
||||
$isSel = $tid === $selectedId;
|
||||
if ($t['active'] !== 'Y' && !$isSel) {
|
||||
continue;
|
||||
}
|
||||
if ($isSel) $found = true;
|
||||
$label = trim($t['nameemployee'] . ' ' . $t['surnameemployee'])
|
||||
. ($t['is_owner'] === 'Y' ? ' (titolare)' : '')
|
||||
. ($t['active'] !== 'Y' ? ' (non attivo)' : '');
|
||||
$html .= '<option value="' . $tid . '"' . ($isSel ? ' selected' : '') . '>' . e($label) . '</option>';
|
||||
}
|
||||
if (!$found) {
|
||||
$html = '<option value="" selected disabled>— Non assegnato —</option>' . $html;
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
@@ -197,6 +274,43 @@ foreach ($propRows as $pr) {
|
||||
.action-btns .btn {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.teacher-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
.teacher-mini {
|
||||
flex: 0 0 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.teacher-mini.none {
|
||||
background: #f3f4f6;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.teacher-select {
|
||||
padding: 5px 30px 5px 10px !important;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.teacher-select.unassigned {
|
||||
border-color: #f0b27a;
|
||||
background-color: #fff8f1;
|
||||
color: #b9770e;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -238,6 +352,12 @@ foreach ($propRows as $pr) {
|
||||
<?php if ($message === 'inserted') : ?>
|
||||
<div class="alert alert-success" role="alert">Classe inserita con successo.</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($message === 'teacherassigned') : ?>
|
||||
<div class="alert alert-success" role="alert">Insegnante assegnato con successo.</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($message === 'teachererror') : ?>
|
||||
<div class="alert alert-danger" role="alert">Impossibile assegnare l'insegnante. Riprova.</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($message === 'success') : ?>
|
||||
<div class="alert alert-success" role="alert">Propagazione avvenuta con successo.</div>
|
||||
<?php endif; ?>
|
||||
@@ -261,6 +381,11 @@ foreach ($propRows as $pr) {
|
||||
<?php echo e($insertFeedback['text']); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (empty($teachers)) : ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
Non ci sono insegnanti: <a href="insegnanti.php">aggiungine almeno uno</a> prima di creare una classe.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Form inserimento classe -->
|
||||
<div class="row">
|
||||
@@ -343,6 +468,14 @@ foreach ($propRows as $pr) {
|
||||
<input type="text" class="form-control" id="colorclass" name="colorclass" value="#1ebf73">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="mb-3">
|
||||
<label for="idteacher" class="form-label">Insegnante</label>
|
||||
<select name="idteacher" id="idteacher" class="form-select" required>
|
||||
<?php echo teacherOptions($teachers, $ownerId); ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-save">Inserisci classe</button>
|
||||
@@ -358,7 +491,7 @@ foreach ($propRows as $pr) {
|
||||
<div class="card admin-card">
|
||||
<div class="card-body">
|
||||
<div class="section-title">Classi esistenti</div>
|
||||
<p class="section-lead">Propaga una classe sul calendario, associala o modificane i dettagli.</p>
|
||||
<p class="section-lead">Propaga una classe sul calendario, associala, assegna l'insegnante o modificane i dettagli.</p>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table class-table align-middle mb-0">
|
||||
@@ -369,16 +502,20 @@ foreach ($propRows as $pr) {
|
||||
<th>Orario</th>
|
||||
<th>Durata</th>
|
||||
<th>Categoria</th>
|
||||
<th>Insegnante</th>
|
||||
<th class="text-end">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php if (empty($services)) : ?>
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted py-4">Nessuna classe presente.</td>
|
||||
<td colspan="7" class="text-center text-muted py-4">Nessuna classe presente.</td>
|
||||
</tr>
|
||||
<?php else : ?>
|
||||
<?php foreach ($services as $svc) : ?>
|
||||
<?php foreach ($services as $svc) :
|
||||
$svcTeacherId = (int) $svc['idteacher'];
|
||||
$svcTeacher = $teachersById[$svcTeacherId] ?? null;
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="color-dot" style="background-color: <?php echo e($svc['colorclass'] ?: '#ccc'); ?>;"></span>
|
||||
@@ -391,6 +528,25 @@ foreach ($propRows as $pr) {
|
||||
<?php echo e($svc['classduration']); ?>
|
||||
</td>
|
||||
<td><?php echo e($svc['namecategory']); ?></td>
|
||||
<td>
|
||||
<form method="post" class="teacher-cell">
|
||||
<input type="hidden" name="action" value="assignteacher">
|
||||
<input type="hidden" name="idservice" value="<?php echo (int) $svc['idservice']; ?>">
|
||||
<?php if ($svcTeacher && $svcTeacher['photo']) : ?>
|
||||
<img class="teacher-mini" src="assets/images/teachers/<?php echo e($svcTeacher['photo']); ?>" alt="">
|
||||
<?php elseif ($svcTeacher) :
|
||||
$tName = trim($svcTeacher['nameemployee'] . ' ' . $svcTeacher['surnameemployee']); ?>
|
||||
<span class="teacher-mini" style="background:<?php echo teacherColor($tName); ?>;"><?php echo e(teacherInitials($svcTeacher['nameemployee'], $svcTeacher['surnameemployee'])); ?></span>
|
||||
<?php else : ?>
|
||||
<span class="teacher-mini none"><i class="fas fa-user"></i></span>
|
||||
<?php endif; ?>
|
||||
<select name="idteacher" class="form-select teacher-select <?php echo $svcTeacher ? '' : 'unassigned'; ?>"
|
||||
data-current="<?php echo $svcTeacherId; ?>"
|
||||
data-classname="<?php echo e($svc['servicename'] . ' ' . $svc['day'] . ' ' . $svc['time']); ?>">
|
||||
<?php echo teacherOptions($teachers, $svcTeacherId); ?>
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
<td class="text-end action-btns">
|
||||
<button type="button" class="btn btn-primary btn-sm toggle-form">Propaga</button>
|
||||
<a href="associate-services.php?id=<?php echo (int) $svc['idservice']; ?>">
|
||||
@@ -407,7 +563,7 @@ foreach ($propRows as $pr) {
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="expanded-row">
|
||||
<td colspan="6">
|
||||
<td colspan="7">
|
||||
<div class="expanded-content">
|
||||
<form class="calendar-form" method="post" action="nextdateclass.php">
|
||||
<div class="row">
|
||||
@@ -514,6 +670,17 @@ foreach ($propRows as $pr) {
|
||||
$('#colorclass').val(col.toHexString());
|
||||
}
|
||||
});
|
||||
|
||||
// Cambio insegnante dalla tabella: conferma e salva
|
||||
$('.teacher-select').on('change', function() {
|
||||
var sel = $(this);
|
||||
var teacherName = sel.find('option:selected').text();
|
||||
if (confirm('Assegnare ' + teacherName + ' alla classe ' + sel.data('classname') + '?')) {
|
||||
sel.closest('form')[0].submit();
|
||||
} else {
|
||||
sel.val(String(sel.data('current')) === '0' ? '' : String(sel.data('current')));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Espansione riga propagazione
|
||||
|
||||
+58
-2
@@ -28,8 +28,16 @@ function avatarColor(string $seed): string
|
||||
return $palette[$idx];
|
||||
}
|
||||
|
||||
$optRow = $pdo->query("SELECT maxbeforetimecancell FROM `option` LIMIT 1")->fetch();
|
||||
$optRow = $pdo->query("SELECT maxbeforetimecancell, show_teacher FROM `option` LIMIT 1")->fetch();
|
||||
$timetocancel = $optRow ? (int) $optRow['maxbeforetimecancell'] : 24;
|
||||
$showTeacher = ($optRow['show_teacher'] ?? 'N') === 'Y';
|
||||
|
||||
$teachersById = [];
|
||||
if ($showTeacher) {
|
||||
foreach ($pdo->query("SELECT idemployee, nameemployee, surnameemployee, photo FROM employee")->fetchAll() as $t) {
|
||||
$teachersById[(int) $t['idemployee']] = $t;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET['prev_month'])) {
|
||||
$currentMonthStart = preg_replace('/[^0-9\-]/', '', $_GET['prev_month']) . '-01';
|
||||
@@ -44,7 +52,7 @@ if (strtotime($currentMonthStart) === false) {
|
||||
$currentMonthEnd = date("Y-m-t", strtotime($currentMonthStart));
|
||||
|
||||
$sqlSchedule = "SELECT serviceschedule.*, service.servicename, service.colorclass,
|
||||
service.maxcapacity, service.idservice
|
||||
service.maxcapacity, service.idservice, service.idteacher
|
||||
FROM serviceschedule
|
||||
LEFT JOIN service ON serviceschedule.idservice = service.idservice
|
||||
WHERE serviceschedule.dateschedule BETWEEN :start AND DATE_ADD(:end, INTERVAL 1 DAY)
|
||||
@@ -231,6 +239,38 @@ $italianMonths = [
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.class-teacher {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.class-teacher strong {
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.teacher-avatar-sm {
|
||||
flex: 0 0 26px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: #E1D0B8;
|
||||
color: #5a4630;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.teacher-avatar-sm.none {
|
||||
background: #fdf0e3;
|
||||
color: #e67e22;
|
||||
}
|
||||
|
||||
.class-meta {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
@@ -610,6 +650,22 @@ $italianMonths = [
|
||||
</h3>
|
||||
<p class="class-meta"><i class="far fa-clock me-1"></i><?php echo e($whenFmt); ?></p>
|
||||
<p class="class-meta"><i class="fas fa-location-dot me-1"></i>via Valassina 62/B Seregno - Sala Contesto Yoga</p>
|
||||
<?php if ($showTeacher) :
|
||||
$tch = $teachersById[(int) ($sch['idteacher'] ?? 0)] ?? null; ?>
|
||||
<p class="class-meta class-teacher">
|
||||
<?php if ($tch) : ?>
|
||||
<?php if (!empty($tch['photo'])) : ?>
|
||||
<img class="teacher-avatar-sm" src="assets/images/teachers/<?php echo e($tch['photo']); ?>" alt="">
|
||||
<?php else : ?>
|
||||
<span class="teacher-avatar-sm"><?php echo e(initials($tch['nameemployee'], $tch['surnameemployee'])); ?></span>
|
||||
<?php endif; ?>
|
||||
Insegnante: <strong><?php echo e(trim($tch['nameemployee'] . ' ' . $tch['surnameemployee'])); ?></strong>
|
||||
<?php else : ?>
|
||||
<span class="teacher-avatar-sm none"><i class="fas fa-user" style="font-size:10px;"></i></span>
|
||||
<span style="color:#e67e22;font-weight:600;">Insegnante non assegnato</span>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="class-toolbar">
|
||||
<button class="btn-soft btn-bookings" onclick="togglePanel(this)"><i class="fas fa-list-ul me-1"></i> Vedi prenotazioni</button>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
@@ -166,6 +166,12 @@
|
||||
<span class="menu-item">Opzioni Scuola</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="insegnanti.php" class="">
|
||||
<i class="bx bx-slider-alt icon nav-icon"></i>
|
||||
<span class="menu-item">Insegnanti</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Template (demo) -->
|
||||
<li class="menu-title" data-key="t-applications">Template</li>
|
||||
|
||||
@@ -0,0 +1,783 @@
|
||||
<?php ob_start();
|
||||
require_once('include/headscript.php');
|
||||
/**
|
||||
* YogiBook - Gestione insegnanti (CRUD)
|
||||
* Tabella: employee
|
||||
* - un solo titolare (is_owner = 'Y'), non eliminabile né disattivabile
|
||||
* - notifiche email attivabili/disattivabili
|
||||
* - foto opzionale (JPG/PNG/WEBP, max 3 MB) in assets/images/teachers/
|
||||
*/
|
||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||
|
||||
if (!function_exists('e')) {
|
||||
function e($value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
if (empty($_SESSION['csrf_teachers'])) {
|
||||
$_SESSION['csrf_teachers'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
$csrf = $_SESSION['csrf_teachers'];
|
||||
|
||||
define('PHOTO_DIR_REL', 'assets/images/teachers/');
|
||||
$photoDirAbs = __DIR__ . '/' . PHOTO_DIR_REL;
|
||||
|
||||
/* ================================================================ helper */
|
||||
|
||||
function flashRedirect(string $type, string $msg): void
|
||||
{
|
||||
$_SESSION['teachers_flash'] = [$type, $msg];
|
||||
header('Location: insegnanti.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
function teacherInitials(string $first, string $last): string
|
||||
{
|
||||
$ini = mb_strtoupper(mb_substr(trim($first), 0, 1) . mb_substr(trim($last), 0, 1));
|
||||
return $ini !== '' ? $ini : '?';
|
||||
}
|
||||
|
||||
function teacherColor(string $seed): string
|
||||
{
|
||||
$palette = ['#1ebf73', '#2980b9', '#8e44ad', '#e67e22', '#16a085', '#c0392b', '#2c3e50', '#b08d57'];
|
||||
return $palette[abs(crc32($seed)) % count($palette)];
|
||||
}
|
||||
|
||||
function deletePhotoFile(string $dir, ?string $name): void
|
||||
{
|
||||
if ($name && preg_match('/^teacher_[a-f0-9]{16}\.(jpg|png|webp)$/', $name) && is_file($dir . $name)) {
|
||||
@unlink($dir . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ritorna il nome file salvato, null se nessun file caricato. Lancia RuntimeException se non valido. */
|
||||
function handlePhotoUpload(array $file, string $dir): ?string
|
||||
{
|
||||
if (($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
|
||||
return null;
|
||||
}
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
throw new RuntimeException('Errore nel caricamento della foto.');
|
||||
}
|
||||
if ($file['size'] > 3 * 1024 * 1024) {
|
||||
throw new RuntimeException('La foto supera i 3 MB.');
|
||||
}
|
||||
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
|
||||
$ext = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'][$mime] ?? null;
|
||||
if (!$ext || @getimagesize($file['tmp_name']) === false) {
|
||||
throw new RuntimeException('Formato foto non valido: usa JPG, PNG o WEBP.');
|
||||
}
|
||||
if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
|
||||
throw new RuntimeException('La cartella delle foto non è scrivibile.');
|
||||
}
|
||||
$name = 'teacher_' . bin2hex(random_bytes(8)) . '.' . $ext;
|
||||
if (!move_uploaded_file($file['tmp_name'], $dir . $name)) {
|
||||
throw new RuntimeException('Impossibile salvare la foto.');
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
function findTeacher(PDO $pdo, int $id): ?array
|
||||
{
|
||||
$st = $pdo->prepare("SELECT * FROM employee WHERE idemployee = :id");
|
||||
$st->execute([':id' => $id]);
|
||||
$row = $st->fetch();
|
||||
return $row ?: null;
|
||||
}
|
||||
|
||||
/* ================================================================ azioni POST */
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (!hash_equals($csrf, (string) ($_POST['csrf'] ?? ''))) {
|
||||
flashRedirect('danger', 'Sessione scaduta, riprova.');
|
||||
}
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
$id = (int) ($_POST['id'] ?? 0);
|
||||
|
||||
try {
|
||||
/* ---------- crea / modifica */
|
||||
if ($action === 'save') {
|
||||
$name = trim((string) ($_POST['nameemployee'] ?? ''));
|
||||
$surname = trim((string) ($_POST['surnameemployee'] ?? ''));
|
||||
$email = trim((string) ($_POST['email'] ?? ''));
|
||||
$phone = trim((string) ($_POST['phone'] ?? ''));
|
||||
$notify = isset($_POST['notifications']) ? 'Y' : 'N';
|
||||
$active = isset($_POST['active']) ? 'Y' : 'N';
|
||||
|
||||
if ($name === '' || $surname === '') {
|
||||
flashRedirect('danger', 'Nome e cognome sono obbligatori.');
|
||||
}
|
||||
if (mb_strlen($name) > 100 || mb_strlen($surname) > 100) {
|
||||
flashRedirect('danger', 'Nome o cognome troppo lunghi.');
|
||||
}
|
||||
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
flashRedirect('danger', 'Indirizzo email non valido.');
|
||||
}
|
||||
if ($phone !== '' && !preg_match('/^[0-9 +().\/-]{5,40}$/', $phone)) {
|
||||
flashRedirect('danger', 'Numero di telefono non valido.');
|
||||
}
|
||||
if ($email !== '') {
|
||||
$dup = $pdo->prepare("SELECT COUNT(*) AS c FROM employee WHERE email = :email AND idemployee <> :id");
|
||||
$dup->execute([':email' => $email, ':id' => $id]);
|
||||
if ((int) $dup->fetch()['c'] > 0) {
|
||||
flashRedirect('danger', 'Questa email è già usata da un altro insegnante.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($id > 0) {
|
||||
$current = findTeacher($pdo, $id);
|
||||
if (!$current) {
|
||||
flashRedirect('danger', 'Insegnante non trovato.');
|
||||
}
|
||||
if ($current['is_owner'] === 'Y' && $active === 'N') {
|
||||
flashRedirect('danger', 'Il titolare non può essere disattivato.');
|
||||
}
|
||||
|
||||
$photo = $current['photo'];
|
||||
$newPhoto = handlePhotoUpload($_FILES['photo'] ?? [], $photoDirAbs);
|
||||
if ($newPhoto !== null) {
|
||||
deletePhotoFile($photoDirAbs, $photo);
|
||||
$photo = $newPhoto;
|
||||
} elseif (isset($_POST['remove_photo'])) {
|
||||
deletePhotoFile($photoDirAbs, $photo);
|
||||
$photo = null;
|
||||
}
|
||||
|
||||
$st = $pdo->prepare("UPDATE employee SET nameemployee = :n, surnameemployee = :s, email = :e,
|
||||
phone = :p, photo = :ph, notifications = :nt, active = :a
|
||||
WHERE idemployee = :id");
|
||||
$st->execute([
|
||||
':n' => $name,
|
||||
':s' => $surname,
|
||||
':e' => $email ?: null,
|
||||
':p' => $phone ?: null,
|
||||
':ph' => $photo,
|
||||
':nt' => $notify,
|
||||
':a' => $active,
|
||||
':id' => $id,
|
||||
]);
|
||||
flashRedirect('success', 'Insegnante aggiornato.');
|
||||
}
|
||||
|
||||
// nuovo
|
||||
$photo = handlePhotoUpload($_FILES['photo'] ?? [], $photoDirAbs);
|
||||
$hasOwner = (int) $pdo->query("SELECT COUNT(*) AS c FROM employee WHERE is_owner = 'Y'")->fetch()['c'] > 0;
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO employee
|
||||
(nameemployee, surnameemployee, email, phone, photo, is_owner, notifications, active, idlocation)
|
||||
VALUES (:n, :s, :e, :p, :ph, :o, :nt, :a, 0)");
|
||||
$st->execute([
|
||||
':n' => $name,
|
||||
':s' => $surname,
|
||||
':e' => $email ?: null,
|
||||
':p' => $phone ?: null,
|
||||
':ph' => $photo,
|
||||
':o' => $hasOwner ? 'N' : 'Y',
|
||||
':nt' => $notify,
|
||||
':a' => $hasOwner ? $active : 'Y',
|
||||
]);
|
||||
flashRedirect('success', 'Insegnante aggiunto' . ($hasOwner ? '.' : ' e impostato come titolare.'));
|
||||
}
|
||||
|
||||
/* ---------- imposta titolare */
|
||||
if ($action === 'set_owner' && $id > 0) {
|
||||
if (!findTeacher($pdo, $id)) {
|
||||
flashRedirect('danger', 'Insegnante non trovato.');
|
||||
}
|
||||
$pdo->beginTransaction();
|
||||
$pdo->exec("UPDATE employee SET is_owner = 'N' WHERE is_owner = 'Y'");
|
||||
$st = $pdo->prepare("UPDATE employee SET is_owner = 'Y', active = 'Y' WHERE idemployee = :id");
|
||||
$st->execute([':id' => $id]);
|
||||
$pdo->commit();
|
||||
flashRedirect('success', 'Titolare aggiornato.');
|
||||
}
|
||||
|
||||
/* ---------- notifiche on/off */
|
||||
if ($action === 'toggle_notifications' && $id > 0) {
|
||||
$st = $pdo->prepare("UPDATE employee SET notifications = IF(notifications = 'Y', 'N', 'Y') WHERE idemployee = :id");
|
||||
$st->execute([':id' => $id]);
|
||||
flashRedirect('success', 'Notifiche aggiornate.');
|
||||
}
|
||||
|
||||
/* ---------- elimina */
|
||||
if ($action === 'delete' && $id > 0) {
|
||||
$t = findTeacher($pdo, $id);
|
||||
if (!$t) {
|
||||
flashRedirect('danger', 'Insegnante non trovato.');
|
||||
}
|
||||
if ($t['is_owner'] === 'Y') {
|
||||
flashRedirect('danger', 'Il titolare non può essere eliminato. Imposta prima un altro titolare.');
|
||||
}
|
||||
$cnt = $pdo->prepare("SELECT COUNT(*) AS c FROM service WHERE idteacher = :id");
|
||||
$cnt->execute([':id' => $id]);
|
||||
$nServices = (int) $cnt->fetch()['c'];
|
||||
if ($nServices > 0) {
|
||||
flashRedirect('danger', 'Impossibile eliminare: ha ' . $nServices . ' classi assegnate. Riassegnale oppure disattiva l\'insegnante.');
|
||||
}
|
||||
$pdo->beginTransaction();
|
||||
$pdo->prepare("DELETE FROM dayoff WHERE idteacher = :id")->execute([':id' => $id]);
|
||||
$pdo->prepare("DELETE FROM employee WHERE idemployee = :id")->execute([':id' => $id]);
|
||||
$pdo->commit();
|
||||
deletePhotoFile($photoDirAbs, $t['photo']);
|
||||
flashRedirect('success', 'Insegnante eliminato.');
|
||||
}
|
||||
|
||||
flashRedirect('danger', 'Azione non valida.');
|
||||
} catch (RuntimeException $ex) {
|
||||
flashRedirect('danger', $ex->getMessage());
|
||||
} catch (Throwable $ex) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
error_log('insegnanti.php: ' . $ex->getMessage());
|
||||
flashRedirect('danger', 'Errore durante il salvataggio. Riprova.');
|
||||
}
|
||||
}
|
||||
|
||||
/* ================================================================ lettura */
|
||||
|
||||
$flash = $_SESSION['teachers_flash'] ?? null;
|
||||
unset($_SESSION['teachers_flash']);
|
||||
|
||||
$teachers = $pdo->query("SELECT e.*,
|
||||
(SELECT COUNT(*) FROM service s WHERE s.idteacher = e.idemployee) AS nservices
|
||||
FROM employee e
|
||||
ORDER BY e.is_owner DESC, e.active DESC, e.surnameemployee, e.nameemployee")->fetchAll();
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>YogiBook - Insegnanti</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">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
|
||||
<style>
|
||||
.teachers-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.teachers-head h5 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.teacher-card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 3px 16px rgba(0, 0, 0, 0.06);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
height: calc(100% - 20px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 5px solid #eef0f3;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.teacher-card:hover {
|
||||
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.teacher-card.owner {
|
||||
border-top-color: #E1D0B8;
|
||||
}
|
||||
|
||||
.teacher-card.inactive {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.teacher-top {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.teacher-avatar {
|
||||
flex: 0 0 64px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
border: 3px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.teacher-name {
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.t-badge {
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
padding: 3px 10px;
|
||||
border-radius: 20px;
|
||||
margin-top: 5px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.t-badge.owner {
|
||||
background: #E1D0B8;
|
||||
color: #5a4630;
|
||||
}
|
||||
|
||||
.t-badge.inactive {
|
||||
background: #eef0f3;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.teacher-info {
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
margin-bottom: 4px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.teacher-info i {
|
||||
width: 18px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.teacher-info a {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.teacher-info .missing {
|
||||
color: #c0c4cc;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.teacher-foot {
|
||||
margin-top: auto;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #f0f2f5;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.notify-form .form-check-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #4b5563;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notify-form .form-check-input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notify-form .form-check-input:checked {
|
||||
background-color: #1ebf73;
|
||||
border-color: #1ebf73;
|
||||
}
|
||||
|
||||
.notify-warning {
|
||||
font-size: 11px;
|
||||
color: #e67e22;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.t-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.t-btn {
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 6px 11px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: filter 0.15s;
|
||||
}
|
||||
|
||||
.t-btn:hover {
|
||||
filter: brightness(0.93);
|
||||
}
|
||||
|
||||
.t-btn.edit {
|
||||
background: #eef2f7;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.t-btn.owner {
|
||||
background: #f5eee4;
|
||||
color: #8a6a3f;
|
||||
}
|
||||
|
||||
.t-btn.del {
|
||||
background: #fdecea;
|
||||
color: #c0392b;
|
||||
}
|
||||
|
||||
.photo-preview {
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: #f3f4f6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #9ca3af;
|
||||
font-size: 28px;
|
||||
border: 3px solid #eef0f3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.photo-preview img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.empty-teachers {
|
||||
text-align: center;
|
||||
color: #9ca3af;
|
||||
padding: 50px 10px;
|
||||
}
|
||||
</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">Insegnanti</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">
|
||||
|
||||
<?php if ($flash) : ?>
|
||||
<div class="alert alert-<?php echo e($flash[0]); ?> alert-dismissible fade show" role="alert">
|
||||
<?php echo e($flash[1]); ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Chiudi"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="teachers-head">
|
||||
<div>
|
||||
<h5>Insegnanti della scuola</h5>
|
||||
<small class="text-muted"><?php echo count($teachers); ?> insegnant<?php echo count($teachers) === 1 ? 'e' : 'i'; ?></small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" onclick="openTeacherModal(null)">
|
||||
<i class="fas fa-user-plus me-1"></i> Nuovo insegnante
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (empty($teachers)) : ?>
|
||||
<div class="empty-teachers">
|
||||
<i class="fas fa-chalkboard-teacher fa-2x mb-2"></i>
|
||||
<p>Nessun insegnante. Aggiungi il primo: diventerà automaticamente il titolare.</p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="row">
|
||||
<?php foreach ($teachers as $t) :
|
||||
$tid = (int) $t['idemployee'];
|
||||
$isOwner = $t['is_owner'] === 'Y';
|
||||
$isActive = $t['active'] === 'Y';
|
||||
$fullName = trim($t['nameemployee'] . ' ' . $t['surnameemployee']);
|
||||
$photoUrl = $t['photo'] ? PHOTO_DIR_REL . $t['photo'] : '';
|
||||
$payload = [
|
||||
'id' => $tid,
|
||||
'nameemployee' => $t['nameemployee'],
|
||||
'surnameemployee' => $t['surnameemployee'],
|
||||
'email' => $t['email'],
|
||||
'phone' => $t['phone'],
|
||||
'photo' => $photoUrl,
|
||||
'notifications' => $t['notifications'] === 'Y',
|
||||
'active' => $isActive,
|
||||
'is_owner' => $isOwner,
|
||||
];
|
||||
?>
|
||||
<div class="col-md-6 col-xl-4">
|
||||
<div class="teacher-card <?php echo $isOwner ? 'owner' : ''; ?> <?php echo $isActive ? '' : 'inactive'; ?>">
|
||||
<div class="teacher-top">
|
||||
<?php if ($photoUrl) : ?>
|
||||
<img class="teacher-avatar" src="<?php echo e($photoUrl); ?>" alt="<?php echo e($fullName); ?>">
|
||||
<?php else : ?>
|
||||
<div class="teacher-avatar" style="background:<?php echo teacherColor($fullName); ?>;"><?php echo e(teacherInitials($t['nameemployee'], $t['surnameemployee'])); ?></div>
|
||||
<?php endif; ?>
|
||||
<div>
|
||||
<div class="teacher-name"><?php echo e($fullName); ?></div>
|
||||
<?php if ($isOwner) : ?><span class="t-badge owner"><i class="fas fa-crown me-1"></i>Titolare</span><?php endif; ?>
|
||||
<?php if (!$isActive) : ?><span class="t-badge inactive">Non attivo</span><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="teacher-info"><i class="far fa-envelope"></i>
|
||||
<?php echo $t['email'] ? '<a href="mailto:' . e($t['email']) . '">' . e($t['email']) . '</a>' : '<span class="missing">email non inserita</span>'; ?>
|
||||
</div>
|
||||
<div class="teacher-info"><i class="fas fa-phone"></i>
|
||||
<?php echo $t['phone'] ? '<a href="tel:' . e(preg_replace('/[^0-9+]/', '', $t['phone'])) . '">' . e($t['phone']) . '</a>' : '<span class="missing">telefono non inserito</span>'; ?>
|
||||
</div>
|
||||
<div class="teacher-info"><i class="fas fa-layer-group"></i>
|
||||
<?php echo (int) $t['nservices']; ?> class<?php echo (int) $t['nservices'] === 1 ? 'e' : 'i'; ?> assegnat<?php echo (int) $t['nservices'] === 1 ? 'a' : 'e'; ?>
|
||||
</div>
|
||||
|
||||
<div class="teacher-foot">
|
||||
<form method="post" class="notify-form">
|
||||
<input type="hidden" name="csrf" value="<?php echo e($csrf); ?>">
|
||||
<input type="hidden" name="action" value="toggle_notifications">
|
||||
<input type="hidden" name="id" value="<?php echo $tid; ?>">
|
||||
<div class="form-check form-switch mb-0">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="notify<?php echo $tid; ?>"
|
||||
<?php echo $t['notifications'] === 'Y' ? 'checked' : ''; ?> onchange="this.form.submit()">
|
||||
<label class="form-check-label" for="notify<?php echo $tid; ?>">Notifiche email</label>
|
||||
</div>
|
||||
<?php if ($t['notifications'] === 'Y' && !$t['email']) : ?>
|
||||
<div class="notify-warning"><i class="fas fa-exclamation-triangle"></i> manca l'email</div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="t-actions">
|
||||
<button type="button" class="t-btn edit" title="Modifica"
|
||||
data-teacher="<?php echo e(json_encode($payload)); ?>"
|
||||
onclick="openTeacherModal(JSON.parse(this.dataset.teacher))">
|
||||
<i class="fas fa-pen"></i>
|
||||
</button>
|
||||
<?php if (!$isOwner) : ?>
|
||||
<form method="post" class="confirm-form"
|
||||
data-title="Impostare come titolare?"
|
||||
data-text="<?php echo e($fullName); ?> diventerà il titolare della scuola al posto dell'attuale.">
|
||||
<input type="hidden" name="csrf" value="<?php echo e($csrf); ?>">
|
||||
<input type="hidden" name="action" value="set_owner">
|
||||
<input type="hidden" name="id" value="<?php echo $tid; ?>">
|
||||
<button type="submit" class="t-btn owner" title="Imposta come titolare"><i class="fas fa-crown"></i></button>
|
||||
</form>
|
||||
<form method="post" class="confirm-form" data-danger="1"
|
||||
data-title="Eliminare <?php echo e($fullName); ?>?"
|
||||
data-text="L'operazione non è reversibile.">
|
||||
<input type="hidden" name="csrf" value="<?php echo e($csrf); ?>">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="id" value="<?php echo $tid; ?>">
|
||||
<button type="submit" class="t-btn del" title="Elimina"><i class="fas fa-trash"></i></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ MODALE CREA / MODIFICA -->
|
||||
<div class="modal fade" id="teacherModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<form class="modal-content" method="post" enctype="multipart/form-data" id="teacherForm">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="teacherModalTitle">Nuovo insegnante</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" name="csrf" value="<?php echo e($csrf); ?>">
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="id" id="t_id" value="0">
|
||||
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<div class="photo-preview" id="t_preview"><i class="fas fa-user"></i></div>
|
||||
<div class="flex-grow-1">
|
||||
<label class="form-label mb-1">Foto <small class="text-muted">(facoltativa, JPG/PNG/WEBP, max 3 MB)</small></label>
|
||||
<input type="file" name="photo" id="t_photo" class="form-control form-control-sm" accept="image/jpeg,image/png,image/webp">
|
||||
<div class="form-check mt-2" id="t_remove_wrap" style="display:none;">
|
||||
<input class="form-check-input" type="checkbox" name="remove_photo" id="t_remove">
|
||||
<label class="form-check-label" for="t_remove">Rimuovi foto attuale</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<label class="form-label mb-1">Nome *</label>
|
||||
<input type="text" name="nameemployee" id="t_name" class="form-control" maxlength="100" required>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="form-label mb-1">Cognome *</label>
|
||||
<input type="text" name="surnameemployee" id="t_surname" class="form-control" maxlength="100" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Email</label>
|
||||
<input type="email" name="email" id="t_email" class="form-control" maxlength="150">
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Telefono</label>
|
||||
<input type="text" name="phone" id="t_phone" class="form-control" maxlength="40">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="notifications" id="t_notify" checked>
|
||||
<label class="form-check-label" for="t_notify">Riceve notifiche email</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="active" id="t_active" checked>
|
||||
<label class="form-check-label" for="t_active">Attivo</label>
|
||||
</div>
|
||||
<div class="text-muted" id="t_owner_note" style="font-size:12px;display:none;">
|
||||
<i class="fas fa-crown me-1" style="color:#b08d57;"></i> È il titolare: non può essere disattivato.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Annulla</button>
|
||||
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i> Salva</button>
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
var teacherModal = null;
|
||||
|
||||
function setPreview(url) {
|
||||
var box = document.getElementById('t_preview');
|
||||
box.innerHTML = url ? '<img src="' + url + '" alt="">' : '<i class="fas fa-user"></i>';
|
||||
}
|
||||
|
||||
function openTeacherModal(t) {
|
||||
var isEdit = !!t;
|
||||
document.getElementById('teacherModalTitle').textContent = isEdit ? 'Modifica insegnante' : 'Nuovo insegnante';
|
||||
document.getElementById('t_id').value = isEdit ? t.id : 0;
|
||||
document.getElementById('t_name').value = isEdit ? (t.nameemployee || '') : '';
|
||||
document.getElementById('t_surname').value = isEdit ? (t.surnameemployee || '') : '';
|
||||
document.getElementById('t_email').value = isEdit ? (t.email || '') : '';
|
||||
document.getElementById('t_phone').value = isEdit ? (t.phone || '') : '';
|
||||
document.getElementById('t_notify').checked = isEdit ? t.notifications : true;
|
||||
|
||||
var active = document.getElementById('t_active');
|
||||
active.checked = isEdit ? t.active : true;
|
||||
active.disabled = isEdit && t.is_owner;
|
||||
document.getElementById('t_owner_note').style.display = (isEdit && t.is_owner) ? 'block' : 'none';
|
||||
|
||||
document.getElementById('t_photo').value = '';
|
||||
document.getElementById('t_remove').checked = false;
|
||||
document.getElementById('t_remove_wrap').style.display = (isEdit && t.photo) ? 'block' : 'none';
|
||||
setPreview(isEdit ? t.photo : '');
|
||||
|
||||
if (!teacherModal) teacherModal = new bootstrap.Modal(document.getElementById('teacherModal'));
|
||||
teacherModal.show();
|
||||
}
|
||||
|
||||
// il titolare ha "Attivo" disabilitato: lo riabilitiamo al submit così il valore viene inviato
|
||||
document.getElementById('teacherForm').addEventListener('submit', function() {
|
||||
document.getElementById('t_active').disabled = false;
|
||||
});
|
||||
|
||||
// anteprima foto + controllo dimensione
|
||||
document.getElementById('t_photo').addEventListener('change', function() {
|
||||
var f = this.files[0];
|
||||
if (!f) return;
|
||||
if (f.size > 3 * 1024 * 1024) {
|
||||
Swal.fire('Foto troppo grande', 'La foto deve essere al massimo di 3 MB.', 'warning');
|
||||
this.value = '';
|
||||
return;
|
||||
}
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
setPreview(ev.target.result);
|
||||
};
|
||||
reader.readAsDataURL(f);
|
||||
document.getElementById('t_remove').checked = false;
|
||||
});
|
||||
|
||||
// conferma per titolare / elimina
|
||||
document.querySelectorAll('.confirm-form').forEach(function(form) {
|
||||
form.addEventListener('submit', function(ev) {
|
||||
if (form.dataset.confirmed === '1') return;
|
||||
ev.preventDefault();
|
||||
var danger = form.dataset.danger === '1';
|
||||
Swal.fire({
|
||||
title: form.dataset.title,
|
||||
text: form.dataset.text,
|
||||
icon: danger ? 'warning' : 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: danger ? '#e74c3c' : '#b08d57',
|
||||
cancelButtonColor: '#6b7280',
|
||||
confirmButtonText: danger ? 'Sì, elimina' : 'Sì, conferma',
|
||||
cancelButtonText: 'Annulla'
|
||||
}).then(function(r) {
|
||||
if (r.isConfirmed) {
|
||||
form.dataset.confirmed = '1';
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+23
-23
@@ -1,33 +1,33 @@
|
||||
<?php
|
||||
require_once('include/headscript.php');
|
||||
|
||||
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
header("Location: options.php");
|
||||
exit();
|
||||
header('Location: options.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
if ($conn->connect_error) {
|
||||
header("Location: options.php?message=error");
|
||||
exit();
|
||||
}
|
||||
$waiting = isset($_POST['waitinglist_enabled']) ? 'Y' : 'N';
|
||||
$showTeacher = isset($_POST['show_teacher']) ? 'Y' : 'N';
|
||||
$maxCancel = max(0, (int) ($_POST['maxbeforetimecancell'] ?? 0));
|
||||
|
||||
// Checkbox non spuntata = nessun invio -> default 'N'
|
||||
$waitingEnabled = (isset($_POST['waitinglist_enabled']) && $_POST['waitinglist_enabled'] === 'Y') ? 'Y' : 'N';
|
||||
$maxCancel = isset($_POST['maxbeforetimecancell']) ? (string) (int) $_POST['maxbeforetimecancell'] : '0';
|
||||
try {
|
||||
$exists = (int) $pdo->query("SELECT COUNT(*) AS c FROM `option`")->fetch()['c'] > 0;
|
||||
|
||||
// Aggiorna l'unico record di option (idption = 1)
|
||||
$stmt = $conn->prepare("UPDATE `option` SET waitinglist_enabled = ?, maxbeforetimecancell = ? WHERE idption = 1");
|
||||
$stmt->bind_param("ss", $waitingEnabled, $maxCancel);
|
||||
if ($exists) {
|
||||
$st = $pdo->prepare("UPDATE `option`
|
||||
SET waitinglist_enabled = :w, show_teacher = :s, maxbeforetimecancell = :m
|
||||
LIMIT 1");
|
||||
} else {
|
||||
$st = $pdo->prepare("INSERT INTO `option` (waitinglist_enabled, show_teacher, maxbeforetimecancell)
|
||||
VALUES (:w, :s, :m)");
|
||||
}
|
||||
$st->execute([':w' => $waiting, ':s' => $showTeacher, ':m' => (string) $maxCancel]);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
header("Location: optionsschool.php?message=success");
|
||||
exit();
|
||||
} else {
|
||||
$stmt->close();
|
||||
$conn->close();
|
||||
header("Location: optionsschool.php?message=error");
|
||||
exit();
|
||||
header('Location: optionsschool.php?message=success');
|
||||
} catch (Throwable $e) {
|
||||
error_log('options_save: ' . $e->getMessage());
|
||||
header('Location: options.php?message=error');
|
||||
}
|
||||
exit;
|
||||
|
||||
@@ -19,6 +19,7 @@ $conn->close();
|
||||
|
||||
$waitingEnabled = ($opt['waitinglist_enabled'] ?? 'N') === 'Y';
|
||||
$maxCancel = $opt['maxbeforetimecancell'] ?? '0';
|
||||
$showTeacher = ($opt['show_teacher'] ?? 'N') === 'Y';
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
@@ -155,6 +156,15 @@ $maxCancel = $opt['maxbeforetimecancell'] ?? '0';
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="waitinglist_enabled" name="waitinglist_enabled" value="Y" <?php echo $waitingEnabled ? 'checked' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-row">
|
||||
<div>
|
||||
<div class="option-label">Mostra insegnante nelle classi</div>
|
||||
<div class="option-help">Se attiva, nelle card delle classi viene indicato il nome (e la foto) dell'insegnante.</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="show_teacher" name="show_teacher" value="Y" <?php echo $showTeacher ? 'checked' : ''; ?>>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="option-row">
|
||||
<div>
|
||||
|
||||
+60
-2
@@ -59,7 +59,7 @@ $stmtBooked->execute([
|
||||
$bookedRows = $stmtBooked->fetchAll();
|
||||
|
||||
// Lezioni in attesa di conferma (pending) dell'utente, nel mese visualizzato
|
||||
$sqlPending = "SELECT bookingclass.*, service.servicename, service.colorclass, serviceschedule.dateschedule
|
||||
$sqlPending = "SELECT bookingclass.*, service.servicename, service.colorclass, service.idteacher, serviceschedule.dateschedule
|
||||
FROM bookingclass
|
||||
LEFT JOIN service ON bookingclass.idservice = service.idservice
|
||||
LEFT JOIN serviceschedule ON bookingclass.idserviceschedule = serviceschedule.idserviceschedule
|
||||
@@ -76,7 +76,7 @@ $stmtPending->execute([
|
||||
$pendingRows = $stmtPending->fetchAll();
|
||||
|
||||
// Lezioni in lista d'attesa (waiting) dell'utente, nel mese visualizzato
|
||||
$sqlWaiting = "SELECT bookingclass.*, service.servicename, service.colorclass, service.maxcapacity, serviceschedule.dateschedule
|
||||
$sqlWaiting = "SELECT bookingclass.*, service.servicename, service.colorclass, service.maxcapacity, service.idteacher, serviceschedule.dateschedule
|
||||
FROM bookingclass
|
||||
LEFT JOIN service ON bookingclass.idservice = service.idservice
|
||||
LEFT JOIN serviceschedule ON bookingclass.idserviceschedule = serviceschedule.idserviceschedule
|
||||
@@ -157,6 +157,18 @@ $pending = (int) ($rowCounts['pending'] ?? 0);
|
||||
|
||||
$toprogram = $totalTickets - $passedRecords - $futureRecords - $pending - $lost;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* 4b) Insegnanti nelle card (solo se attivo nelle Opzioni)
|
||||
* ---------------------------------------------------------------------- */
|
||||
$optShow = $pdo->query("SELECT show_teacher FROM `option` LIMIT 1")->fetch();
|
||||
$showTeacher = ($optShow['show_teacher'] ?? 'N') === 'Y';
|
||||
|
||||
$teachersById = [];
|
||||
if ($showTeacher) {
|
||||
foreach ($pdo->query("SELECT idemployee, nameemployee, surnameemployee, photo FROM employee")->fetchAll() as $t) {
|
||||
$teachersById[(int) $t['idemployee']] = $t;
|
||||
}
|
||||
}
|
||||
/* -------------------------------------------------------------------------
|
||||
* 5) Pre-caricamento limiti di riprogrammazione per gli ordini coinvolti
|
||||
* (una sola query invece di una per ogni card, dentro il loop)
|
||||
@@ -191,6 +203,22 @@ function e($value): string
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function teacherLine(array $row, bool $show, array $teachersById): string
|
||||
{
|
||||
if (!$show) return '';
|
||||
$t = $teachersById[(int) ($row['idteacher'] ?? 0)] ?? null;
|
||||
if (!$t) return '';
|
||||
|
||||
$name = trim($t['nameemployee'] . ' ' . $t['surnameemployee']);
|
||||
if (!empty($t['photo'])) {
|
||||
$avatar = '<img class="teacher-avatar-sm" src="assets/images/teachers/' . e($t['photo']) . '" alt="">';
|
||||
} else {
|
||||
$ini = mb_strtoupper(mb_substr($t['nameemployee'], 0, 1) . mb_substr($t['surnameemployee'], 0, 1));
|
||||
$avatar = '<span class="teacher-avatar-sm">' . e($ini) . '</span>';
|
||||
}
|
||||
return '<p class="custom-paragraph teacher-line">' . $avatar . 'Insegnante: <strong>' . e($name) . '</strong></p>';
|
||||
}
|
||||
|
||||
$italianMonths = [
|
||||
"January" => "Gennaio",
|
||||
"February" => "Febbraio",
|
||||
@@ -305,6 +333,33 @@ $italianMonths = [
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.teacher-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 4px 0 6px;
|
||||
}
|
||||
|
||||
.teacher-line strong {
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.teacher-avatar-sm {
|
||||
flex: 0 0 26px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
background: #E1D0B8;
|
||||
color: #5a4630;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.custom-paragraph {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
@@ -681,6 +736,7 @@ $italianMonths = [
|
||||
<span style="display:inline-block;background:#fff4d6;color:#8a6d00;font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;margin-left:6px;vertical-align:middle;">In attesa</span>
|
||||
</h2>
|
||||
<p class="custom-paragraph">Quando: <?php echo e($pWhen); ?></p>
|
||||
<?php echo teacherLine($prow, $showTeacher, $teachersById); ?>
|
||||
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -719,6 +775,7 @@ $italianMonths = [
|
||||
<span style="display:inline-block;background:#eee;color:#555;font-size:11px;font-weight:700;padding:2px 9px;border-radius:20px;margin-left:6px;vertical-align:middle;">In coda</span>
|
||||
</h2>
|
||||
<p class="custom-paragraph">Quando: <?php echo e($wWhen); ?></p>
|
||||
<?php echo teacherLine($wrow, $showTeacher, $teachersById); ?>
|
||||
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
|
||||
<?php if ($wPos > 0) : ?>
|
||||
<p class="custom-paragraph" style="font-weight:700;color:#8a6d00;">
|
||||
@@ -801,6 +858,7 @@ $italianMonths = [
|
||||
<div class="custom-event-details" style="border-left-color:<?php echo e($colorclass); ?>">
|
||||
<h2 class="custom-heading"><?php echo e($servicename); ?></h2>
|
||||
<p class="custom-paragraph">Quando: <?php echo e($newDateFormat); ?></p>
|
||||
<?php echo teacherLine($row, $showTeacher, $teachersById); ?>
|
||||
<p class="custom-paragraph">Luogo: via Valassina 62/B Seregno - Sala Contesto Yoga</p>
|
||||
<div class="custom-actions">
|
||||
<button class="custom-action-button" onclick="addToCalendar(this)" data-eventname="<?php echo e($servicename); ?>" data-eventdate="<?php echo e($newDateFormat); ?>">
|
||||
|
||||
Reference in New Issue
Block a user