added teacher and option teacher in school
This commit is contained in:
+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