Compare commits
19 Commits
6124e7e8fa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ad68941b0d | |||
| e4b472f0c1 | |||
| 2734679938 | |||
| aba16ce7f3 | |||
| 4aad1e35ef | |||
| 0845ad62a9 | |||
| b55e9f483f | |||
| 73589b3b04 | |||
| 1551e6aca7 | |||
| 7f7dff32d9 | |||
| dda63d9711 | |||
| 7c574649a0 | |||
| 3fb512e713 | |||
| c62d93c3db | |||
| e76fe4dfd6 | |||
| f911b82716 | |||
| 39fb15c649 | |||
| f31a496b8c | |||
| 2254ca90ff |
@@ -23,12 +23,12 @@ REDIS_PASSWORD=null
|
|||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
MAIL_MAILER=mail
|
MAIL_MAILER=mail
|
||||||
MAIL_FROM_NAME=YogaSoul
|
MAIL_FROM_NAME=YogiBoook
|
||||||
MAIL_FROM_ADDRESS=info@yogasoul.it
|
MAIL_FROM_ADDRESS=info@yogiboook.com
|
||||||
MAIL_HOST=mail.yogasoul.it
|
MAIL_HOST=mail.yogiboook.com
|
||||||
MAIL_PORT=465
|
MAIL_PORT=465
|
||||||
MAIL_USERNAME=info@yogasoul.it
|
MAIL_USERNAME=info@yogiboook.com
|
||||||
MAIL_PASSWORD=!Testolina88
|
MAIL_PASSWORD=!NuovaZelanda2020
|
||||||
MAIL_ENCRYPTION=ssl
|
MAIL_ENCRYPTION=ssl
|
||||||
|
|
||||||
PUSHER_APP_ID=
|
PUSHER_APP_ID=
|
||||||
|
|||||||
@@ -20,4 +20,6 @@ yarn-error.log
|
|||||||
/public/build
|
/public/build
|
||||||
.env.backup
|
.env.backup
|
||||||
.env.production
|
.env.production
|
||||||
auth.json
|
auth.json
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|||||||
|
After Width: | Height: | Size: 425 B |
|
After Width: | Height: | Size: 430 B |
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
// add_teacher.php
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once 'class/mailer.php';
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) die("Accesso negato.");
|
||||||
|
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||||
|
|
||||||
|
// Recupera scuola per email mittente
|
||||||
|
$stmt = $pdo->prepare("SELECT name, email FROM schools WHERE id = ?");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$school) die("Scuola non trovata.");
|
||||||
|
|
||||||
|
$first_name = trim($_POST['first_name'] ?? '');
|
||||||
|
$last_name = trim($_POST['last_name'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$specializations = trim($_POST['specializations'] ?? '');
|
||||||
|
|
||||||
|
// Validazione base
|
||||||
|
if (empty($first_name) || empty($last_name) || empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
header("Location: teacher_list.php?error=Campi obbligatori mancanti o email non valida");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se email esiste già
|
||||||
|
$stmt = $pdo->prepare("SELECT id, first_name, last_name FROM auth_users WHERE email = ? LIMIT 1");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
|
||||||
|
// 1) Trova (o crea) la riga in teachers per questo auth_user
|
||||||
|
$stmtT = $pdo->prepare("SELECT id FROM teachers WHERE user_id = ? LIMIT 1");
|
||||||
|
$stmtT->execute([(int)$existing['id']]);
|
||||||
|
$teacherRow = $stmtT->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$teacherRow) {
|
||||||
|
$unique_code = bin2hex(random_bytes(8));
|
||||||
|
$insT = $pdo->prepare("
|
||||||
|
INSERT INTO teachers (user_id, unique_code, status, created_by)
|
||||||
|
VALUES (?, ?, 'active', ?)
|
||||||
|
");
|
||||||
|
$insT->execute([(int)$existing['id'], $unique_code, (int)$iduserlogin]);
|
||||||
|
$teacher_id = (int)$pdo->lastInsertId();
|
||||||
|
} else {
|
||||||
|
$teacher_id = (int)$teacherRow['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Crea (o riusa) il link in teacher_schools come pending
|
||||||
|
$checkLink = $pdo->prepare("
|
||||||
|
SELECT id, status
|
||||||
|
FROM teacher_schools
|
||||||
|
WHERE teacher_id = ? AND school_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$checkLink->execute([$teacher_id, $school_id]);
|
||||||
|
$link = $checkLink->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($link && $link['status'] === 'active') {
|
||||||
|
header("Location: teacher_list.php?error=Insegnante già collegata alla scuola.");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($link && $link['status'] === 'pending') {
|
||||||
|
$link_id = (int)$link['id'];
|
||||||
|
} else {
|
||||||
|
$insLink = $pdo->prepare("
|
||||||
|
INSERT INTO teacher_schools (teacher_id, school_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'pending', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$insLink->execute([$teacher_id, $school_id]);
|
||||||
|
$link_id = (int)$pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email richiesta collegamento
|
||||||
|
$subject = "Richiesta di collegamento alla scuola {$school['name']}";
|
||||||
|
|
||||||
|
// ✅ NON cambio percorso base, aggiungo solo link_id
|
||||||
|
$confirmUrl = "http://localhost/yogiboook/public/userarea/confirm_teacher_link.php"
|
||||||
|
. "?email=" . urlencode($email)
|
||||||
|
. "&school_id={$school_id}"
|
||||||
|
. "&link_id={$link_id}";
|
||||||
|
|
||||||
|
$body = "
|
||||||
|
<h2>Ciao {$existing['first_name']},</h2>
|
||||||
|
<p>Il proprietario della scuola <strong>{$school['name']}</strong> vorrebbe collegarti alla sua struttura su YogiBoook.</p>
|
||||||
|
<p>Se accetti, comparirai nelle lezioni a te associate nella scuola.</p>
|
||||||
|
<p style='margin:30px 0;'>
|
||||||
|
<a href='{$confirmUrl}'
|
||||||
|
style='background:#0d6efd; color:white; padding:12px 24px; text-decoration:none; border-radius:6px;'>
|
||||||
|
Accetta collegamento
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>Se non riconosci questa richiesta, ignora questa email.</p>
|
||||||
|
<p style='color:#666; font-size:0.9em;'>YogiBoook – piattaforma per scuole yoga</p>
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = sendEmail($email, $subject, $body);
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
header("Location: teacher_list.php?success=Insegnante esistente trovato! Email di richiesta collegamento inviata.");
|
||||||
|
} else {
|
||||||
|
header("Location: teacher_list.php?error=Insegnante esistente trovato, ma errore invio email: " . urlencode($result['message']));
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Nuovo utente ===
|
||||||
|
$password = password_hash(bin2hex(random_bytes(12)), PASSWORD_DEFAULT);
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO auth_users (email, first_name, last_name, password, role_id, status, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, 2, 'active', NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$email, $first_name, $last_name, $password]);
|
||||||
|
$user_id = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Foto profilo (opzionale)
|
||||||
|
$profile_picture = null;
|
||||||
|
if (!empty($_FILES['profile_picture']['name']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$ext = strtolower(pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION));
|
||||||
|
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$new_name = "phototeachers/{$user_id}-" . time() . "-profile.$ext";
|
||||||
|
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $new_name)) {
|
||||||
|
$profile_picture = $new_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea record teachers
|
||||||
|
$unique_code = bin2hex(random_bytes(8));
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO teachers
|
||||||
|
(user_id, unique_code, phone, description, specializations, profile_picture, status, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, 'active', ?)
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $unique_code, $phone ?: null, $description, $specializations, $profile_picture, (int)$iduserlogin]);
|
||||||
|
$teacher_id = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Collega alla scuola (nuovo -> active diretto)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO teacher_schools
|
||||||
|
(teacher_id, school_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'active', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$teacher_id, $school_id]);
|
||||||
|
|
||||||
|
header("Location: teacher_list.php?success=Insegnante aggiunta con successo!");
|
||||||
|
exit;
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
<?php
|
||||||
|
// add_user.php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once 'class/mailer.php';
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: utente non loggato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Recupera SOLO la scuola corrente del proprietario loggato
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name, email AS school_email
|
||||||
|
FROM schools
|
||||||
|
WHERE owner_id = ? AND status = 'active'
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) {
|
||||||
|
die("Nessuna scuola trovata per questo proprietario.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = $school['id'];
|
||||||
|
$school_name = $school['name'];
|
||||||
|
$school_email = $school['school_email'];
|
||||||
|
|
||||||
|
// 2. Messaggi di feedback
|
||||||
|
$success_message = $_GET['success'] ?? null;
|
||||||
|
$error_message = $_GET['error'] ?? null;
|
||||||
|
|
||||||
|
// 3. GESTIONE POST - Aggiungi/Collega Utente
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'add_or_link_user') {
|
||||||
|
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$first_name = trim($_POST['first_name'] ?? '');
|
||||||
|
$last_name = trim($_POST['last_name'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
|
||||||
|
// Validazioni
|
||||||
|
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$error_message = "Email non valida.";
|
||||||
|
} elseif (empty($first_name) || empty($last_name)) {
|
||||||
|
$error_message = "Nome e cognome obbligatori.";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// CASE 1: Verifica se utente ESISTE già (case-insensitive)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, first_name, last_name, email_verified_at, status
|
||||||
|
FROM auth_users
|
||||||
|
WHERE LOWER(email) = LOWER(?)
|
||||||
|
");
|
||||||
|
$stmt->execute([$email]);
|
||||||
|
$existingUser = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existingUser) {
|
||||||
|
// ✅ UTENTE ESISTE → SOLO COLLEGA alla scuola
|
||||||
|
$user_id = $existingUser['id'];
|
||||||
|
|
||||||
|
// Verifica se è già collegato a questa scuola
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id FROM user_schools
|
||||||
|
WHERE user_id = ? AND school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$error_message = "Questo utente è già associato alla tua scuola.";
|
||||||
|
} else {
|
||||||
|
// COLLEGAMENTO
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_schools (user_id, school_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'active', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
|
||||||
|
// 📧 EMAIL: "Sei stato agganciato alla scuola XXXX"
|
||||||
|
$subject = "Associato a {$school_name} - Yogibook";
|
||||||
|
$body = "
|
||||||
|
<h2>Ciao {$first_name} {$last_name},</h2>
|
||||||
|
<p>Sei stato <strong>associato alla scuola {$school_name}</strong> sulla piattaforma Yogibook.</p>
|
||||||
|
<p>Ora puoi accedere con le tue credenziali e vedere le lezioni di questa scuola.</p>
|
||||||
|
<p><strong>Login:</strong> <a href='https://app.yogiboook.com/login'>app.yogiboook.com/login</a></p>
|
||||||
|
<hr>
|
||||||
|
<p><em>Se non riconosci questa scuola, contatta: {$school_email}</em></p>
|
||||||
|
<p style='color:#666; font-size:0.9em;'>Messaggio automatico – non rispondere.</p>
|
||||||
|
";
|
||||||
|
|
||||||
|
$emailResult = sendEmail($email, $subject, $body);
|
||||||
|
|
||||||
|
if ($emailResult['success']) {
|
||||||
|
$success_message = "Utente <strong>{$first_name} {$last_name}</strong> collegato con successo a {$school_name}! 📧 Email inviata.";
|
||||||
|
} else {
|
||||||
|
$error_message = "Utente collegato, ma errore email: " . $emailResult['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ❌ UTENTE NON ESISTE → CREA + link reset password
|
||||||
|
$tempPassword = bin2hex(random_bytes(16)); // Password random (inutile)
|
||||||
|
$hashedPassword = password_hash($tempPassword, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO auth_users (
|
||||||
|
email, first_name, last_name, phone,
|
||||||
|
password, role_id, status,
|
||||||
|
created_at, updated_at, email_verified_at
|
||||||
|
) VALUES (?, ?, ?, ?, ?, 2, 'active', NOW(), NOW(), NULL)
|
||||||
|
");
|
||||||
|
$success = $stmt->execute([
|
||||||
|
$email,
|
||||||
|
$first_name,
|
||||||
|
$last_name,
|
||||||
|
empty($phone) ? null : $phone,
|
||||||
|
$hashedPassword
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$user_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// COLLEGA alla scuola
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_schools (user_id, school_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'active', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
|
||||||
|
// 📧 EMAIL: Link diretto a reset password
|
||||||
|
$resetLink = "https://app.yogiboook.com/public/password/reset?email=" . urlencode($email);
|
||||||
|
|
||||||
|
$subject = "Benvenuto in {$school_name} - Imposta Password | Yogibook";
|
||||||
|
$body = "
|
||||||
|
<h2>Ciao {$first_name}, benvenuto in {$school_name}!</h2>
|
||||||
|
<p>La scuola ti ha invitato sulla piattaforma Yogibook.</p>
|
||||||
|
<p><strong>PRIMO PASSO OBBLIGATORIO:</strong> imposta la tua password:</p>
|
||||||
|
<p style='text-align:center; margin:40px 0;'>
|
||||||
|
<a href='{$resetLink}' style='background:#0d6efd; color:white; padding:15px 30px; text-decoration:none; border-radius:8px; font-size:16px; font-weight:bold;'>
|
||||||
|
IMPOSTA LA TUA PASSWORD
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p><small>Non funziona il link? Copia-incolla: <br><strong>{$resetLink}</strong></small></p>
|
||||||
|
<hr>
|
||||||
|
<p><strong>Login:</strong> <a href='https://app.yogiboook.com/login'>app.yogiboook.com/login</a></p>
|
||||||
|
<p><em>Contatta la scuola: {$school_email}</em></p>
|
||||||
|
<p style='color:#666; font-size:0.9em;'>Messaggio automatico – non rispondere.</p>
|
||||||
|
";
|
||||||
|
|
||||||
|
$emailResult = sendEmail($email, $subject, $body);
|
||||||
|
|
||||||
|
if ($emailResult['success']) {
|
||||||
|
$success_message = "✅ Nuovo utente <strong>{$first_name} {$last_name}</strong> creato e collegato a {$school_name}! 📧 Link reset password inviato.";
|
||||||
|
} else {
|
||||||
|
$error_message = "Utente creato/collegato, ma errore email: " . $emailResult['message'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error_message = "Errore creazione utente. Riprova.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Lista UTENTI ASSOCIATI SOLO A QUESTA SCUOLA (punto 1 ✅)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
au.id, au.first_name, au.last_name, au.email, au.phone,
|
||||||
|
au.email_verified_at, au.status AS user_status,
|
||||||
|
us.status AS school_status, us.created_at
|
||||||
|
FROM user_schools us
|
||||||
|
JOIN auth_users au ON us.user_id = au.id
|
||||||
|
WHERE us.school_id = ?
|
||||||
|
ORDER BY au.last_name, au.first_name
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$schoolUsers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Gestione Utenti - <?= htmlspecialchars($school_name) ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="page-title-box d-sm-flex align-items-center justify-content-between">
|
||||||
|
<h4 class="mb-sm-0">Gestione Utenti</h4>
|
||||||
|
<div class="page-title-right">
|
||||||
|
<a href="school_dashboard.php" class="btn btn-secondary">
|
||||||
|
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MESSAGGI -->
|
||||||
|
<?php if ($success_message): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bx bx-check-circle me-2"></i>
|
||||||
|
<?= $success_message ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error_message): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<i class="bx bx-error me-2"></i>
|
||||||
|
<?= htmlspecialchars($error_message) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- FORM AGGIUNGI UTENTE -->
|
||||||
|
<div class="col-xl-6">
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">👤 Aggiungi / Collega Utente</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="add_or_link_user">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="first_name" class="form-control" required
|
||||||
|
placeholder="Mario" maxlength="50">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Cognome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="last_name" class="form-control" required
|
||||||
|
placeholder="Rossi" maxlength="50">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Email <span class="text-danger">*</span></label>
|
||||||
|
<input type="email" name="email" class="form-control" required
|
||||||
|
placeholder="mario.rossi@email.com" autocomplete="email">
|
||||||
|
<div class="form-text">Se esiste già → lo collega. Altrimenti → lo crea.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Telefono (opzionale)</label>
|
||||||
|
<input type="tel" name="phone" class="form-control"
|
||||||
|
placeholder="+39 333 1234567">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="bx bx-user-plus me-2"></i>
|
||||||
|
Aggiungi / Collega
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STATS RAPIDE -->
|
||||||
|
<div class="col-xl-6">
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h3 class="text-primary mb-1"><?= count($schoolUsers) ?></h3>
|
||||||
|
<p class="mb-0 text-muted">Utenti associati a <?= htmlspecialchars($school_name) ?></p>
|
||||||
|
<?php if (count($schoolUsers) > 0): ?>
|
||||||
|
<small class="text-success">
|
||||||
|
<i class="bx bx-check-circle"></i>
|
||||||
|
<?= count(array_filter($schoolUsers, fn($u) => $u['school_status'] === 'active')) ?> attivi
|
||||||
|
</small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- LISTA UTENTI ASSOCIATI SOLO A QUESTA SCUOLA -->
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">👥 Utenti di <?= htmlspecialchars($school_name) ?> (<?= count($schoolUsers) ?>)</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($schoolUsers)): ?>
|
||||||
|
<div class="text-center py-5 text-muted">
|
||||||
|
<i class="bx bx-user-plus fs-1 mb-3 opacity-50"></i>
|
||||||
|
<p>Nessun utente associato ancora.</p>
|
||||||
|
<p class="small">Usa il form qui sopra per aggiungerne uno!</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Telefono</th>
|
||||||
|
<th>Associato il</th>
|
||||||
|
<th>Stato Scuola</th>
|
||||||
|
<th>Email Verificata</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($schoolUsers as $user): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong><?= htmlspecialchars($user['first_name'] . ' ' . $user['last_name']) ?></strong>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<small class="text-muted"><?= htmlspecialchars($user['email']) ?></small>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($user['phone'] ?: '<span class="text-muted">—</span>') ?></td>
|
||||||
|
<td>
|
||||||
|
<small class="text-muted">
|
||||||
|
<?= date('d/m/Y', strtotime($user['created_at'])) ?>
|
||||||
|
</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $user['school_status'] === 'active' ? 'success' : 'warning' ?>">
|
||||||
|
<?= ucfirst($user['school_status']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?= $user['email_verified_at']
|
||||||
|
? '<span class="badge bg-success"><i class="bx bx-check"></i> Sì</span>'
|
||||||
|
: '<span class="badge bg-warning">Non ancora</span>' ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script>
|
||||||
|
// Auto-hide alerts dopo 8 secondi
|
||||||
|
setTimeout(() => {
|
||||||
|
document.querySelectorAll('.alert').forEach(alert => {
|
||||||
|
var bsAlert = new bootstrap.Alert(alert);
|
||||||
|
bsAlert.close();
|
||||||
|
});
|
||||||
|
}, 8000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,557 @@
|
|||||||
|
<?php
|
||||||
|
// admin_subscription_plans.php
|
||||||
|
|
||||||
|
// Show errors (remove in production)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// DB connection
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Check login
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: ID utente non definito.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current user is admin.
|
||||||
|
* IMPORTANT: set your real admin role_id(s) here.
|
||||||
|
*/
|
||||||
|
function isAdmin(PDO $pdo, int $userId): bool
|
||||||
|
{
|
||||||
|
// TODO: adjust these role ids according to your system
|
||||||
|
$adminRoleIds = [1]; // e.g. 1 = superadmin
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT role_id FROM auth_users WHERE id = ?");
|
||||||
|
$stmt->execute([$userId]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$row) return false;
|
||||||
|
return in_array((int)$row['role_id'], $adminRoleIds, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAdmin($pdo, (int)$iduserlogin)) {
|
||||||
|
die("Accesso negato: pagina riservata all'amministratore.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMoneyFromCents(int $cents, string $currency): string
|
||||||
|
{
|
||||||
|
$amount = number_format($cents / 100, 2, ',', '.');
|
||||||
|
return $amount . ' ' . strtoupper($currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle POST actions
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
// Common fields
|
||||||
|
$code = trim($_POST['code'] ?? '');
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
|
||||||
|
$stripe_product_id = trim($_POST['stripe_product_id'] ?? '');
|
||||||
|
$stripe_price_id = trim($_POST['stripe_price_id'] ?? '');
|
||||||
|
|
||||||
|
$currency = strtoupper(trim($_POST['currency'] ?? 'EUR'));
|
||||||
|
$unit_amount = (int)($_POST['unit_amount'] ?? 0); // cents
|
||||||
|
$interval = in_array(($_POST['interval'] ?? ''), ['day', 'week', 'month', 'year'], true) ? $_POST['interval'] : 'month';
|
||||||
|
$interval_count = max(1, (int)($_POST['interval_count'] ?? 1));
|
||||||
|
$trial_days = max(0, (int)($_POST['trial_days'] ?? 0));
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
|
||||||
|
// ADD
|
||||||
|
if ($action === 'add_plan') {
|
||||||
|
if ($code === '' || $name === '') {
|
||||||
|
$error = "Code e Nome sono obbligatori.";
|
||||||
|
} elseif ($stripe_price_id === '') {
|
||||||
|
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
|
||||||
|
} elseif (strlen($currency) !== 3) {
|
||||||
|
$error = "Currency deve essere nel formato ISO (es. EUR).";
|
||||||
|
} elseif ($unit_amount < 0) {
|
||||||
|
$error = "Unit amount non può essere negativo.";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO billing_plans
|
||||||
|
(code, name, description, stripe_product_id, stripe_price_id, currency, unit_amount, `interval`, interval_count, trial_days, is_active, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
$code,
|
||||||
|
$name,
|
||||||
|
$description ?: null,
|
||||||
|
$stripe_product_id ?: null,
|
||||||
|
$stripe_price_id,
|
||||||
|
$currency,
|
||||||
|
$unit_amount,
|
||||||
|
$interval,
|
||||||
|
$interval_count,
|
||||||
|
$trial_days,
|
||||||
|
$is_active
|
||||||
|
]);
|
||||||
|
$success_message = "Piano creato con successo.";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Errore durante la creazione del piano: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDIT
|
||||||
|
if ($action === 'edit_plan') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
$error = "ID piano non valido.";
|
||||||
|
} elseif ($code === '' || $name === '') {
|
||||||
|
$error = "Code e Nome sono obbligatori.";
|
||||||
|
} elseif ($stripe_price_id === '') {
|
||||||
|
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
|
||||||
|
} elseif (strlen($currency) !== 3) {
|
||||||
|
$error = "Currency deve essere nel formato ISO (es. EUR).";
|
||||||
|
} elseif ($unit_amount < 0) {
|
||||||
|
$error = "Unit amount non può essere negativo.";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE billing_plans
|
||||||
|
SET
|
||||||
|
code = ?,
|
||||||
|
name = ?,
|
||||||
|
description = ?,
|
||||||
|
stripe_product_id = ?,
|
||||||
|
stripe_price_id = ?,
|
||||||
|
currency = ?,
|
||||||
|
unit_amount = ?,
|
||||||
|
`interval` = ?,
|
||||||
|
interval_count = ?,
|
||||||
|
trial_days = ?,
|
||||||
|
is_active = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([
|
||||||
|
$code,
|
||||||
|
$name,
|
||||||
|
$description ?: null,
|
||||||
|
$stripe_product_id ?: null,
|
||||||
|
$stripe_price_id,
|
||||||
|
$currency,
|
||||||
|
$unit_amount,
|
||||||
|
$interval,
|
||||||
|
$interval_count,
|
||||||
|
$trial_days,
|
||||||
|
$is_active,
|
||||||
|
$id
|
||||||
|
]);
|
||||||
|
$success_message = "Piano aggiornato con successo.";
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$error = "Errore durante l'aggiornamento del piano: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DISABLE
|
||||||
|
if ($action === 'disable_plan') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
$error = "ID piano non valido.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 0, updated_at = NOW() WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$success_message = "Piano disattivato.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENABLE
|
||||||
|
if ($action === 'enable_plan') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
$error = "ID piano non valido.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 1, updated_at = NOW() WHERE id = ?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
$success_message = "Piano riattivato.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch plans
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM billing_plans ORDER BY is_active DESC, name ASC");
|
||||||
|
$stmt->execute();
|
||||||
|
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="card radius-10 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-1">Subscription Plans (Admin)</h5>
|
||||||
|
<p class="mb-0 text-muted">Gestione piani abbonamento delle scuole</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPlanModal">
|
||||||
|
<i class="bx bx-plus"></i> Nuovo Piano
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger" role="alert"><?php echo htmlspecialchars($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($success_message)): ?>
|
||||||
|
<div class="alert alert-success" role="alert"><?php echo htmlspecialchars($success_message); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">Elenco Piani</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="plansTable" class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Prezzo</th>
|
||||||
|
<th>Intervallo</th>
|
||||||
|
<th>Trial</th>
|
||||||
|
<th>Stripe Product</th>
|
||||||
|
<th>Stripe Price *</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($plans as $p): ?>
|
||||||
|
<tr>
|
||||||
|
<td><span class="badge bg-dark"><?php echo htmlspecialchars($p['code']); ?></span></td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
|
||||||
|
<?php if (!empty($p['description'])): ?>
|
||||||
|
<div class="text-muted small"><?php echo nl2br(htmlspecialchars($p['description'])); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo formatMoneyFromCents((int)$p['unit_amount'], $p['currency']); ?></td>
|
||||||
|
<td><?php echo (int)$p['interval_count'] . ' / ' . htmlspecialchars($p['interval']); ?></td>
|
||||||
|
<td><?php echo (int)$p['trial_days'] > 0 ? ((int)$p['trial_days'] . ' gg') : '—'; ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($p['stripe_product_id'])): ?>
|
||||||
|
<span class="badge bg-secondary"><?php echo htmlspecialchars($p['stripe_product_id']); ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-info"><?php echo htmlspecialchars($p['stripe_price_id']); ?></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge <?php echo ((int)$p['is_active'] === 1) ? 'bg-success' : 'bg-danger'; ?>">
|
||||||
|
<?php echo ((int)$p['is_active'] === 1) ? 'Attivo' : 'Disattivo'; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-nowrap">
|
||||||
|
<button class="btn btn-sm btn-warning"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#editPlanModal"
|
||||||
|
onclick='fillEditPlanModal(<?php echo json_encode([
|
||||||
|
"id" => (int)$p["id"],
|
||||||
|
"code" => $p["code"],
|
||||||
|
"name" => $p["name"],
|
||||||
|
"description" => $p["description"],
|
||||||
|
"stripe_product_id" => $p["stripe_product_id"],
|
||||||
|
"stripe_price_id" => $p["stripe_price_id"],
|
||||||
|
"currency" => $p["currency"],
|
||||||
|
"unit_amount" => (int)$p["unit_amount"],
|
||||||
|
"interval" => $p["interval"],
|
||||||
|
"interval_count" => (int)$p["interval_count"],
|
||||||
|
"trial_days" => (int)$p["trial_days"],
|
||||||
|
"is_active" => (int)$p["is_active"],
|
||||||
|
]); ?>)'>
|
||||||
|
<i class="bx bx-edit"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<?php if ((int)$p['is_active'] === 1): ?>
|
||||||
|
<form method="POST" style="display:inline;" onsubmit="return confirm('Disattivare questo piano?');">
|
||||||
|
<input type="hidden" name="action" value="disable_plan">
|
||||||
|
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
|
||||||
|
<button class="btn btn-sm btn-danger" type="submit" title="Disattiva">
|
||||||
|
<i class="bx bx-power-off"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<form method="POST" style="display:inline;" onsubmit="return confirm('Riattivare questo piano?');">
|
||||||
|
<input type="hidden" name="action" value="enable_plan">
|
||||||
|
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
|
||||||
|
<button class="btn btn-sm btn-success" type="submit" title="Riattiva">
|
||||||
|
<i class="bx bx-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 text-muted small">
|
||||||
|
<strong>Nota:</strong> `unit_amount` è in centesimi (es. 2990 = 29,90 EUR)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#plansTable').DataTable({
|
||||||
|
"language": {
|
||||||
|
"url": "//cdn.datatables.net/plug-ins/1.10.25/i18n/Italian.json"
|
||||||
|
},
|
||||||
|
"pageLength": 10,
|
||||||
|
"lengthMenu": [10, 25, 50, 100],
|
||||||
|
"order": [
|
||||||
|
[1, "asc"]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function fillEditPlanModal(p) {
|
||||||
|
document.getElementById('edit_id').value = p.id;
|
||||||
|
document.getElementById('edit_code').value = p.code || '';
|
||||||
|
document.getElementById('edit_name').value = p.name || '';
|
||||||
|
document.getElementById('edit_description').value = p.description || '';
|
||||||
|
document.getElementById('edit_stripe_product_id').value = p.stripe_product_id || '';
|
||||||
|
document.getElementById('edit_stripe_price_id').value = p.stripe_price_id || '';
|
||||||
|
document.getElementById('edit_currency').value = p.currency || 'EUR';
|
||||||
|
document.getElementById('edit_unit_amount').value = (p.unit_amount ?? 0);
|
||||||
|
document.getElementById('edit_interval').value = p.interval || 'month';
|
||||||
|
document.getElementById('edit_interval_count').value = p.interval_count || 1;
|
||||||
|
document.getElementById('edit_trial_days').value = p.trial_days || 0;
|
||||||
|
document.getElementById('edit_is_active').checked = (parseInt(p.is_active, 10) === 1);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- ADD MODAL -->
|
||||||
|
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Nuovo Piano</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add_plan">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Code *</label>
|
||||||
|
<input type="text" name="code" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label">Nome *</label>
|
||||||
|
<input type="text" name="name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descrizione</label>
|
||||||
|
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Valuta</label>
|
||||||
|
<input type="text" name="currency" class="form-control" value="EUR" maxlength="3">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Unit amount (centesimi) *</label>
|
||||||
|
<input type="number" name="unit_amount" class="form-control" min="0" value="0" required>
|
||||||
|
<small class="text-muted">es: 2990 = 29,90</small>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Trial days</label>
|
||||||
|
<input type="number" name="trial_days" class="form-control" min="0" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Interval</label>
|
||||||
|
<select name="interval" class="form-control">
|
||||||
|
<option value="day">day</option>
|
||||||
|
<option value="week">week</option>
|
||||||
|
<option value="month" selected>month</option>
|
||||||
|
<option value="year">year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Interval count</label>
|
||||||
|
<input type="number" name="interval_count" class="form-control" min="1" value="1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Stato</label>
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" value="1" checked>
|
||||||
|
<label class="form-check-label">Attivo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Stripe product id</label>
|
||||||
|
<input type="text" name="stripe_product_id" class="form-control" placeholder="prod_... (optional)">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Stripe price id *</label>
|
||||||
|
<input type="text" name="stripe_price_id" class="form-control" placeholder="price_..." required>
|
||||||
|
<small class="text-muted">Obbligatorio (NOT NULL)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Crea Piano</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- EDIT MODAL -->
|
||||||
|
<div class="modal fade" id="editPlanModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Modifica Piano</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit_plan">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Code *</label>
|
||||||
|
<input type="text" name="code" id="edit_code" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label">Nome *</label>
|
||||||
|
<input type="text" name="name" id="edit_name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descrizione</label>
|
||||||
|
<textarea name="description" id="edit_description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Valuta</label>
|
||||||
|
<input type="text" name="currency" id="edit_currency" class="form-control" maxlength="3">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Unit amount (centesimi) *</label>
|
||||||
|
<input type="number" name="unit_amount" id="edit_unit_amount" class="form-control" min="0" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Trial days</label>
|
||||||
|
<input type="number" name="trial_days" id="edit_trial_days" class="form-control" min="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Interval</label>
|
||||||
|
<select name="interval" id="edit_interval" class="form-control">
|
||||||
|
<option value="day">day</option>
|
||||||
|
<option value="week">week</option>
|
||||||
|
<option value="month">month</option>
|
||||||
|
<option value="year">year</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Interval count</label>
|
||||||
|
<input type="number" name="interval_count" id="edit_interval_count" class="form-control" min="1">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label">Stato</label>
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active" value="1">
|
||||||
|
<label class="form-check-label">Attivo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Stripe product id</label>
|
||||||
|
<input type="text" name="stripe_product_id" id="edit_stripe_product_id" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label">Stripe price id *</label>
|
||||||
|
<input type="text" name="stripe_price_id" id="edit_stripe_price_id" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Salva</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,544 @@
|
|||||||
|
<?php
|
||||||
|
// admin_subscriptions.php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// ---- Auth check ----
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Access denied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Admin check (Vanguard usually uses role_id=1 for admin) ----
|
||||||
|
// Adjust role_id list if needed.
|
||||||
|
$stmt = $pdo->prepare("SELECT role_id, email FROM auth_users WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$me = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$me || !in_array((int)$me['role_id'], [1])) {
|
||||||
|
die("Access denied: admin only.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Handle POST actions ----
|
||||||
|
$success_message = null;
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
// Update school status (local app status)
|
||||||
|
if ($action === 'update_school_status') {
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||||
|
$new_status = $_POST['status'] ?? 'active';
|
||||||
|
$allowed = ['active', 'inactive', 'suspended'];
|
||||||
|
|
||||||
|
if ($school_id <= 0 || !in_array($new_status, $allowed, true)) {
|
||||||
|
$error = "Invalid request.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("UPDATE schools SET status = ? WHERE id = ?");
|
||||||
|
if ($stmt->execute([$new_status, $school_id])) {
|
||||||
|
$success_message = "School status updated.";
|
||||||
|
} else {
|
||||||
|
$error = "Failed updating school status.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Stripe actions (cancel, resume, change plan) should call Stripe API.
|
||||||
|
// Here we provide placeholders so UI is ready.
|
||||||
|
if ($action === 'flag_cancel_at_period_end') {
|
||||||
|
$sub_id = (int)($_POST['subscription_row_id'] ?? 0);
|
||||||
|
$flag = (int)($_POST['flag'] ?? 0);
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($sub_id <= 0 || $school_id <= 0 || !in_array($flag, [0, 1], true)) {
|
||||||
|
$error = "Invalid request.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE school_subscriptions
|
||||||
|
SET cancel_at_period_end = ?
|
||||||
|
WHERE id = ? AND school_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([$flag, $sub_id, $school_id]);
|
||||||
|
|
||||||
|
if ($ok) {
|
||||||
|
$success_message = $flag ? "Marked cancel at period end (LOCAL)." : "Unmarked cancel at period end (LOCAL).";
|
||||||
|
} else {
|
||||||
|
$error = "Failed updating subscription flag.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Filters (GET) ----
|
||||||
|
$q = trim($_GET['q'] ?? '');
|
||||||
|
$plan_id = (int)($_GET['plan_id'] ?? 0);
|
||||||
|
$sub_status = trim($_GET['sub_status'] ?? ''); // e.g. active, trialing, past_due, canceled, incomplete, unpaid
|
||||||
|
$school_status = trim($_GET['school_status'] ?? ''); // active, inactive, suspended
|
||||||
|
$has_sub = $_GET['has_sub'] ?? ''; // '1' or '0' or ''
|
||||||
|
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
// Search by school name/email/owner email
|
||||||
|
if ($q !== '') {
|
||||||
|
$where[] = "(s.name LIKE ? OR s.email LIKE ? OR ou.email LIKE ? OR CONCAT(ou.first_name,' ',ou.last_name) LIKE ?)";
|
||||||
|
$like = '%' . $q . '%';
|
||||||
|
$params[] = $like;
|
||||||
|
$params[] = $like;
|
||||||
|
$params[] = $like;
|
||||||
|
$params[] = $like;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plan_id > 0) {
|
||||||
|
$where[] = "ss.plan_id = ?";
|
||||||
|
$params[] = $plan_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sub_status !== '') {
|
||||||
|
$where[] = "ss.status = ?";
|
||||||
|
$params[] = $sub_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($school_status !== '') {
|
||||||
|
$where[] = "s.status = ?";
|
||||||
|
$params[] = $school_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($has_sub === '1') {
|
||||||
|
$where[] = "ss.id IS NOT NULL";
|
||||||
|
} elseif ($has_sub === '0') {
|
||||||
|
$where[] = "ss.id IS NULL";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlWhere = '';
|
||||||
|
if (!empty($where)) {
|
||||||
|
$sqlWhere = "WHERE " . implode(" AND ", $where);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Load plans for filter dropdown ----
|
||||||
|
$stmt = $pdo->prepare("SELECT id, code, name, currency, unit_amount, `interval`, interval_count, is_active FROM billing_plans ORDER BY is_active DESC, unit_amount ASC, name ASC");
|
||||||
|
$stmt->execute();
|
||||||
|
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ---- Main query: schools + subscription + owner + plan ----
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
s.id AS school_id,
|
||||||
|
s.name AS school_name,
|
||||||
|
s.email AS school_email,
|
||||||
|
s.status AS school_status,
|
||||||
|
s.created_at AS school_created_at,
|
||||||
|
|
||||||
|
ou.id AS owner_id,
|
||||||
|
ou.first_name AS owner_first_name,
|
||||||
|
ou.last_name AS owner_last_name,
|
||||||
|
ou.email AS owner_email,
|
||||||
|
|
||||||
|
ss.id AS subscription_row_id,
|
||||||
|
ss.stripe_customer_id,
|
||||||
|
ss.stripe_subscription_id,
|
||||||
|
COALESCE(ss.status, 'none') AS subscription_status,
|
||||||
|
ss.current_period_start,
|
||||||
|
ss.current_period_end,
|
||||||
|
ss.trial_start,
|
||||||
|
ss.trial_end,
|
||||||
|
ss.cancel_at_period_end,
|
||||||
|
ss.updated_at AS subscription_updated_at,
|
||||||
|
|
||||||
|
bp.id AS plan_id,
|
||||||
|
bp.code AS plan_code,
|
||||||
|
bp.name AS plan_name,
|
||||||
|
bp.currency,
|
||||||
|
bp.unit_amount,
|
||||||
|
bp.`interval`,
|
||||||
|
bp.interval_count
|
||||||
|
|
||||||
|
FROM schools s
|
||||||
|
JOIN auth_users ou ON ou.id = s.owner_id
|
||||||
|
LEFT JOIN school_subscriptions ss ON ss.school_id = s.id
|
||||||
|
LEFT JOIN billing_plans bp ON bp.id = ss.plan_id
|
||||||
|
|
||||||
|
$sqlWhere
|
||||||
|
ORDER BY
|
||||||
|
-- Put schools without subscription at the bottom
|
||||||
|
CASE WHEN ss.id IS NULL THEN 1 ELSE 0 END ASC,
|
||||||
|
|
||||||
|
-- Show problematic subscriptions first
|
||||||
|
FIELD(ss.status, 'past_due','unpaid','incomplete','incomplete_expired','canceled','paused','trialing','active') ASC,
|
||||||
|
|
||||||
|
s.created_at DESC
|
||||||
|
|
||||||
|
");
|
||||||
|
$stmt->execute($params);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// ---- Helpers ----
|
||||||
|
function h($v)
|
||||||
|
{
|
||||||
|
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function moneyFmt($unit_amount, $currency)
|
||||||
|
{
|
||||||
|
if ($unit_amount === null || $currency === null) return '—';
|
||||||
|
$amount = ((int)$unit_amount) / 100;
|
||||||
|
return number_format($amount, 2, ',', '.') . ' ' . strtoupper($currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dateFmt($dt)
|
||||||
|
{
|
||||||
|
if (!$dt) return '—';
|
||||||
|
// Accept both timestamp and datetime strings
|
||||||
|
$ts = is_numeric($dt) ? (int)$dt : strtotime($dt);
|
||||||
|
if (!$ts) return '—';
|
||||||
|
return date('d/m/Y', $ts);
|
||||||
|
}
|
||||||
|
|
||||||
|
function badgeClassForSub($status)
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'active' => 'bg-success',
|
||||||
|
'trialing' => 'bg-info',
|
||||||
|
'past_due' => 'bg-warning',
|
||||||
|
'unpaid' => 'bg-danger',
|
||||||
|
'incomplete' => 'bg-warning',
|
||||||
|
'incomplete_expired' => 'bg-danger',
|
||||||
|
'canceled' => 'bg-secondary',
|
||||||
|
'paused' => 'bg-secondary',
|
||||||
|
'none' => 'bg-secondary',
|
||||||
|
];
|
||||||
|
return $map[$status] ?? 'bg-dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
function badgeClassForSchool($status)
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'active' => 'bg-success',
|
||||||
|
'inactive' => 'bg-secondary',
|
||||||
|
'suspended' => 'bg-danger',
|
||||||
|
];
|
||||||
|
return $map[$status] ?? 'bg-dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="card radius-10 mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Admin Subscriptions</h5>
|
||||||
|
<div class="text-muted small">Schools + Stripe subscription status overview</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="admin_billing_plans.php" class="btn btn-outline-primary">
|
||||||
|
<i class="bx bx-list-ul"></i> Billing Plans
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($success_message): ?>
|
||||||
|
<div class="alert alert-success"><?php echo h($success_message); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger"><?php echo h($error); ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Filters -->
|
||||||
|
<div class="card radius-10 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="GET" class="row g-2 align-items-end">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Search</label>
|
||||||
|
<input type="text" class="form-control" name="q" value="<?php echo h($q); ?>" placeholder="School / email / owner">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Plan</label>
|
||||||
|
<select class="form-control" name="plan_id">
|
||||||
|
<option value="0">All plans</option>
|
||||||
|
<?php foreach ($plans as $p): ?>
|
||||||
|
<option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)$p['id'] === $plan_id) ? 'selected' : ''; ?>>
|
||||||
|
<?php
|
||||||
|
$label = $p['name'] . ' (' . $p['code'] . ') - ' . moneyFmt($p['unit_amount'], $p['currency']);
|
||||||
|
$label .= ' / ' . $p['interval_count'] . ' ' . $p['interval'];
|
||||||
|
if (!(int)$p['is_active']) $label .= ' [inactive]';
|
||||||
|
echo h($label);
|
||||||
|
?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Subscription status</label>
|
||||||
|
<select class="form-control" name="sub_status">
|
||||||
|
<option value="">All</option>
|
||||||
|
<?php foreach (['none', 'active', 'trialing', 'past_due', 'unpaid', 'incomplete', 'incomplete_expired', 'canceled', 'paused'] as $st): ?>
|
||||||
|
<option value="<?php echo h($st); ?>" <?php echo ($sub_status === $st) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo h($st); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">School status</label>
|
||||||
|
<select class="form-control" name="school_status">
|
||||||
|
<option value="">All</option>
|
||||||
|
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
|
||||||
|
<option value="<?php echo h($st); ?>" <?php echo ($school_status === $st) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo h($st); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-1">
|
||||||
|
<label class="form-label">Has sub</label>
|
||||||
|
<select class="form-control" name="has_sub">
|
||||||
|
<option value="" <?php echo ($has_sub === '') ? 'selected' : ''; ?>>All</option>
|
||||||
|
<option value="1" <?php echo ($has_sub === '1') ? 'selected' : ''; ?>>Yes</option>
|
||||||
|
<option value="0" <?php echo ($has_sub === '0') ? 'selected' : ''; ?>>No</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-12 d-flex gap-2 mt-2">
|
||||||
|
<button class="btn btn-primary" type="submit">
|
||||||
|
<i class="bx bx-filter"></i> Apply
|
||||||
|
</button>
|
||||||
|
<a class="btn btn-outline-secondary" href="admin_subscriptions.php">Reset</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results -->
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0">Schools</h6>
|
||||||
|
<div class="text-muted small"><?php echo (int)count($rows); ?> rows</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered align-middle mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="min-width:260px;">School</th>
|
||||||
|
<th style="min-width:220px;">Owner</th>
|
||||||
|
<th style="min-width:190px;">School status</th>
|
||||||
|
<th style="min-width:160px;">Subscription</th>
|
||||||
|
<th style="min-width:220px;">Plan</th>
|
||||||
|
<th style="min-width:200px;">Period</th>
|
||||||
|
<th style="min-width:200px;">Trial</th>
|
||||||
|
<th style="min-width:140px;">Cancel at period end</th>
|
||||||
|
<th style="min-width:260px;">Stripe IDs</th>
|
||||||
|
<th style="min-width:160px;">Updated</th>
|
||||||
|
<th style="min-width:180px;">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($rows as $row): ?>
|
||||||
|
<?php
|
||||||
|
$hasSubscription = !empty($row['subscription_row_id']);
|
||||||
|
$subStatus = $row['subscription_status'] ?? 'none';
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?php echo h($row['school_name']); ?></div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
ID: <?php echo (int)$row['school_id']; ?> · <?php echo h($row['school_email']); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Created: <?php echo h(dateFmt($row['school_created_at'])); ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?php echo h(trim(($row['owner_first_name'] ?? '') . ' ' . ($row['owner_last_name'] ?? ''))); ?></div>
|
||||||
|
<div class="text-muted small"><?php echo h($row['owner_email']); ?></div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<form method="POST" class="d-flex gap-2 align-items-center">
|
||||||
|
<input type="hidden" name="action" value="update_school_status">
|
||||||
|
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
|
||||||
|
|
||||||
|
<select name="status" class="form-control form-control-sm" style="min-width:140px;">
|
||||||
|
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
|
||||||
|
<option value="<?php echo h($st); ?>" <?php echo ($row['school_status'] === $st) ? 'selected' : ''; ?>>
|
||||||
|
<?php echo h($st); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-primary">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="badge <?php echo h(badgeClassForSchool($row['school_status'])); ?>">
|
||||||
|
<?php echo h($row['school_status']); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<span class="badge <?php echo h(badgeClassForSub($subStatus)); ?>">
|
||||||
|
<?php echo h($subStatus); ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<div class="text-muted small mt-2">
|
||||||
|
Qty: <?php echo (int)($row['quantity'] ?? 1); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription && !empty($row['plan_id'])): ?>
|
||||||
|
<div class="fw-bold"><?php echo h($row['plan_name']); ?></div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<?php echo h($row['plan_code']); ?> · <?php echo h(moneyFmt($row['unit_amount'], $row['currency'])); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Every <?php echo (int)$row['interval_count']; ?> <?php echo h($row['interval']); ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Start: <?php echo h(dateFmt($row['current_period_start'])); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
End: <?php echo h(dateFmt($row['current_period_end'])); ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<div class="text-muted small">
|
||||||
|
Start: <?php echo h(dateFmt($row['trial_start'])); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
End: <?php echo h(dateFmt($row['trial_end'])); ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
|
||||||
|
<span class="badge bg-warning">Yes</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<div class="text-muted small">
|
||||||
|
cust: <?php echo h($row['stripe_customer_id'] ?: '—'); ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small">
|
||||||
|
sub: <?php echo h($row['stripe_subscription_id'] ?: '—'); ?>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php echo h(dateFmt($row['subscription_updated_at'])); ?>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<?php if ($hasSubscription): ?>
|
||||||
|
<form method="POST" class="d-inline">
|
||||||
|
<input type="hidden" name="action" value="flag_cancel_at_period_end">
|
||||||
|
<input type="hidden" name="subscription_row_id" value="<?php echo (int)$row['subscription_row_id']; ?>">
|
||||||
|
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
|
||||||
|
<input type="hidden" name="flag" value="<?php echo ((int)$row['cancel_at_period_end'] === 1) ? 0 : 1; ?>">
|
||||||
|
|
||||||
|
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-success">
|
||||||
|
Unmark
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-warning">
|
||||||
|
Mark
|
||||||
|
</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">—</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if (empty($rows)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="11" class="text-center text-muted py-4">No results</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div> <!-- /page-content -->
|
||||||
|
</div> <!-- /page-wrapper -->
|
||||||
|
|
||||||
|
<?php if (file_exists('include/footer.php')) include('include/footer.php'); ?>
|
||||||
|
</div> <!-- /wrapper -->
|
||||||
|
|
||||||
|
<?php if (file_exists('jsinclude.php')) include('jsinclude.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
// ajax_client_bookings.php
|
||||||
|
|
||||||
|
// Non serve require_once se usi la stessa connessione del template principale
|
||||||
|
// Ma per sicurezza includi headscript.php (che contiene già DBHandler)
|
||||||
|
require_once('include/headscript.php'); // adatta il percorso se necessario
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Poi il resto del codice...
|
||||||
|
$user_id = (int)($_POST['user_id'] ?? 0);
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($user_id <= 0 || $school_id <= 0) {
|
||||||
|
echo '<div class="alert alert-warning">Dati non validi.</div>';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
cs.id AS session_id,
|
||||||
|
cs.session_date,
|
||||||
|
cs.start_time,
|
||||||
|
cs.end_time,
|
||||||
|
c.name AS class_name,
|
||||||
|
ct.level,
|
||||||
|
sb.status,
|
||||||
|
sb.booked_at,
|
||||||
|
o.id AS order_id,
|
||||||
|
o.order_number,
|
||||||
|
o.total_entries,
|
||||||
|
o.available_entries
|
||||||
|
FROM session_bookings sb
|
||||||
|
INNER JOIN class_sessions cs
|
||||||
|
ON sb.session_id = cs.id
|
||||||
|
AND cs.school_id = ?
|
||||||
|
INNER JOIN classes c
|
||||||
|
ON cs.class_id = c.id
|
||||||
|
AND c.school_id = ?
|
||||||
|
INNER JOIN class_types ct
|
||||||
|
ON cs.class_type_id = ct.id
|
||||||
|
AND ct.school_id = ?
|
||||||
|
LEFT JOIN orders o
|
||||||
|
ON sb.order_id = o.id
|
||||||
|
AND o.school_id = ?
|
||||||
|
WHERE sb.user_id = ?
|
||||||
|
ORDER BY cs.session_date DESC, cs.start_time DESC
|
||||||
|
LIMIT 100
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$school_id,
|
||||||
|
$school_id,
|
||||||
|
$school_id,
|
||||||
|
$school_id,
|
||||||
|
$user_id
|
||||||
|
]);
|
||||||
|
$bookings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (empty($bookings)) {
|
||||||
|
echo '<div class="alert alert-info">Nessuna prenotazione registrata per questo utente.</div>';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-bordered">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Orario</th>
|
||||||
|
<th>Classe</th>
|
||||||
|
<th>Livello</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Ordine</th>
|
||||||
|
<th>Data prenotazione</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($bookings as $b):
|
||||||
|
$statoClass = match ($b['status']) {
|
||||||
|
'attended' => 'bg-success',
|
||||||
|
'missed' => 'bg-danger',
|
||||||
|
'booked' => (strtotime($b['session_date']) >= time()) ? 'bg-primary' : 'bg-secondary',
|
||||||
|
'cancelled' => 'bg-dark',
|
||||||
|
'rescheduled' => 'bg-info',
|
||||||
|
default => 'bg-secondary'
|
||||||
|
};
|
||||||
|
$statoText = match ($b['status']) {
|
||||||
|
'attended' => 'Frequentata',
|
||||||
|
'missed' => 'Persa',
|
||||||
|
'booked' => (strtotime($b['session_date']) >= time()) ? 'Prenotata' : 'Scaduta',
|
||||||
|
'cancelled' => 'Cancellata',
|
||||||
|
'rescheduled' => 'Riprog.',
|
||||||
|
default => $b['status']
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= date('d/m/Y', strtotime($b['session_date'])) ?></td>
|
||||||
|
<td><?= substr($b['start_time'], 0, 5) ?> – <?= substr($b['end_time'], 0, 5) ?></td>
|
||||||
|
<td><?= htmlspecialchars($b['class_name']) ?></td>
|
||||||
|
<td><?= ucfirst($b['level'] ?? '—') ?></td>
|
||||||
|
<td><span class="badge <?= $statoClass ?>"><?= $statoText ?></span></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($b['order_id']): ?>
|
||||||
|
#<?= $b['order_number'] ?><br>
|
||||||
|
<small><?= $b['available_entries'] ?>/<?= $b['total_entries'] ?></small>
|
||||||
|
<?php else: ?>
|
||||||
|
—
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><small><?= date('d/m/Y H:i', strtotime($b['booked_at'])) ?></small></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
// public/userarea/api/_bootstrap.php
|
||||||
|
// Auth via Laravel Sanctum personal access tokens (PDO only).
|
||||||
|
// Table: auth_personal_access_tokens
|
||||||
|
// Exposes: $pdo (PDO), $iduserlogin (int)
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// CORS for Flutter Web (dev server runs on a different port)
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Headers: Authorization, Content-Type, Accept');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
|
||||||
|
header('Access-Control-Max-Age: 86400');
|
||||||
|
|
||||||
|
// Preflight
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(204);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../class/db-functions.php';
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Authorization header (Apache/Nginx/CGI compatible)
|
||||||
|
*/
|
||||||
|
function getAuthorizationHeader(): ?string
|
||||||
|
{
|
||||||
|
if (function_exists('getallheaders')) {
|
||||||
|
$headers = getallheaders();
|
||||||
|
foreach ($headers as $k => $v) {
|
||||||
|
if (strtolower($k) === 'authorization') return $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) return $_SERVER['HTTP_AUTHORIZATION'];
|
||||||
|
if (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) return $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract Bearer token
|
||||||
|
*/
|
||||||
|
function getBearerToken(): ?string
|
||||||
|
{
|
||||||
|
$auth = getAuthorizationHeader();
|
||||||
|
if (!$auth) return null;
|
||||||
|
|
||||||
|
if (preg_match('/Bearer\s+(.*)$/i', $auth, $m)) {
|
||||||
|
return trim($m[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bearer = getBearerToken();
|
||||||
|
if (!$bearer) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (missing Bearer token).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanctum plain token format: "<id>|<plainTextToken>"
|
||||||
|
if (strpos($bearer, '|') === false) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token format).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$tokenId, $plainToken] = explode('|', $bearer, 2);
|
||||||
|
$tokenId = (int)$tokenId;
|
||||||
|
|
||||||
|
if ($tokenId <= 0 || $plainToken === '') {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid token).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- IMPORTANT: your table name ---
|
||||||
|
$tokensTable = 'auth_personal_access_tokens';
|
||||||
|
|
||||||
|
// Lookup token by id
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, tokenable_id, token, expires_at
|
||||||
|
FROM {$tokensTable}
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$tokenId]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$row) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (token not found).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional expiry check
|
||||||
|
if (!empty($row['expires_at'])) {
|
||||||
|
$now = new DateTimeImmutable('now', new DateTimeZone('Europe/Rome'));
|
||||||
|
$exp = new DateTimeImmutable($row['expires_at'], new DateTimeZone('Europe/Rome'));
|
||||||
|
if ($exp <= $now) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (token expired).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanctum stores SHA-256 hash of the plain token
|
||||||
|
$hash = hash('sha256', $plainToken);
|
||||||
|
if (!hash_equals((string)$row['token'], $hash)) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (token mismatch).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iduserlogin = (int)$row['tokenable_id'];
|
||||||
|
if ($iduserlogin <= 0) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized (invalid user).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last_used_at (best effort)
|
||||||
|
try {
|
||||||
|
$pdo->prepare("UPDATE {$tokensTable} SET last_used_at = NOW() WHERE id = ?")->execute([$tokenId]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||||||
|
|
||||||
|
$cert_id = 0;
|
||||||
|
if ($method === 'DELETE') {
|
||||||
|
$cert_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||||
|
} else {
|
||||||
|
// POST fallback
|
||||||
|
$cert_id = isset($_POST['cert_id']) ? (int)$_POST['cert_id'] : (isset($_GET['id']) ? (int)$_GET['id'] : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($cert_id <= 0) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Missing certificate id (id or cert_id).']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cert and ensure ownership
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, stored_path
|
||||||
|
FROM user_medical_certificates
|
||||||
|
WHERE id = ? AND user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$cert_id, $iduserlogin]);
|
||||||
|
$cert = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$cert) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Certificate not found.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stored_path like: userarea/certificate/xxx
|
||||||
|
$stored = (string)$cert['stored_path'];
|
||||||
|
$publicRoot = realpath(__DIR__ . '/../../'); // points to /public
|
||||||
|
$fullPath = $publicRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, ltrim($stored, '/\\'));
|
||||||
|
|
||||||
|
if (is_file($fullPath)) {
|
||||||
|
@unlink($fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
$del = $pdo->prepare("DELETE FROM user_medical_certificates WHERE id = ? AND user_id = ?");
|
||||||
|
$del->execute([$cert_id, $iduserlogin]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'deleted_id' => $cert_id], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server error.', 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Optional: school_id filter if you later add it to the table
|
||||||
|
// $school_id = isset($_GET['school_id']) ? (int)$_GET['school_id'] : 0;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, filename, stored_path, document_name, expiry_date, uploaded_at, notes
|
||||||
|
FROM user_medical_certificates
|
||||||
|
WHERE user_id = ?
|
||||||
|
ORDER BY uploaded_at DESC
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$now = new DateTimeImmutable('now', new DateTimeZone('Europe/Rome'));
|
||||||
|
|
||||||
|
$certs = array_map(function (array $c) use ($now): array {
|
||||||
|
$expiry = !empty($c['expiry_date']) ? new DateTimeImmutable($c['expiry_date']) : null;
|
||||||
|
$isExpired = $expiry ? ($expiry < $now->setTime(0, 0)) : false;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => (int)$c['id'],
|
||||||
|
'document_name' => (string)($c['document_name'] ?? ''),
|
||||||
|
'filename' => (string)($c['filename'] ?? ''),
|
||||||
|
'stored_path' => (string)($c['stored_path'] ?? ''),
|
||||||
|
'file_url' => '/' . ltrim((string)($c['stored_path'] ?? ''), '/'),
|
||||||
|
'uploaded_at' => $c['uploaded_at'],
|
||||||
|
'expiry_date' => $c['expiry_date'],
|
||||||
|
'is_expired' => $isExpired,
|
||||||
|
'notes' => $c['notes'] ?? null,
|
||||||
|
];
|
||||||
|
}, $rows);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'count' => count($certs),
|
||||||
|
'certificates' => $certs
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server error.', 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Method not allowed. Use POST.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($_FILES['certificate']) || ($_FILES['certificate']['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Missing file field: certificate']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $_FILES['certificate'];
|
||||||
|
|
||||||
|
$document_name = trim((string)($_POST['document_name'] ?? 'certificato'));
|
||||||
|
if ($document_name === '') $document_name = 'certificato';
|
||||||
|
|
||||||
|
$expiry_date = (string)($_POST['expiry_date'] ?? '');
|
||||||
|
if ($expiry_date === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $expiry_date)) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'expiry_date is required (YYYY-MM-DD)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notes = trim((string)($_POST['notes'] ?? ''));
|
||||||
|
|
||||||
|
// Allowed extensions
|
||||||
|
$allowed_ext = ['jpg', 'jpeg', 'png', 'pdf', 'heic', 'heif'];
|
||||||
|
$ext = strtolower(pathinfo((string)$file['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed_ext, true)) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unsupported format. Allowed: jpg, jpeg, png, pdf, heic, heif']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int)$file['size'] > 10 * 1024 * 1024) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'File too large (max 10MB)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload dir: ../certificate relative to /public/userarea/api
|
||||||
|
$upload_dir = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'certificate' . DIRECTORY_SEPARATOR;
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$safe_name = preg_replace('/[^a-zA-Z0-9\._-]/', '_', basename((string)$file['name']));
|
||||||
|
$new_filename = $iduserlogin . '-' . time() . '-' . $safe_name;
|
||||||
|
|
||||||
|
$destination = $upload_dir . $new_filename;
|
||||||
|
|
||||||
|
if (!move_uploaded_file((string)$file['tmp_name'], $destination)) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error saving the file.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stored_path = 'userarea/certificate/' . $new_filename;
|
||||||
|
|
||||||
|
// OPTIONAL: if you later add school_id column, include it here too.
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_medical_certificates
|
||||||
|
(user_id, filename, stored_path, document_name, expiry_date, notes, uploaded_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$iduserlogin,
|
||||||
|
(string)$file['name'],
|
||||||
|
$stored_path,
|
||||||
|
$document_name,
|
||||||
|
$expiry_date,
|
||||||
|
$notes
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'certificate' => [
|
||||||
|
'id' => $newId,
|
||||||
|
'document_name' => $document_name,
|
||||||
|
'filename' => (string)$file['name'],
|
||||||
|
'stored_path' => $stored_path,
|
||||||
|
'file_url' => '/' . $stored_path,
|
||||||
|
'expiry_date' => $expiry_date,
|
||||||
|
'notes' => $notes !== '' ? $notes : null
|
||||||
|
]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server error.', 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
// public/userarea/api/my_lessons.php
|
||||||
|
// GET ?school_id=3&month=2025-12
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // gives $pdo, $iduserlogin, $authUser
|
||||||
|
|
||||||
|
try {
|
||||||
|
$school_id = isset($_GET['school_id']) ? (int)$_GET['school_id'] : 0;
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Missing required parameter: school_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$month = $_GET['month'] ?? (new DateTimeImmutable('now', new DateTimeZone('Europe/Rome')))->format('Y-m');
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}$/', (string)$month)) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Invalid month format. Use YYYY-MM.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tz = new DateTimeZone('Europe/Rome');
|
||||||
|
$monthDate = DateTimeImmutable::createFromFormat('Y-m', $month, $tz) ?: new DateTimeImmutable('now', $tz);
|
||||||
|
|
||||||
|
$startOfMonth = $monthDate->format('Y-m-01'); // inclusive
|
||||||
|
$endOfMonth = $monthDate->modify('first day of next month')->format('Y-m-d'); // exclusive
|
||||||
|
$prevMonth = $monthDate->modify('-1 month')->format('Y-m');
|
||||||
|
$nextMonth = $monthDate->modify('+1 month')->format('Y-m');
|
||||||
|
|
||||||
|
// --- Authorization: user must belong to that school ---
|
||||||
|
// Adjust if your authorization logic differs.
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT 1
|
||||||
|
FROM user_schools
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND school_id = ?
|
||||||
|
AND status = 'active'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin, $school_id]);
|
||||||
|
if (!$stmt->fetchColumn()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Forbidden: user not allowed for this school.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- School info (same as your page) ---
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name, address_street, address_city, address_province
|
||||||
|
FROM schools
|
||||||
|
WHERE id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$school) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'School not found.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main query (ported from your PHP page) ---
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
sb.id as booking_id,
|
||||||
|
sb.status,
|
||||||
|
sb.order_id,
|
||||||
|
cs.id as session_id,
|
||||||
|
cs.session_date,
|
||||||
|
cs.start_time,
|
||||||
|
cs.end_time,
|
||||||
|
cs.room_name,
|
||||||
|
c.name as class_name,
|
||||||
|
ct.level,
|
||||||
|
o.available_entries,
|
||||||
|
o.available_recoveries
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
JOIN orders o ON sb.order_id = o.id
|
||||||
|
WHERE sb.user_id = ?
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND cs.session_date >= ?
|
||||||
|
AND cs.session_date < ?
|
||||||
|
ORDER BY cs.session_date ASC, cs.start_time ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Better 24h rule: compare against actual lesson start datetime
|
||||||
|
$now = new DateTimeImmutable('now', $tz);
|
||||||
|
$limit = $now->modify('+24 hours');
|
||||||
|
|
||||||
|
$lessons = [];
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
$startDt = DateTimeImmutable::createFromFormat(
|
||||||
|
'Y-m-d H:i:s',
|
||||||
|
$r['session_date'] . ' ' . $r['start_time'],
|
||||||
|
$tz
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$startDt) {
|
||||||
|
// fallback if start_time is H:i
|
||||||
|
$startDt = DateTimeImmutable::createFromFormat(
|
||||||
|
'Y-m-d H:i',
|
||||||
|
$r['session_date'] . ' ' . substr((string)$r['start_time'], 0, 5),
|
||||||
|
$tz
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$canModify = false;
|
||||||
|
if ($r['status'] === 'booked' && $startDt instanceof DateTimeImmutable) {
|
||||||
|
$canModify = ($startDt > $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lessons[] = [
|
||||||
|
'booking_id' => (int)$r['booking_id'],
|
||||||
|
'status' => (string)$r['status'],
|
||||||
|
'order_id' => (int)$r['order_id'],
|
||||||
|
|
||||||
|
'session' => [
|
||||||
|
'session_id' => (int)$r['session_id'],
|
||||||
|
'date' => (string)$r['session_date'],
|
||||||
|
'start_time' => (string)$r['start_time'],
|
||||||
|
'end_time' => (string)$r['end_time'],
|
||||||
|
'room_name' => $r['room_name'] !== null ? (string)$r['room_name'] : null,
|
||||||
|
'start_datetime_iso' => $startDt ? $startDt->format(DateTimeInterface::ATOM) : null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'class' => [
|
||||||
|
'name' => (string)$r['class_name'],
|
||||||
|
'level' => ($r['level'] ?? null) ? (string)$r['level'] : null,
|
||||||
|
],
|
||||||
|
|
||||||
|
'wallet' => [
|
||||||
|
'available_entries' => (int)($r['available_entries'] ?? 0),
|
||||||
|
'available_recoveries' => (int)($r['available_recoveries'] ?? 0),
|
||||||
|
],
|
||||||
|
|
||||||
|
'can_reschedule' => $canModify,
|
||||||
|
'can_cancel' => $canModify,
|
||||||
|
'can_modify' => $canModify,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'month' => $monthDate->format('Y-m'),
|
||||||
|
'prev_month' => $prevMonth,
|
||||||
|
'next_month' => $nextMonth,
|
||||||
|
'school' => [
|
||||||
|
'id' => (int)$school['id'],
|
||||||
|
'name' => (string)$school['name'],
|
||||||
|
'address_street' => $school['address_street'] ?? null,
|
||||||
|
'address_city' => $school['address_city'] ?? null,
|
||||||
|
'address_province' => $school['address_province'] ?? null,
|
||||||
|
'address_full' => trim(
|
||||||
|
($school['address_street'] ?? '') . ', ' .
|
||||||
|
($school['address_city'] ?? '') . ' ' .
|
||||||
|
($school['address_province'] ?? '')
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'lessons' => $lessons
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
$school_id = isset($_GET['school_id']) ? (int)$_GET['school_id'] : 0;
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Missing school_id']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Security: user must be active in this school ---
|
||||||
|
$chk = $pdo->prepare("
|
||||||
|
SELECT 1
|
||||||
|
FROM user_schools us
|
||||||
|
JOIN schools s ON s.id = us.school_id
|
||||||
|
WHERE us.user_id = ?
|
||||||
|
AND us.school_id = ?
|
||||||
|
AND us.status = 'active'
|
||||||
|
AND s.status = 'active'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$chk->execute([$iduserlogin, $school_id]);
|
||||||
|
|
||||||
|
if (!$chk->fetchColumn()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Forbidden: user not allowed for this school']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Defaults (same as your include) ---
|
||||||
|
$defaults = [
|
||||||
|
'portal_purchases_enabled' => 1,
|
||||||
|
'allowed_product_types' => 'subscription,carnet,drop_in',
|
||||||
|
'payment_methods' => 'manual',
|
||||||
|
'currency_code' => 'EUR',
|
||||||
|
'enable_notifications' => 1,
|
||||||
|
'allow_freeze_global' => 1,
|
||||||
|
'freeze_max_days_global' => 30,
|
||||||
|
'auto_propagate_on_purchase' => 1,
|
||||||
|
'allow_full_access_rebooking' => 1,
|
||||||
|
// Add here any other defaults you want to guarantee
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Load settings row ---
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM school_settings
|
||||||
|
WHERE school_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$settings) {
|
||||||
|
// Create row with defaults (only school_id is required by your schema)
|
||||||
|
$ins = $pdo->prepare("INSERT INTO school_settings (school_id) VALUES (?)");
|
||||||
|
$ins->execute([$school_id]);
|
||||||
|
|
||||||
|
// Reload
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM school_settings WHERE school_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge defaults (fallback for NULL / missing fields)
|
||||||
|
$schoolSettings = array_merge($defaults, $settings);
|
||||||
|
|
||||||
|
// Ensure arrays
|
||||||
|
$paymentMethods = array_values(array_filter(array_map('trim', explode(',', (string)($schoolSettings['payment_methods'] ?? '')))));
|
||||||
|
$productTypes = array_values(array_filter(array_map('trim', explode(',', (string)($schoolSettings['allowed_product_types'] ?? '')))));
|
||||||
|
|
||||||
|
$schoolSettings['payment_methods_array'] = $paymentMethods;
|
||||||
|
$schoolSettings['allowed_product_types_array'] = $productTypes;
|
||||||
|
|
||||||
|
// Optional: cast some known int flags to int (helps Flutter)
|
||||||
|
foreach (
|
||||||
|
[
|
||||||
|
'portal_purchases_enabled',
|
||||||
|
'enable_notifications',
|
||||||
|
'allow_freeze_global',
|
||||||
|
'freeze_max_days_global',
|
||||||
|
'auto_propagate_on_purchase',
|
||||||
|
'allow_full_access_rebooking'
|
||||||
|
] as $k
|
||||||
|
) {
|
||||||
|
if (isset($schoolSettings[$k])) {
|
||||||
|
$schoolSettings[$k] = is_numeric($schoolSettings[$k]) ? (int)$schoolSettings[$k] : $schoolSettings[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'school_id' => $school_id,
|
||||||
|
'settings' => $schoolSettings
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
// public/userarea/api/api_user_schools.php
|
||||||
|
// Returns ONLY the schools where the authenticated user is enrolled.
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // provides $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Optional user info for greeting
|
||||||
|
$stmt = $pdo->prepare("SELECT first_name, avatar FROM auth_users WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
|
||||||
|
// User schools only
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.name,
|
||||||
|
s.logo,
|
||||||
|
s.address_street,
|
||||||
|
s.address_postal_code,
|
||||||
|
s.address_city,
|
||||||
|
s.address_province,
|
||||||
|
s.address_country
|
||||||
|
FROM user_schools us
|
||||||
|
JOIN schools s ON us.school_id = s.id
|
||||||
|
WHERE us.user_id = ?
|
||||||
|
AND us.status = 'active'
|
||||||
|
AND s.status = 'active'
|
||||||
|
ORDER BY s.name
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$schools = array_map(function (array $s): array {
|
||||||
|
$street = trim((string)($s['address_street'] ?? ''));
|
||||||
|
$zip = trim((string)($s['address_postal_code'] ?? ''));
|
||||||
|
$city = trim((string)($s['address_city'] ?? ''));
|
||||||
|
$prov = trim((string)($s['address_province'] ?? ''));
|
||||||
|
$cntry = trim((string)($s['address_country'] ?? ''));
|
||||||
|
|
||||||
|
$line2Parts = [];
|
||||||
|
if ($zip !== '') $line2Parts[] = $zip;
|
||||||
|
if ($city !== '') $line2Parts[] = $city;
|
||||||
|
|
||||||
|
$line2 = implode(' ', $line2Parts);
|
||||||
|
if ($prov !== '') $line2 .= ' (' . $prov . ')';
|
||||||
|
if ($cntry !== '') $line2 .= ' - ' . $cntry;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => (int)$s['id'],
|
||||||
|
'name' => (string)($s['name'] ?? ''),
|
||||||
|
'logo' => ($s['logo'] ?? null) !== null && trim((string)$s['logo']) !== '' ? (string)$s['logo'] : null,
|
||||||
|
'address_street' => $street !== '' ? $street : null,
|
||||||
|
'address_postal_code' => $zip !== '' ? $zip : null,
|
||||||
|
'address_city' => $city !== '' ? $city : null,
|
||||||
|
'address_province' => $prov !== '' ? $prov : null,
|
||||||
|
'address_country' => $cntry !== '' ? $cntry : null,
|
||||||
|
'address_full' => trim($street . ($street && $line2 ? ', ' : '') . $line2),
|
||||||
|
];
|
||||||
|
}, $rows);
|
||||||
|
|
||||||
|
$autoSelect = (count($schools) === 1);
|
||||||
|
$selectedSchoolId = $autoSelect ? (int)$schools[0]['id'] : null;
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'auto_select' => $autoSelect,
|
||||||
|
'selected_school_id' => $selectedSchoolId,
|
||||||
|
'user' => [
|
||||||
|
'id' => $iduserlogin,
|
||||||
|
'first_name' => $user['first_name'] ?? null,
|
||||||
|
'avatar' => $user['avatar'] ?? null,
|
||||||
|
],
|
||||||
|
'schools' => $schools,
|
||||||
|
'message' => empty($schools)
|
||||||
|
? 'No active schools assigned to this user. Please contact your school.'
|
||||||
|
: null
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error.',
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
$user_id = (int)$iduserlogin;
|
||||||
|
if ($user_id <= 0) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaults = [
|
||||||
|
'notify_email' => 1,
|
||||||
|
'notify_whatsapp' => 0,
|
||||||
|
'notify_push' => 0,
|
||||||
|
'notify_booking_confirm' => 1,
|
||||||
|
'notify_booking_cancel' => 1,
|
||||||
|
'notify_session_cancel' => 1,
|
||||||
|
'notify_payment_receipt' => 1,
|
||||||
|
'notify_expiration_reminder' => 1,
|
||||||
|
'newsletter_opt_in' => 0,
|
||||||
|
'marketing_opt_in' => 0,
|
||||||
|
'locale' => 'it',
|
||||||
|
'timezone' => 'Europe/Rome',
|
||||||
|
];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM user_settings
|
||||||
|
WHERE user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$settings) {
|
||||||
|
// Create row with defaults (user_id only required by your schema)
|
||||||
|
$ins = $pdo->prepare("INSERT INTO user_settings (user_id) VALUES (?)");
|
||||||
|
$ins->execute([$user_id]);
|
||||||
|
|
||||||
|
// Reload
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$userSettings = array_merge($defaults, $settings);
|
||||||
|
|
||||||
|
// Cast numeric flags to int for Flutter
|
||||||
|
foreach (
|
||||||
|
[
|
||||||
|
'notify_email',
|
||||||
|
'notify_whatsapp',
|
||||||
|
'notify_push',
|
||||||
|
'notify_booking_confirm',
|
||||||
|
'notify_booking_cancel',
|
||||||
|
'notify_session_cancel',
|
||||||
|
'notify_payment_receipt',
|
||||||
|
'notify_expiration_reminder',
|
||||||
|
'newsletter_opt_in',
|
||||||
|
'marketing_opt_in',
|
||||||
|
] as $k
|
||||||
|
) {
|
||||||
|
if (isset($userSettings[$k])) {
|
||||||
|
$userSettings[$k] = is_numeric($userSettings[$k]) ? (int)$userSettings[$k] : $userSettings[$k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'settings' => $userSettings
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server error.', 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/_bootstrap.php'; // $pdo, $iduserlogin
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (strtoupper($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
||||||
|
http_response_code(405);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Method not allowed. Use POST.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_id = (int)$iduserlogin;
|
||||||
|
|
||||||
|
$raw = file_get_contents('php://input');
|
||||||
|
$data = json_decode($raw ?: '', true);
|
||||||
|
if (!is_array($data)) $data = $_POST;
|
||||||
|
|
||||||
|
// Whitelist fields you allow to be updated
|
||||||
|
$allowed = [
|
||||||
|
'notify_email',
|
||||||
|
'notify_whatsapp',
|
||||||
|
'notify_push',
|
||||||
|
'notify_booking_confirm',
|
||||||
|
'notify_booking_cancel',
|
||||||
|
'notify_session_cancel',
|
||||||
|
'notify_payment_receipt',
|
||||||
|
'notify_expiration_reminder',
|
||||||
|
'newsletter_opt_in',
|
||||||
|
'marketing_opt_in',
|
||||||
|
'locale',
|
||||||
|
'timezone',
|
||||||
|
];
|
||||||
|
|
||||||
|
$updates = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($allowed as $field) {
|
||||||
|
if (array_key_exists($field, $data)) {
|
||||||
|
$val = $data[$field];
|
||||||
|
|
||||||
|
// Normalize booleans/numbers for tinyint fields
|
||||||
|
if (is_bool($val)) $val = $val ? 1 : 0;
|
||||||
|
|
||||||
|
$updates[] = "{$field} = ?";
|
||||||
|
$params[] = $val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($updates)) {
|
||||||
|
http_response_code(422);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'No valid fields to update.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure row exists
|
||||||
|
$pdo->prepare("INSERT IGNORE INTO user_settings (user_id) VALUES (?)")->execute([$user_id]);
|
||||||
|
|
||||||
|
$params[] = $user_id;
|
||||||
|
|
||||||
|
$sql = "UPDATE user_settings SET " . implode(', ', $updates) . " WHERE user_id = ?";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Server error.', 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['iduserlogin'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Non autorizzato']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$booking_id = (int)($_POST['booking_id'] ?? 0);
|
||||||
|
$user_id = (int)$_SESSION['iduserlogin'];
|
||||||
|
|
||||||
|
if ($booking_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'ID prenotazione non valido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica che la prenotazione appartenga all'utente e sia cancellabile
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT sb.id
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.id = ?
|
||||||
|
AND sb.user_id = ?
|
||||||
|
AND sb.status = 'booked'
|
||||||
|
AND cs.session_date > DATE_ADD(NOW(), INTERVAL 24 HOUR)
|
||||||
|
");
|
||||||
|
$stmt->execute([$booking_id, $user_id]);
|
||||||
|
if (!$stmt->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Prenotazione non trovata, non tua, già annullata o non più cancellabile (entro 24 ore)']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiorna status a 'cancelled'
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE session_bookings
|
||||||
|
SET status = 'cancelled',
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$success = $stmt->execute([$booking_id]);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => $success,
|
||||||
|
'message' => $success ? 'Prenotazione annullata con successo' : 'Errore durante l\'aggiornamento'
|
||||||
|
]);
|
||||||
@@ -12,16 +12,24 @@ $dotenv->load();
|
|||||||
|
|
||||||
function sendEmail($to, $subject, $body, $attachments = [], $cc = [], $bcc = [])
|
function sendEmail($to, $subject, $body, $attachments = [], $cc = [], $bcc = [])
|
||||||
{
|
{
|
||||||
|
|
||||||
// Configurazione SMTP
|
// Configurazione SMTP
|
||||||
$mail = new PHPMailer(true);
|
$mail = new PHPMailer(true);
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// Configurazione server SMTP con dati da .env
|
// Configurazione server SMTP con dati da .env
|
||||||
$mail->isSMTP();
|
$mail->isSMTP();
|
||||||
$mail->Host = $_ENV['MAIL_HOST'] ?? 'smtp.example.com';
|
$mail->Host = $_ENV['MAIL_HOST'] ?? 'smtp.example.com';
|
||||||
$mail->SMTPAuth = true;
|
$mail->SMTPAuth = true;
|
||||||
$mail->Username = $_ENV['MAIL_USERNAME'] ?? 'email@example.com';
|
$mail->Username = $_ENV['MAIL_USERNAME'] ?? 'email@example.com';
|
||||||
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? 'password';
|
$mail->Password = $_ENV['MAIL_PASSWORD'] ?? 'password';
|
||||||
$mail->SMTPSecure = $_ENV['MAIL_ENCRYPTION'] ?? PHPMailer::ENCRYPTION_STARTTLS;
|
$enc = strtolower(trim($_ENV['MAIL_ENCRYPTION'] ?? 'tls'));
|
||||||
|
if ($enc === 'ssl') {
|
||||||
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||||
|
} else {
|
||||||
|
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||||
|
}
|
||||||
|
|
||||||
$mail->Port = $_ENV['MAIL_PORT'] ?? 587;
|
$mail->Port = $_ENV['MAIL_PORT'] ?? 587;
|
||||||
|
|
||||||
// Mittente
|
// Mittente
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) die("Errore: utente non loggato.");
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
/* 1) Scuola */
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([(int)$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$school) die("Scuola non trovata.");
|
||||||
|
$school_id = (int)$school['id'];
|
||||||
|
|
||||||
|
/* 2) class_type_id da GET */
|
||||||
|
$class_type_id = isset($_GET['class_type_id']) ? (int)$_GET['class_type_id'] : 0;
|
||||||
|
if ($class_type_id <= 0) die("Parametro class_type_id mancante.");
|
||||||
|
|
||||||
|
/* 3) Verifica che la variazione appartenga alla scuola + dati per header */
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ct.id, ct.class_id, ct.level, ct.day_of_week, ct.start_time, ct.room_name, ct.status,
|
||||||
|
c.name AS class_name
|
||||||
|
FROM class_types ct
|
||||||
|
JOIN classes c ON c.id = ct.class_id
|
||||||
|
WHERE ct.id = ? AND c.school_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$class_type_id, $school_id]);
|
||||||
|
$base = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$base) die("Variazione non trovata o non autorizzata.");
|
||||||
|
|
||||||
|
/* 4) Legami già esistenti (per spuntare checkbox) */
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN class_type_id = ? THEN linked_class_type_id
|
||||||
|
ELSE class_type_id
|
||||||
|
END AS other_id
|
||||||
|
FROM class_type_links
|
||||||
|
WHERE school_id = ?
|
||||||
|
AND (class_type_id = ? OR linked_class_type_id = ?)
|
||||||
|
");
|
||||||
|
$stmt->execute([$class_type_id, $school_id, $class_type_id, $class_type_id]);
|
||||||
|
$linkedIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
$linkedMap = array_flip($linkedIds);
|
||||||
|
|
||||||
|
$feedback = "";
|
||||||
|
|
||||||
|
/* 5) Salvataggio */
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_links') {
|
||||||
|
$selected = $_POST['links'] ?? [];
|
||||||
|
$selected_ids = array_values(array_unique(array_map('intval', $selected)));
|
||||||
|
|
||||||
|
// rimuovo l'eventuale "se stesso"
|
||||||
|
$selected_ids = array_values(array_filter($selected_ids, fn($x) => $x > 0 && $x !== $class_type_id));
|
||||||
|
|
||||||
|
// Validazione: tutti devono essere della stessa scuola
|
||||||
|
if (!empty($selected_ids)) {
|
||||||
|
$in = implode(',', array_fill(0, count($selected_ids), '?'));
|
||||||
|
$params = $selected_ids;
|
||||||
|
$params[] = $school_id;
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM class_types ct
|
||||||
|
JOIN classes c ON c.id = ct.class_id
|
||||||
|
WHERE ct.id IN ($in) AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute($params);
|
||||||
|
$cnt_ok = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($cnt_ok !== count($selected_ids)) {
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
Selezione non valida: contiene variazioni non appartenenti alla scuola.
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($feedback === "") {
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// 1) cancello tutti i link dove compare questa variazione (da entrambi i lati)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
DELETE FROM class_type_links
|
||||||
|
WHERE school_id = ?
|
||||||
|
AND (class_type_id = ? OR linked_class_type_id = ?)
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id, $class_type_id, $class_type_id]);
|
||||||
|
|
||||||
|
// 2) inserisco i nuovi (coppia canonica: min/max)
|
||||||
|
if (!empty($selected_ids)) {
|
||||||
|
$ins = $pdo->prepare("
|
||||||
|
INSERT IGNORE INTO class_type_links (school_id, class_type_id, linked_class_type_id)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
foreach ($selected_ids as $other) {
|
||||||
|
$a = min($class_type_id, $other);
|
||||||
|
$b = max($class_type_id, $other);
|
||||||
|
$ins->execute([$school_id, $a, $b]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$feedback = '<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
Associazioni salvate!
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
// ricarico per checkbox
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN class_type_id = ? THEN linked_class_type_id
|
||||||
|
ELSE class_type_id
|
||||||
|
END AS other_id
|
||||||
|
FROM class_type_links
|
||||||
|
WHERE school_id = ?
|
||||||
|
AND (class_type_id = ? OR linked_class_type_id = ?)
|
||||||
|
");
|
||||||
|
$stmt->execute([$class_type_id, $school_id, $class_type_id, $class_type_id]);
|
||||||
|
$linkedIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||||
|
$linkedMap = array_flip($linkedIds);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
Errore salvataggio: ' . htmlspecialchars($e->getMessage()) . '
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 6) Lista di TUTTE le altre variazioni della scuola */
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ct.id,
|
||||||
|
c.name AS class_name,
|
||||||
|
ct.level,
|
||||||
|
ct.day_of_week,
|
||||||
|
ct.start_time,
|
||||||
|
ct.room_name,
|
||||||
|
ct.status
|
||||||
|
FROM class_types ct
|
||||||
|
JOIN classes c ON c.id = ct.class_id
|
||||||
|
WHERE c.school_id = ?
|
||||||
|
AND ct.id <> ?
|
||||||
|
AND ct.status = 'active'
|
||||||
|
ORDER BY c.name, ct.level, ct.day_of_week, ct.start_time
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id, $class_type_id]);
|
||||||
|
$all = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
function fmtLevel($lvl)
|
||||||
|
{
|
||||||
|
return $lvl ? ucfirst($lvl) : '-';
|
||||||
|
}
|
||||||
|
function fmtDay($d)
|
||||||
|
{
|
||||||
|
return $d ? ucfirst($d) : '-';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Associa variazioni - <?php echo htmlspecialchars($school['name']); ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
<style>
|
||||||
|
.sticky-tools {
|
||||||
|
position: sticky;
|
||||||
|
top: 10px;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-muted {
|
||||||
|
opacity: .75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="container-fluid px-1">
|
||||||
|
|
||||||
|
<div class="card radius-15 shadow mb-3">
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Associazioni per riprogrammazione</h5>
|
||||||
|
<small class="opacity-75">
|
||||||
|
<?php echo htmlspecialchars($base['class_name']); ?> —
|
||||||
|
<?php echo fmtLevel($base['level']); ?> —
|
||||||
|
<?php echo fmtDay($base['day_of_week']); ?> <?php echo substr($base['start_time'], 0, 5); ?>
|
||||||
|
<?php if (!empty($base['room_name'])) echo " — " . htmlspecialchars($base['room_name']); ?>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="school_dashboard.php" class="btn btn-light btn-sm">Dashboard</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php echo $feedback; ?>
|
||||||
|
|
||||||
|
<div class="card radius-15 shadow">
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<div class="sticky-tools bg-white pb-2">
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input id="searchBox" type="text" class="form-control" placeholder="Cerca (classe / livello / giorno / ora / sala)...">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-md-end">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnSelectAll">Seleziona tutte</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" id="btnClearAll">Deseleziona tutte</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="save_links">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover align-middle" id="assocTable">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th style="width:70px;">Link</th>
|
||||||
|
<th>Classe</th>
|
||||||
|
<th>Livello</th>
|
||||||
|
<th>Giorno</th>
|
||||||
|
<th>Ora</th>
|
||||||
|
<th>Sala</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($all)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center text-muted py-4">Nessuna altra variazione disponibile.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($all as $r): ?>
|
||||||
|
<?php
|
||||||
|
$id = (int)$r['id'];
|
||||||
|
$checked = isset($linkedMap[$id]) ? 'checked' : '';
|
||||||
|
$text = strtolower(
|
||||||
|
($r['class_name'] ?? '') . ' ' .
|
||||||
|
($r['level'] ?? '') . ' ' .
|
||||||
|
($r['day_of_week'] ?? '') . ' ' .
|
||||||
|
($r['start_time'] ?? '') . ' ' .
|
||||||
|
($r['room_name'] ?? '')
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<tr data-search="<?php echo htmlspecialchars($text); ?>">
|
||||||
|
<td class="text-center">
|
||||||
|
<input class="form-check-input linkCheck" type="checkbox" name="links[]"
|
||||||
|
value="<?php echo $id; ?>" <?php echo $checked; ?>>
|
||||||
|
</td>
|
||||||
|
<td><strong><?php echo htmlspecialchars($r['class_name']); ?></strong></td>
|
||||||
|
<td><?php echo fmtLevel($r['level']); ?></td>
|
||||||
|
<td><?php echo fmtDay($r['day_of_week']); ?></td>
|
||||||
|
<td><?php echo substr($r['start_time'], 0, 5); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($r['room_name'] ?: '—'); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end gap-2 mt-3">
|
||||||
|
<a href="javascript:history.back()" class="btn btn-secondary">Annulla</a>
|
||||||
|
<button type="submit" class="btn btn-primary">Salva associazioni</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="mt-3 text-muted small">
|
||||||
|
Nota: le associazioni sono <strong>bidirezionali</strong>. Se A è associata a B, anche B risulta associata ad A.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const searchBox = document.getElementById('searchBox');
|
||||||
|
const rows = Array.from(document.querySelectorAll('#assocTable tbody tr[data-search]'));
|
||||||
|
const checks = () => Array.from(document.querySelectorAll('.linkCheck'));
|
||||||
|
|
||||||
|
if (searchBox) {
|
||||||
|
searchBox.addEventListener('input', function() {
|
||||||
|
const q = (this.value || '').trim().toLowerCase();
|
||||||
|
rows.forEach(tr => {
|
||||||
|
const hay = tr.getAttribute('data-search') || '';
|
||||||
|
tr.style.display = hay.includes(q) ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btnSelectAll')?.addEventListener('click', function() {
|
||||||
|
checks().forEach(c => {
|
||||||
|
if (c.closest('tr')?.style.display !== 'none') c.checked = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnClearAll')?.addEventListener('click', function() {
|
||||||
|
checks().forEach(c => {
|
||||||
|
if (c.closest('tr')?.style.display !== 'none') c.checked = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
<?php
|
||||||
|
// clients_situation.php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once 'class/mailer.php'; // assumo sia incluso qui o in headscript
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: ID utente non definito.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scuola corrente
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name, email AS school_email
|
||||||
|
FROM schools
|
||||||
|
WHERE owner_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) {
|
||||||
|
die("Nessuna scuola trovata per questo proprietario.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = $school['id'];
|
||||||
|
$school_name = $school['name'];
|
||||||
|
$school_email = $school['school_email'];
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// INVIO EMAIL da modale
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'send_email_to_user') {
|
||||||
|
|
||||||
|
$user_id = (int)($_POST['user_id'] ?? 0);
|
||||||
|
$subject = trim($_POST['subject'] ?? '');
|
||||||
|
$message = trim($_POST['message'] ?? '');
|
||||||
|
|
||||||
|
if ($user_id <= 0 || empty($subject) || empty($message)) {
|
||||||
|
$error = "Dati mancanti per l'invio email.";
|
||||||
|
} else {
|
||||||
|
// Recupera email utente
|
||||||
|
$stmt = $pdo->prepare("SELECT email, first_name, last_name FROM auth_users WHERE id = ?");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$error = "Utente non trovato.";
|
||||||
|
} else {
|
||||||
|
$to = $user['email'];
|
||||||
|
$body = "
|
||||||
|
<h2>Comunicazione da {$school_name}</h2>
|
||||||
|
<p>Gentile {$user['first_name']} {$user['last_name']},</p>
|
||||||
|
<div style='margin: 20px 0; padding: 15px; border-left: 4px solid #0d6efd; background: #f8f9fa;'>
|
||||||
|
" . nl2br(htmlspecialchars($message)) . "
|
||||||
|
</div>
|
||||||
|
<p style='color:#555; font-size:0.95em;'>
|
||||||
|
Questa è una comunicazione ufficiale da parte della scuola.<br>
|
||||||
|
Per qualsiasi dubbio rispondi direttamente a questa email o contatta: {$school_email}
|
||||||
|
</p>
|
||||||
|
<hr style='border-color:#eee;'>
|
||||||
|
<small style='color:#777;'>YogiBoook – piattaforma per scuole yoga</small>
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = sendEmail($to, $subject, $body);
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
$success = "Email inviata con successo a {$user['first_name']} {$user['last_name']}";
|
||||||
|
} else {
|
||||||
|
$error = "Errore nell'invio: " . $result['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// Lista clienti + statistiche aggregate
|
||||||
|
$clients = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
au.id,
|
||||||
|
au.first_name,
|
||||||
|
au.last_name,
|
||||||
|
au.email,
|
||||||
|
COUNT(DISTINCT o.id) AS num_orders,
|
||||||
|
COALESCE(SUM(o.total_entries), 0) AS total_entries,
|
||||||
|
|
||||||
|
-- Praticate = prenotate nel passato (booked + data < oggi)
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = au.id
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'booked'
|
||||||
|
AND cs.session_date < CURDATE()
|
||||||
|
) AS lezioni_praticate,
|
||||||
|
|
||||||
|
-- Perse (missed + data passata)
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = au.id
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'missed'
|
||||||
|
AND cs.session_date < CURDATE()
|
||||||
|
) AS lezioni_perse,
|
||||||
|
|
||||||
|
-- Prenotate future (booked + data >= oggi)
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = au.id
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'booked'
|
||||||
|
AND cs.session_date >= CURDATE()
|
||||||
|
) AS prenotazioni_future
|
||||||
|
|
||||||
|
FROM auth_users au
|
||||||
|
INNER JOIN user_schools us ON au.id = us.user_id
|
||||||
|
LEFT JOIN orders o ON au.id = o.user_id AND o.school_id = ?
|
||||||
|
WHERE us.school_id = ?
|
||||||
|
AND us.status = 'active'
|
||||||
|
GROUP BY au.id
|
||||||
|
ORDER BY au.last_name, au.first_name
|
||||||
|
");
|
||||||
|
$clients->execute([$school_id, $school_id, $school_id, $school_id, $school_id]);
|
||||||
|
$client_list = $clients->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Situazione Clienti - <?= htmlspecialchars($school_name) ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="page-breadcrumb d-none d-sm-flex align-items-center mb-3">
|
||||||
|
<div class="breadcrumb-title pe-3">Clienti</div>
|
||||||
|
<div class="ps-3">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb mb-0 p-0">
|
||||||
|
<li class="breadcrumb-item"><a href="school_dashboard.php"><i class="bx bx-home-alt"></i></a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Situazione Clienti</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="mb-4">Situazione Clienti – <?= htmlspecialchars($school_name) ?></h4>
|
||||||
|
|
||||||
|
<?php if (isset($success)): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<?= htmlspecialchars($success) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<?= htmlspecialchars($error) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-striped align-middle" id="clientsTable">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Cliente</th>
|
||||||
|
<th>Ordini</th>
|
||||||
|
<th>Entrate totali</th>
|
||||||
|
<th>Praticate</th>
|
||||||
|
<th>Perse</th>
|
||||||
|
<th>Prenotate (future)</th>
|
||||||
|
<th>Rimanenti</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($client_list as $c):
|
||||||
|
$rimanenti = $c['total_entries'] - $c['lezioni_praticate'] - $c['lezioni_perse'];
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>
|
||||||
|
<?= htmlspecialchars($c['first_name'] . ' ' . $c['last_name']) ?>
|
||||||
|
</strong>
|
||||||
|
<br>
|
||||||
|
<small class="text-muted"><?= htmlspecialchars($c['email']) ?></small>
|
||||||
|
</td>
|
||||||
|
<td class="text-center"><?= $c['num_orders'] ?></td>
|
||||||
|
<td class="text-center"><?= $c['total_entries'] ?: '—' ?></td>
|
||||||
|
<td class="text-center text-success"><?= $c['lezioni_praticate'] ?></td>
|
||||||
|
<td class="text-center text-danger"><?= $c['lezioni_perse'] ?></td>
|
||||||
|
<td class="text-center text-primary"><?= $c['prenotazioni_future'] ?></td>
|
||||||
|
<td class="text-center fw-bold <?= $rimanenti <= 0 ? 'text-danger' : '' ?>">
|
||||||
|
<?= $rimanenti > 0 ? $rimanenti : '0' ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-outline-primary me-1"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#detailModal"
|
||||||
|
data-userid="<?= $c['id'] ?>"
|
||||||
|
data-name="<?= htmlspecialchars($c['first_name'] . ' ' . $c['last_name']) ?>">
|
||||||
|
<i class="bx bx-detail"></i> Dettaglio
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-outline-info"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#emailModal"
|
||||||
|
data-userid="<?= $c['id'] ?>"
|
||||||
|
data-name="<?= htmlspecialchars($c['first_name'] . ' ' . $c['last_name']) ?>"
|
||||||
|
data-email="<?= htmlspecialchars($c['email']) ?>">
|
||||||
|
<i class="bx bx-envelope"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if (empty($client_list)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-5 text-muted">
|
||||||
|
Nessun cliente associato trovato.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALE DETTAGLIO PRENOTAZIONI -->
|
||||||
|
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="detailModalLabel">Storico prenotazioni di <span id="modalClientName"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="detailBody">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-primary" role="status"></div>
|
||||||
|
<p class="mt-2">Caricamento...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALE INVIO EMAIL -->
|
||||||
|
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="emailModalLabel">Invia comunicazione a <span id="emailClientName"></span></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="post">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="send_email_to_user">
|
||||||
|
<input type="hidden" name="user_id" id="emailUserId">
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Oggetto</label>
|
||||||
|
<input type="text" name="subject" class="form-control" value="Comunicazione da YogiBoook" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Messaggio</label>
|
||||||
|
<textarea name="message" class="form-control" rows="8" required placeholder="Scrivi qui il messaggio per il cliente..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small class="text-muted d-block">
|
||||||
|
Il messaggio verrà inviato da sistema e includerà automaticamente il nome della scuola e il tuo indirizzo email di contatto.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Invia Email</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#clientsTable').DataTable({
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.7/i18n/it-IT.json'
|
||||||
|
},
|
||||||
|
pageLength: 15,
|
||||||
|
order: [
|
||||||
|
[0, 'asc']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dettaglio cliente
|
||||||
|
$('[data-bs-target="#detailModal"]').on('click', function() {
|
||||||
|
const userid = $(this).data('userid');
|
||||||
|
const name = $(this).data('name');
|
||||||
|
|
||||||
|
$('#modalClientName').text(name);
|
||||||
|
$('#detailBody').html('<div class="text-center py-4"><div class="spinner-border text-primary" role="status"></div><p class="mt-2">Caricamento storico...</p></div>');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'ajax_client_bookings.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
user_id: userid,
|
||||||
|
school_id: <?= $school_id ?>
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$('#detailBody').html(response);
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#detailBody').html('<div class="alert alert-danger">Errore durante il caricamento dei dati.</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Precompila modale email
|
||||||
|
$('[data-bs-target="#emailModal"]').on('click', function() {
|
||||||
|
const userid = $(this).data('userid');
|
||||||
|
const name = $(this).data('name');
|
||||||
|
$('#emailUserId').val(userid);
|
||||||
|
$('#emailClientName').text(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
// public/userarea/confirm_teacher_link.php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
require_once('class/db-functions.php');
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
$success = null;
|
||||||
|
$action_taken = false;
|
||||||
|
|
||||||
|
// Parametri dalla mail / form
|
||||||
|
$email = trim($_POST['email'] ?? $_GET['email'] ?? '');
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? $_GET['school_id'] ?? 0);
|
||||||
|
$link_id = (int)($_POST['link_id'] ?? $_GET['link_id'] ?? 0);
|
||||||
|
|
||||||
|
// Validazione minima: o link_id valido, oppure email+school_id
|
||||||
|
if ($link_id <= 0) {
|
||||||
|
if (empty($email) || $school_id <= 0 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
die("Link non valido. Parametri mancanti o email errata.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera scuola (serve sempre per messaggi UI)
|
||||||
|
$school = null;
|
||||||
|
if ($school_id > 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE id = ?");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Se link_id NON c'è (vecchie mail), ricavalo dal pending usando email+school_id
|
||||||
|
if ($link_id <= 0 && $school) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT ts.id
|
||||||
|
FROM teacher_schools ts
|
||||||
|
JOIN teachers t ON ts.teacher_id = t.id
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE u.email = ?
|
||||||
|
AND ts.school_id = ?
|
||||||
|
AND ts.status = 'pending'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$email, $school_id]);
|
||||||
|
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if ($tmp) {
|
||||||
|
$link_id = (int)$tmp['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica richiesta (solo pending) tramite link_id
|
||||||
|
$request = null;
|
||||||
|
if ($link_id > 0) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
ts.id, ts.status,
|
||||||
|
u.first_name, u.last_name,
|
||||||
|
s.id AS school_id, s.name AS school_name
|
||||||
|
FROM teacher_schools ts
|
||||||
|
JOIN schools s ON ts.school_id = s.id
|
||||||
|
JOIN teachers t ON ts.teacher_id = t.id
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE ts.id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$link_id]);
|
||||||
|
$request = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request || $request['status'] !== 'pending') {
|
||||||
|
$error = "Nessuna richiesta di collegamento in attesa (potrebbe essere già stata gestita).";
|
||||||
|
} else {
|
||||||
|
// Allinea school dalla request (così non dipendi da school_id passato)
|
||||||
|
$school = ['id' => (int)$request['school_id'], 'name' => $request['school_name']];
|
||||||
|
$teacher_name = trim(($request['first_name'] ?? '') . ' ' . ($request['last_name'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST: accetta/rifiuta usando SOLO link_id
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
$link_id = (int)($_POST['link_id'] ?? 0);
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id
|
||||||
|
FROM teacher_schools
|
||||||
|
WHERE id = ? AND status = 'pending'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$link_id]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$row) {
|
||||||
|
$error = "Questa richiesta non è più disponibile (potrebbe essere già stata gestita).";
|
||||||
|
} else {
|
||||||
|
if ($_POST['action'] === 'accept') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE teacher_schools
|
||||||
|
SET status = 'active', updated_at = NOW()
|
||||||
|
WHERE id = ? AND status = 'pending'
|
||||||
|
");
|
||||||
|
$stmt->execute([$link_id]);
|
||||||
|
|
||||||
|
$success = "Collegamento accettato! Ora sei collegata alla scuola <strong>" . htmlspecialchars($school['name']) . "</strong>.";
|
||||||
|
$action_taken = true;
|
||||||
|
} elseif ($_POST['action'] === 'reject') {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM teacher_schools WHERE id = ? AND status = 'pending'");
|
||||||
|
$stmt->execute([$link_id]);
|
||||||
|
|
||||||
|
$success = "Hai rifiutato il collegamento con la scuola <strong>" . htmlspecialchars($school['name']) . "</strong>.";
|
||||||
|
$action_taken = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Conferma Collegamento Scuola - YogiBoook</title>
|
||||||
|
<?php include(__DIR__ . '/cssinclude.php'); ?>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: #f8f9fa;
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 80px auto;
|
||||||
|
padding: 40px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, .1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
padding: 14px 40px;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-big {
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="confirm-container">
|
||||||
|
<?php if ($action_taken && $success): ?>
|
||||||
|
<i class="bx bx-check-circle text-success icon-big"></i>
|
||||||
|
<h3 class="mb-4">Operazione completata!</h3>
|
||||||
|
<p class="lead mb-5"><?= $success ?></p>
|
||||||
|
<p class="mb-4 text-muted">Per gestire le lezioni di questa scuola, accedi o registrati su YogiBoook.</p>
|
||||||
|
<div class="d-flex justify-content-center gap-3">
|
||||||
|
<a href="../login.php" class="btn btn-primary btn-lg">Accedi</a>
|
||||||
|
<a href="../register.php" class="btn btn-outline-primary btn-lg">Registrati</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php elseif (!empty($error)): ?>
|
||||||
|
<i class="bx bx-error-circle text-danger icon-big"></i>
|
||||||
|
<h3>Errore</h3>
|
||||||
|
<p class="lead"><?= htmlspecialchars($error) ?></p>
|
||||||
|
<a href="../login.php" class="btn btn-secondary mt-4">Torna al sito</a>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<h3 class="mb-4">Richiesta di collegamento scuola</h3>
|
||||||
|
<p class="lead mb-4">Ciao <?= htmlspecialchars($teacher_name ?: 'insegnante') ?>,</p>
|
||||||
|
<p class="mb-5">
|
||||||
|
La scuola <strong><?= htmlspecialchars($school['name']) ?></strong> vorrebbe collegarti alla sua struttura su YogiBoook.
|
||||||
|
</p>
|
||||||
|
<p class="mb-4">Accettando, verrai visualizzata/o nelle lezioni della scuola.</p>
|
||||||
|
|
||||||
|
<form method="POST" class="d-flex justify-content-center gap-4">
|
||||||
|
<input type="hidden" name="email" value="<?= htmlspecialchars($email) ?>">
|
||||||
|
<input type="hidden" name="school_id" value="<?= (int)$school['id'] ?>">
|
||||||
|
<input type="hidden" name="link_id" value="<?= (int)$request['id'] ?>">
|
||||||
|
|
||||||
|
<button type="submit" name="action" value="accept" class="btn btn-success btn-lg">
|
||||||
|
<i class="bx bx-check me-2"></i> Accetta collegamento
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="action" value="reject" class="btn btn-outline-danger btn-lg">
|
||||||
|
<i class="bx bx-x me-2"></i> Rifiuta
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include(__DIR__ . '/include/footer.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -347,6 +347,7 @@ foreach ($sessions as $s) {
|
|||||||
<p class="mb-0 text-muted">Gestione completa: lista prenotati, P/C, cancellazione con avviso email.</p>
|
<p class="mb-0 text-muted">Gestione completa: lista prenotati, P/C, cancellazione con avviso email.</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<a href="sessions_archive.php" class="btn btn-outline-secondary">Archivio</a>
|
||||||
<a href="school_dashboard.php" class="btn btn-outline-primary">← Dashboard</a>
|
<a href="school_dashboard.php" class="btn btn-outline-primary">← Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ error_reporting(E_ALL | E_STRICT);
|
|||||||
|
|
||||||
include('../../extra/auth.php');
|
include('../../extra/auth.php');
|
||||||
|
|
||||||
if (! Auth::check()) {
|
if (!Auth::check()) {
|
||||||
redirectTo('../../public/login');
|
redirectTo('../../public/login');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
@@ -20,42 +21,65 @@ $nameuser = $user->present()->first_name;
|
|||||||
$surnameuser = $user->present()->last_name;
|
$surnameuser = $user->present()->last_name;
|
||||||
$emailuser = $user->present()->email;
|
$emailuser = $user->present()->email;
|
||||||
$avatar = $user->present()->avatar;
|
$avatar = $user->present()->avatar;
|
||||||
$kindofrole = $user->present()->role_id; // <-- Questo è il ruolo (es. 1=admin, 2=teacher, 3=student, ecc.)
|
$kindofrole = $user->present()->role_id;
|
||||||
|
$kindofrole = (int)$user->present()->role_id;
|
||||||
|
|
||||||
// --- INIZIO: Reindirizzamento intelligente per studenti senza profilo ---
|
|
||||||
if (session_status() == PHP_SESSION_NONE) {
|
// Definisci ruolo studente (conferma che sia 2!)
|
||||||
|
define('ROLE_STUDENTE', 2);
|
||||||
|
|
||||||
|
// Avvia sessione se non attiva
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definisci qui l'ID del ruolo STUDENTE (cambialo se è diverso!)
|
|
||||||
define('ROLE_STUDENTE', 2); // Cambia 3 con il ruolo corretto del tuo studente
|
|
||||||
|
|
||||||
// Escludi alcune pagine dove NON vuoi il redirect (es. login, logout, profile)
|
|
||||||
$current_page = basename($_SERVER['PHP_SELF']);
|
$current_page = basename($_SERVER['PHP_SELF']);
|
||||||
$no_redirect_pages = ['login.php', 'logout.php', 'student_profile.php', 'register.php'];
|
|
||||||
|
|
||||||
if (
|
// Pagine escluse da TUTTI i redirect/redirect automatici
|
||||||
$kindofrole == ROLE_STUDENTE &&
|
$excluded_pages = [
|
||||||
!in_array($current_page, $no_redirect_pages) &&
|
'login.php',
|
||||||
!isset($_SESSION['student_profile_completed'])
|
'logout.php',
|
||||||
) {
|
'register.php',
|
||||||
// Controlla se esiste il record in tabella students
|
'forgot-password.php', // se esiste
|
||||||
$stmt = $db->prepare("SELECT id FROM students WHERE user_id = ? LIMIT 1");
|
'student_profile.php',
|
||||||
|
'select_school.php'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// 1. CREAZIONE AUTOMATICA PROFILO STUDENTE (se manca)
|
||||||
|
// ================================================
|
||||||
|
if ($kindofrole === ROLE_STUDENTE && !in_array($current_page, $excluded_pages)) {
|
||||||
|
|
||||||
|
$stmt = $db->prepare("SELECT 1 FROM students WHERE user_id = ? LIMIT 1");
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
$student_exists = $stmt->fetch();
|
$profile_exists = $stmt->fetchColumn();
|
||||||
|
|
||||||
if (!$student_exists) {
|
if (!$profile_exists) {
|
||||||
// Non ha completato il profilo → reindirizza
|
// Crea record minimo obbligatorio
|
||||||
$_SESSION['student_profile_pending'] = true;
|
$stmt_insert = $db->prepare("
|
||||||
header("Location: student_profile.php");
|
INSERT INTO students (
|
||||||
exit;
|
user_id,
|
||||||
|
billing_country,
|
||||||
|
shipping_same_as_billing,
|
||||||
|
privacy_consent,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
) VALUES (
|
||||||
|
?, 'Italia', 1, 1, NOW(), NOW()
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$stmt_insert->execute([$iduserlogin]);
|
||||||
|
|
||||||
|
// Imposta flag per non rifare controlli inutili
|
||||||
|
$_SESSION['student_profile_completed'] = true;
|
||||||
} else {
|
} else {
|
||||||
// Ha già completato → segna per non controllare più
|
|
||||||
$_SESSION['student_profile_completed'] = true;
|
$_SESSION['student_profile_completed'] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- FINE: Reindirizzamento intelligente ---
|
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// 2. SALVA DATI UTENTE IN SESSIONE
|
||||||
|
// ================================================
|
||||||
$_SESSION["iduserlogin"] = $iduserlogin;
|
$_SESSION["iduserlogin"] = $iduserlogin;
|
||||||
$_SESSION["nameuser"] = $nameuser;
|
$_SESSION["nameuser"] = $nameuser;
|
||||||
$_SESSION["surnameuser"] = $surnameuser;
|
$_SESSION["surnameuser"] = $surnameuser;
|
||||||
@@ -63,3 +87,45 @@ $_SESSION["emailuser"] = $emailuser;
|
|||||||
$_SESSION["photouser"] = $avatar;
|
$_SESSION["photouser"] = $avatar;
|
||||||
|
|
||||||
$photouser = $_SESSION["photouser"];
|
$photouser = $_SESSION["photouser"];
|
||||||
|
|
||||||
|
if (defined('SKIP_SCHOOL_CONTEXT') && SKIP_SCHOOL_CONTEXT === true) {
|
||||||
|
return; // oppure salta SOLO i redirect scuola
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// 3. LOGICA SCUOLA (solo se profilo base esiste)
|
||||||
|
// ================================================
|
||||||
|
$has_school_association = false;
|
||||||
|
|
||||||
|
$stmt_school_check = $db->prepare("SELECT 1 FROM user_schools WHERE user_id = ? LIMIT 1");
|
||||||
|
$stmt_school_check->execute([$iduserlogin]);
|
||||||
|
$has_school_association = (bool) $stmt_school_check->fetchColumn();
|
||||||
|
|
||||||
|
if ($has_school_association) {
|
||||||
|
// include('schoolid_select.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica impostazioni scuola solo se c'è school_id valida
|
||||||
|
$pages_allow_no_school = ['school_profile.php'];
|
||||||
|
|
||||||
|
if (isset($_SESSION['school_id']) && (int)$_SESSION['school_id'] > 0) {
|
||||||
|
include('school_settings_loader.php');
|
||||||
|
} else {
|
||||||
|
// Per studenti: se non ha scuola selezionata → vai a select_school
|
||||||
|
if ($kindofrole === ROLE_STUDENTE && !in_array($current_page, $excluded_pages)) {
|
||||||
|
header("Location: select_school.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per owner/admin: vai a school_profile se non è una pagina permessa
|
||||||
|
elseif (!in_array($current_page, $pages_allow_no_school)) {
|
||||||
|
header("Location: school_profile.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default impostazioni minime
|
||||||
|
$schoolSettings = [
|
||||||
|
'timezone' => 'Europe/Rome',
|
||||||
|
'locale' => 'it',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
// Recupera logo e nome scuola corrente (da sessione)
|
||||||
|
$school_logo_path = null;
|
||||||
|
$school_display_name = 'Nessuna scuola selezionata';
|
||||||
|
|
||||||
|
if (!empty($_SESSION['school_id'])) {
|
||||||
|
$school_id = (int)$_SESSION['school_id'];
|
||||||
|
|
||||||
|
$stmt_school = $pdo->prepare("SELECT name, logo FROM schools WHERE id = ?");
|
||||||
|
$stmt_school->execute([$school_id]);
|
||||||
|
$current_school = $stmt_school->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($current_school) {
|
||||||
|
$school_display_name = $current_school['name'];
|
||||||
|
|
||||||
|
$logoRaw = trim($current_school['logo'] ?? '');
|
||||||
|
if (!empty($logoRaw)) {
|
||||||
|
$physicalPath = __DIR__ . '/../' . $logoRaw; // adatta path se necessario
|
||||||
|
if (file_exists($physicalPath)) {
|
||||||
|
$school_logo_path = '/' . $logoRaw; // path web root-relative
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?php
|
||||||
|
// Gate per mostrare logo + menu Utente
|
||||||
|
// - Admin e User: sempre
|
||||||
|
// - school_owner: solo se ha record in user_schools
|
||||||
|
$showUserArea = (Auth::user()->hasRole('Admin') || Auth::user()->hasRole('User'))
|
||||||
|
|| (Auth::user()->hasRole('school_owner') && !empty($hasUserSchools));
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.school-info {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-info img {
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.school-info .fw-bold {
|
||||||
|
color: #343a40;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<div class="sidebar-wrapper" data-simplebar="true">
|
<div class="sidebar-wrapper" data-simplebar="true">
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<div>
|
<div>
|
||||||
@@ -11,71 +61,163 @@
|
|||||||
</div>
|
</div>
|
||||||
<!--navigation-->
|
<!--navigation-->
|
||||||
<ul class="metismenu" id="menu">
|
<ul class="metismenu" id="menu">
|
||||||
<li class="menu-label">Utente</li>
|
<!-- Logo e nome scuola corrente -->
|
||||||
<li>
|
<?php if ($showUserArea): ?>
|
||||||
<a href="user_dashboard.php">
|
<div class="school-info text-center py-3 px-2 border-bottom">
|
||||||
<div class="parent-icon"><i class="bx bx-home"></i></div>
|
<?php if (!empty($logoRaw)): ?>
|
||||||
<div class="menu-title">Dashboard Utente</div>
|
<img src="<?= htmlspecialchars($logoRaw) ?>"
|
||||||
</a>
|
alt="Logo <?= htmlspecialchars($school_display_name) ?>"
|
||||||
</li>
|
class="img-fluid mb-2"
|
||||||
<li>
|
style="max-height: 80px; width: auto; object-fit: contain; border-radius: 8px; border: 1px solid #e9ecef; box-shadow: 0 2px 6px rgba(0,0,0,0.08);">
|
||||||
<a href="shop-school.php">
|
<?php else: ?>
|
||||||
<div class="parent-icon"><i class="bx bx-store"></i></div>
|
<div class="bg-light d-inline-block p-3 mb-2 rounded-3" style="width: 60px; height: 60px;">
|
||||||
<div class="menu-title">Shop</div>
|
<i class="bx bx-building-house bx-md text-muted"></i>
|
||||||
</a>
|
</div>
|
||||||
</li>
|
<?php endif; ?>
|
||||||
<li>
|
|
||||||
<a href="checkout.php">
|
|
||||||
<div class="parent-icon"><i class="bx bx-cart"></i></div>
|
|
||||||
<div class="menu-title">Carrello</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="user-settings.php">
|
|
||||||
<div class="parent-icon"><i class="bx bx-cog"></i></div>
|
|
||||||
<div class="menu-title">Impostazioni</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-label">Proprietario Scuola</li>
|
<div class="fw-bold text-truncate" style="font-size: 1rem; max-width: 180px; margin: 0 auto;">
|
||||||
<li>
|
<?= htmlspecialchars($school_display_name) ?>
|
||||||
<a href="school_dashboard.php">
|
</div>
|
||||||
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
</div>
|
||||||
<div class="menu-title">Dashboard Scuola</div>
|
<?php endif; ?>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="products.php">
|
|
||||||
<div class="parent-icon"><i class="bx bx-package"></i></div>
|
|
||||||
<div class="menu-title">Prodotti</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="school_settings.php">
|
|
||||||
<div class="parent-icon"><i class="bx bx-cog"></i></div>
|
|
||||||
<div class="menu-title">Impostazioni</div>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="menu-label">Others</li>
|
<?php
|
||||||
<li>
|
//menù user
|
||||||
<a href="emplate/index.html" target="_blank">
|
if ($showUserArea) : ?>
|
||||||
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
<li class="menu-label">Utente</li>
|
||||||
<div class="menu-title">Template</div>
|
<li>
|
||||||
</a>
|
<a href="user_dashboard.php">
|
||||||
</li>
|
<div class="parent-icon"><i class="bx bx-home"></i></div>
|
||||||
<li>
|
<div class="menu-title">Dashboard Utente</div>
|
||||||
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
</a>
|
||||||
<div class="parent-icon"><i class="bx bx-book"></i></div>
|
</li>
|
||||||
<div class="menu-title">Documentation</div>
|
<li>
|
||||||
</a>
|
<a href="my_lessons.php">
|
||||||
</li>
|
<div class="parent-icon"><i class="bx bx-store"></i></div>
|
||||||
<li>
|
<div class="menu-title">Le mie prenotazioni</div>
|
||||||
<a href="https://themeforest.net/user/codervent" target="_blank">
|
</a>
|
||||||
<div class="parent-icon"><i class="bx bx-support"></i></div>
|
</li>
|
||||||
<div class="menu-title">Support</div>
|
<li>
|
||||||
</a>
|
<a href="my_certificates.php">
|
||||||
</li>
|
<div class="parent-icon"><i class="bx bx-store"></i></div>
|
||||||
|
<div class="menu-title">Certificati</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php if (!empty($schoolSettings['portal_purchases_enabled'])): ?>
|
||||||
|
<li>
|
||||||
|
<a href="shop-school.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-store"></i></div>
|
||||||
|
<div class="menu-title">Shop</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<li>
|
||||||
|
<a href="checkout.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-cart"></i></div>
|
||||||
|
<div class="menu-title">Carrello</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="user-settings.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-cog"></i></div>
|
||||||
|
<div class="menu-title">Impostazioni</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
//menù school_owner
|
||||||
|
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?>
|
||||||
|
<li class="menu-label">Proprietario Scuola</li>
|
||||||
|
<li>
|
||||||
|
<a href="school_dashboard.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||||
|
<div class="menu-title">Dashboard Scuola</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="future_sessions.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||||
|
<div class="menu-title">Calendario Lezioni</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="products.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-package"></i></div>
|
||||||
|
<div class="menu-title">Prodotti</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="school_settings.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-cog"></i></div>
|
||||||
|
<div class="menu-title">Impostazioni</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
//menù teacher
|
||||||
|
|
||||||
|
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?>
|
||||||
|
<li class="menu-label">Insegnanti</li>
|
||||||
|
<li>
|
||||||
|
<a href="teacher_list.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||||
|
<div class="menu-title">Profilo insegnanti</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="teacher_page.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
|
||||||
|
<div class="menu-title">Il mio profilo</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
//menù admin only
|
||||||
|
if ((Auth::user()->hasRole('Admin'))) : ?>
|
||||||
|
<li class="menu-label">Subscription Area</li>
|
||||||
|
<li>
|
||||||
|
<a href="admin_subscriptions.php" target="">
|
||||||
|
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||||
|
<div class="menu-title">Subscription</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="admin_subscription_plans.php" target="">
|
||||||
|
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||||
|
<div class="menu-title">Subscription Plan</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="menu-label">Others</li>
|
||||||
|
<li>
|
||||||
|
<a href="template/index.html" target="_blank">
|
||||||
|
<div class="parent-icon"><i class="bx bx-layout"></i></div>
|
||||||
|
<div class="menu-title">Template</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://codervent.com/rocker/documentation/index.html" target="_blank">
|
||||||
|
<div class="parent-icon"><i class="bx bx-book"></i></div>
|
||||||
|
<div class="menu-title">Documentation</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://themeforest.net/user/codervent" target="_blank">
|
||||||
|
<div class="parent-icon"><i class="bx bx-support"></i></div>
|
||||||
|
<div class="menu-title">Support</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!--end navigation-->
|
<!--end navigation-->
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
// include/school_settings_loader.php
|
||||||
|
|
||||||
|
if (defined('SCHOOL_SETTINGS_LOADED')) return;
|
||||||
|
define('SCHOOL_SETTINGS_LOADED', true);
|
||||||
|
|
||||||
|
global $schoolSettings;
|
||||||
|
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
// 1) Prova a prendere school_id dalla sessione
|
||||||
|
$school_id = (int)($_SESSION['school_id'] ?? 0);
|
||||||
|
|
||||||
|
// 2) Se non c'è, prova a risolverlo dal DB via owner (utente loggato)
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
$owner_id = (int)($iduserlogin ?? $_SESSION['iduserlogin'] ?? 0);
|
||||||
|
|
||||||
|
if ($owner_id > 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM schools WHERE owner_id = ? ORDER BY id DESC LIMIT 1");
|
||||||
|
$stmt->execute([$owner_id]);
|
||||||
|
$school_id = (int)($stmt->fetchColumn() ?: 0);
|
||||||
|
|
||||||
|
if ($school_id > 0) {
|
||||||
|
$_SESSION['school_id'] = $school_id; // sincronizza sessione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default settings MINIMI (se non esiste scuola o settings)
|
||||||
|
$defaults = [
|
||||||
|
'portal_purchases_enabled' => 0,
|
||||||
|
'allowed_product_types' => 'subscription,carnet,drop_in',
|
||||||
|
'payment_methods' => 'manual',
|
||||||
|
'currency_code' => 'EUR',
|
||||||
|
'enable_notifications' => 1,
|
||||||
|
'allow_freeze_global' => 1,
|
||||||
|
'freeze_max_days_global' => 30,
|
||||||
|
'auto_propagate_on_purchase' => 1,
|
||||||
|
'allow_full_access_rebooking' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
// Nessuna scuola → default
|
||||||
|
$schoolSettings = $defaults;
|
||||||
|
} else {
|
||||||
|
// Carica settings se esistono
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM school_settings WHERE school_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$settings) {
|
||||||
|
// Se mancano, crea riga base (solo school_id) e ricarica
|
||||||
|
$stmtIns = $pdo->prepare("INSERT INTO school_settings (school_id) VALUES (?)");
|
||||||
|
$stmtIns->execute([$school_id]);
|
||||||
|
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$schoolSettings = array_merge($defaults, $settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers array
|
||||||
|
$schoolSettings['payment_methods_array'] = array_filter(array_map('trim', explode(',', $schoolSettings['payment_methods'] ?? '')));
|
||||||
|
$schoolSettings['allowed_product_types_array'] = array_filter(array_map('trim', explode(',', $schoolSettings['allowed_product_types'] ?? '')));
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
// include/schoolid_select.php
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// SKIP se siamo già su select_school.php
|
||||||
|
// ========================================
|
||||||
|
if (defined('SKIP_SCHOOL_CONTEXT')) {
|
||||||
|
return; // esce senza eseguire nulla
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('APP_BASE')) {
|
||||||
|
$base = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])), '/');
|
||||||
|
define('APP_BASE', $base === '' ? '' : $base);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check school id if user go to select school
|
||||||
|
if (Auth::user()->hasRole('User')) {
|
||||||
|
|
||||||
|
$school_id = (int)($_SESSION['school_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
// manda alla pagina che decide: 1 scuola -> set in automatico, >1 -> selezione
|
||||||
|
header('Location: ' . APP_BASE . '/select_school.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- SCHOOL OWNER & ADMIN: assegna automaticamente la scuola ---
|
||||||
|
if (
|
||||||
|
Auth::check() &&
|
||||||
|
(
|
||||||
|
Auth::user()->hasRole('school_owner') ||
|
||||||
|
Auth::user()->hasRole('Admin')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
$school_id = (int)($_SESSION['school_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
|
||||||
|
$stmt = $db->prepare("
|
||||||
|
SELECT id
|
||||||
|
FROM schools
|
||||||
|
WHERE owner_id = ?
|
||||||
|
AND status = 'active'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$owner_school_id = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($owner_school_id > 0) {
|
||||||
|
$_SESSION['school_id'] = $owner_school_id;
|
||||||
|
} else {
|
||||||
|
// owner/admin senza scuola: mandalo alla pagina profilo che permette di crearla
|
||||||
|
$current_page = basename($_SERVER['PHP_SELF']);
|
||||||
|
|
||||||
|
// evita loop: se sei già su school_profile.php non redirectare di nuovo
|
||||||
|
if ($current_page !== 'school_profile.php') {
|
||||||
|
header("Location: school_profile.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// se sei già nella pagina profilo, lascia proseguire senza school_id
|
||||||
|
unset($_SESSION['school_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
// include/user_settings_loader.php
|
||||||
|
|
||||||
|
// Evita inclusioni multiple
|
||||||
|
if (defined('USER_SETTINGS_LOADED')) return;
|
||||||
|
define('USER_SETTINGS_LOADED', true);
|
||||||
|
|
||||||
|
global $userSettings; // o usa $_SESSION['user_settings'] se preferisci
|
||||||
|
|
||||||
|
$user_id = (int)($iduserlogin ?? $_SESSION['iduserlogin'] ?? 0);
|
||||||
|
|
||||||
|
if ($user_id <= 0) {
|
||||||
|
// Utente non loggato → valori di default minimi/sicuri
|
||||||
|
$userSettings = [
|
||||||
|
'notify_email' => 1,
|
||||||
|
'notify_whatsapp' => 0,
|
||||||
|
'notify_push' => 0,
|
||||||
|
'notify_booking_confirm' => 1,
|
||||||
|
'notify_booking_cancel' => 1,
|
||||||
|
'notify_session_cancel' => 1,
|
||||||
|
'notify_payment_receipt' => 1,
|
||||||
|
'notify_expiration_reminder' => 1,
|
||||||
|
'newsletter_opt_in' => 0,
|
||||||
|
'marketing_opt_in' => 0,
|
||||||
|
'locale' => 'it',
|
||||||
|
'timezone' => 'Europe/Rome',
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM user_settings
|
||||||
|
WHERE user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($settings) {
|
||||||
|
$userSettings = $settings;
|
||||||
|
} else {
|
||||||
|
// Utente senza impostazioni → crea record con default
|
||||||
|
$stmt_insert = $pdo->prepare("
|
||||||
|
INSERT INTO user_settings (user_id) VALUES (?)
|
||||||
|
");
|
||||||
|
$stmt_insert->execute([$user_id]);
|
||||||
|
|
||||||
|
// Ricarica dopo insert
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$userSettings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback per campi che potrebbero essere NULL o mancanti
|
||||||
|
$userSettings = array_merge([
|
||||||
|
'notify_email' => 1,
|
||||||
|
'notify_whatsapp' => 0,
|
||||||
|
'notify_push' => 0,
|
||||||
|
'notify_booking_confirm' => 1,
|
||||||
|
'notify_booking_cancel' => 1,
|
||||||
|
'notify_session_cancel' => 1,
|
||||||
|
'notify_payment_receipt' => 1,
|
||||||
|
'notify_expiration_reminder' => 1,
|
||||||
|
'newsletter_opt_in' => 0,
|
||||||
|
'marketing_opt_in' => 0,
|
||||||
|
'locale' => 'it',
|
||||||
|
'timezone' => 'Europe/Rome',
|
||||||
|
], $userSettings);
|
||||||
|
}
|
||||||
@@ -0,0 +1,338 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['iduserlogin'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iduserlogin = (int)$_SESSION['iduserlogin'];
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// CARICAMENTO CERTIFICATO (POST)
|
||||||
|
// =============================================
|
||||||
|
$success = $error = "";
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['certificate']) && $_FILES['certificate']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$file = $_FILES['certificate'];
|
||||||
|
$allowed_ext = ['jpg', 'jpeg', 'png', 'pdf', 'heic', 'heif'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($ext, $allowed_ext)) {
|
||||||
|
$error = "Formato non supportato. Ammessi: jpg, jpeg, png, pdf, heic, heif";
|
||||||
|
} elseif ($file['size'] > 10 * 1024 * 1024) {
|
||||||
|
$error = "File troppo grande (max 10MB)";
|
||||||
|
} elseif (empty($_POST['expiry_date'])) {
|
||||||
|
$error = "La data di scadenza è obbligatoria";
|
||||||
|
} else {
|
||||||
|
$upload_dir = __DIR__ . '/certificate/';
|
||||||
|
if (!is_dir($upload_dir)) {
|
||||||
|
mkdir($upload_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$safe_name = preg_replace('/[^a-zA-Z0-9\._-]/', '_', basename($file['name']));
|
||||||
|
$new_filename = $iduserlogin . '-' . time() . '-' . $safe_name;
|
||||||
|
$destination = $upload_dir . $new_filename;
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $destination)) {
|
||||||
|
$document_name = trim($_POST['document_name'] ?? 'certificato');
|
||||||
|
if (empty($document_name)) $document_name = 'certificato';
|
||||||
|
|
||||||
|
$expiry_date = $_POST['expiry_date'];
|
||||||
|
$notes = trim($_POST['notes'] ?? '');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_medical_certificates
|
||||||
|
(user_id, filename, stored_path, document_name, expiry_date, notes, uploaded_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, NOW())
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$iduserlogin,
|
||||||
|
$file['name'],
|
||||||
|
'userarea/certificate/' . $new_filename, // ← solo questo
|
||||||
|
$document_name,
|
||||||
|
$expiry_date,
|
||||||
|
$notes
|
||||||
|
]);
|
||||||
|
|
||||||
|
$success = "Certificato caricato correttamente!";
|
||||||
|
} else {
|
||||||
|
$error = "Errore durante il salvataggio del file.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ELIMINAZIONE CERTIFICATO
|
||||||
|
// =============================================
|
||||||
|
if (isset($_GET['delete']) && is_numeric($_GET['delete'])) {
|
||||||
|
$cert_id = (int)$_GET['delete'];
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT stored_path FROM user_medical_certificates WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$cert_id, $iduserlogin]);
|
||||||
|
$cert = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($cert) {
|
||||||
|
$full_path = __DIR__ . '/' . $cert['stored_path'];
|
||||||
|
if (file_exists($full_path)) {
|
||||||
|
@unlink($full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM user_medical_certificates WHERE id = ? AND user_id = ?");
|
||||||
|
$stmt->execute([$cert_id, $iduserlogin]);
|
||||||
|
|
||||||
|
$success = "Certificato eliminato.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// LISTA CERTIFICATI
|
||||||
|
// =============================================
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, filename, stored_path, document_name, expiry_date, uploaded_at, notes
|
||||||
|
FROM user_medical_certificates
|
||||||
|
WHERE user_id = ?
|
||||||
|
ORDER BY uploaded_at DESC
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$certificates = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Dati utente base (solo per titolo o saluto)
|
||||||
|
$stmt = $pdo->prepare("SELECT first_name FROM auth_users WHERE id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>I miei Certificati Medici - Yogiboook</title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
<style>
|
||||||
|
.dropzone {
|
||||||
|
border: 2px dashed #0d6efd;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 50px 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #f8f9fa;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropzone.dragover {
|
||||||
|
background: #e7f1ff;
|
||||||
|
border-color: #0dcaf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expired {
|
||||||
|
color: #dc3545;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-link {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-link:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Riga rosa tenue per scaduti */
|
||||||
|
tr.expired-row {
|
||||||
|
background-color: #ffebee !important;
|
||||||
|
/* rosa molto chiaro / rosso tenue */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge scaduto */
|
||||||
|
.badge-expired {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0.4em 0.8em;
|
||||||
|
border-radius: 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="container-xl">
|
||||||
|
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h4 class="mb-0">I miei Certificati Medici</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<?= htmlspecialchars($success) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<?= htmlspecialchars($error) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- FORM UPLOAD -->
|
||||||
|
<form method="POST" enctype="multipart/form-data" id="uploadForm">
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<div class="col-lg-7">
|
||||||
|
<div class="dropzone" id="dropzone">
|
||||||
|
<i class="bx bx-cloud-upload bx-lg mb-3 text-primary"></i>
|
||||||
|
<h5>Trascina qui il file oppure clicca per selezionare</h5>
|
||||||
|
<p class="text-muted mb-1">Formati: jpg, jpeg, png, pdf, heic, heif (max 10 MB)</p>
|
||||||
|
<input type="file" name="certificate" id="fileInput" accept=".jpg,.jpeg,.png,.pdf,.heic,.heif" hidden>
|
||||||
|
</div>
|
||||||
|
<div id="selectedFileName" class="mt-2 text-primary fw-bold small" style="min-height: 1.5em;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-5">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome documento <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" name="document_name" class="form-control" value="certificato" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Data scadenza <span class="text-danger">*</span></label>
|
||||||
|
<input type="date" name="expiry_date" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Note (opzionale)</label>
|
||||||
|
<textarea name="notes" class="form-control" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="bx bx-upload me-2"></i> Carica Certificato
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- TABELLA CERTIFICATI -->
|
||||||
|
<?php if (empty($certificates)): ?>
|
||||||
|
<div class="text-center py-5 text-muted">
|
||||||
|
<i class="bx bx-file-blank bx-lg"></i>
|
||||||
|
<h5 class="mt-3">Nessun certificato caricato</h5>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<h5 class="mt-5 mb-3">Certificati caricati (<?= count($certificates) ?>)</h5>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-bordered align-middle">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Data caricamento</th>
|
||||||
|
<th>Nome documento / File</th>
|
||||||
|
<th>Scadenza</th>
|
||||||
|
<th>Note</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($certificates as $cert):
|
||||||
|
$file_url = '/' . $cert['stored_path']; // usa percorso root-relative come consigliato prima
|
||||||
|
$expired = $cert['expiry_date'] && strtotime($cert['expiry_date']) < time();
|
||||||
|
$row_class = $expired ? 'expired-row' : '';
|
||||||
|
?>
|
||||||
|
<tr class="<?= $row_class ?>">
|
||||||
|
<td><?= date('d/m/Y H:i', strtotime($cert['uploaded_at'])) ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="<?= htmlspecialchars($file_url) ?>" target="_blank" class="file-link">
|
||||||
|
<?= htmlspecialchars($cert['document_name']) ?>
|
||||||
|
<br>
|
||||||
|
<small class="text-muted">(<?= htmlspecialchars($cert['filename']) ?>)</small>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="<?= $expired ? 'expired' : '' ?>">
|
||||||
|
<?= $cert['expiry_date'] ? date('d/m/Y', strtotime($cert['expiry_date'])) : '—' ?>
|
||||||
|
<?php if ($expired): ?>
|
||||||
|
<span class="badge-expired ms-2">SCADUTO</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= $cert['notes'] ? nl2br(htmlspecialchars(substr($cert['notes'], 0, 100))) . (strlen($cert['notes']) > 100 ? '...' : '') : '—' ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<a href="?delete=<?= $cert['id'] ?>" class="btn btn-sm btn-outline-danger delete-cert"
|
||||||
|
onclick="return confirm('Vuoi davvero eliminare questo certificato?');">
|
||||||
|
<i class="bx bx-trash"></i> Elimina
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const dropzone = document.getElementById('dropzone');
|
||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
|
||||||
|
dropzone.addEventListener('click', () => fileInput.click());
|
||||||
|
|
||||||
|
dropzone.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener('dragleave', () => {
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropzone.classList.remove('dragover');
|
||||||
|
if (e.dataTransfer.files.length > 0) {
|
||||||
|
fileInput.files = e.dataTransfer.files;
|
||||||
|
document.getElementById('uploadForm').submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileInput.addEventListener('change', function() {
|
||||||
|
const fileNameDiv = document.getElementById('selectedFileName');
|
||||||
|
if (this.files.length > 0) {
|
||||||
|
fileNameDiv.textContent = 'File selezionato: ' + this.files[0].name;
|
||||||
|
fileNameDiv.classList.add('text-success');
|
||||||
|
} else {
|
||||||
|
fileNameDiv.textContent = '';
|
||||||
|
fileNameDiv.classList.remove('text-success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -46,12 +46,26 @@ $stmt = $pdo->prepare("
|
|||||||
JOIN orders o ON sb.order_id = o.id
|
JOIN orders o ON sb.order_id = o.id
|
||||||
WHERE sb.user_id = ?
|
WHERE sb.user_id = ?
|
||||||
AND cs.school_id = ?
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'booked'
|
||||||
AND cs.session_date >= ?
|
AND cs.session_date >= ?
|
||||||
AND cs.session_date < ?
|
AND cs.session_date < ?
|
||||||
ORDER BY cs.session_date ASC, cs.start_time ASC
|
ORDER BY cs.session_date ASC, cs.start_time ASC
|
||||||
");
|
");
|
||||||
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
|
$stmt->execute([$iduserlogin, $school_id, $startOfMonth, $endOfMonth]);
|
||||||
$bookings = $stmt->fetchAll();
|
$bookings = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// === CONTROLLA CERTIFICATI VALIDI ===
|
||||||
|
$stmt_cert = $pdo->prepare("
|
||||||
|
SELECT COUNT(*) AS valid_count
|
||||||
|
FROM user_medical_certificates
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND expiry_date IS NOT NULL
|
||||||
|
AND expiry_date >= CURDATE()
|
||||||
|
AND is_valid = 1
|
||||||
|
");
|
||||||
|
$stmt_cert->execute([$iduserlogin]);
|
||||||
|
$cert_result = $stmt_cert->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$has_valid_cert = ($cert_result['valid_count'] > 0);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -205,9 +219,24 @@ $bookings = $stmt->fetchAll();
|
|||||||
<a href="?month=<?= $nextMonth ?>" class="nav-arrow">→</a>
|
<a href="?month=<?= $nextMonth ?>" class="nav-arrow">→</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php if (!empty($_GET['rebook']) && !empty($_GET['msg'])): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show mb-4">
|
||||||
|
<?= htmlspecialchars($_GET['msg']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
|
<?php if (!$has_valid_cert): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow" role="alert">
|
||||||
|
<strong>Attenzione!</strong> Non hai un certificato medico valido caricato.
|
||||||
|
<br>Ti potrebbe essere vietato l'accesso alle lezioni/pratiche.
|
||||||
|
<a href="my_certificates.php" class="alert-link fw-bold ms-2">
|
||||||
|
Caricalo subito qui →
|
||||||
|
</a>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (empty($bookings)): ?>
|
<?php if (empty($bookings)): ?>
|
||||||
<div class="text-center py-5 px-3">
|
<div class="text-center py-5 px-3">
|
||||||
<i class="bx bx-calendar-x" style="font-size:4.5rem;color:#e0e0e0;"></i>
|
<i class="bx bx-calendar-x" style="font-size:4.5rem;color:#e0e0e0;"></i>
|
||||||
@@ -264,7 +293,7 @@ $bookings = $stmt->fetchAll();
|
|||||||
<button class="btn-custom btn-primary-custom" onclick="reschedule(<?= $b['booking_id'] ?>)">
|
<button class="btn-custom btn-primary-custom" onclick="reschedule(<?= $b['booking_id'] ?>)">
|
||||||
Riprogramma
|
Riprogramma
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-custom btn-outline-custom" onclick="cancelBooking(<?= $b['booking_id'] ?>)">
|
<button class="btn-custom btn-outline-custom" onclick="confirmCancel(<?= $b['booking_id'] ?>)">
|
||||||
Cancella
|
Cancella
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -288,29 +317,56 @@ $bookings = $stmt->fetchAll();
|
|||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<script>
|
<script>
|
||||||
const reschedule = id => Swal.fire({
|
const confirmCancel = id => {
|
||||||
title: 'Riprogrammare?',
|
Swal.fire({
|
||||||
text: 'Scegli un nuovo orario',
|
title: 'Annullare questa prenotazione?',
|
||||||
icon: 'question',
|
html: 'Cancellando la lezione:<br>' +
|
||||||
showCancelButton: true,
|
'<ul style="text-align:left; margin:15px 0 0 20px; font-size:0.95rem;">' +
|
||||||
confirmButtonText: 'Sì',
|
'<li>Il ticket/ingresso resterà disponibile nella tua area utente</li>' +
|
||||||
cancelButtonText: 'No'
|
'<li>Potrai riprogrammarlo su un\'altra data (se non hai superato il limite di recuperi o la scadenza del pacchetto)</li>' +
|
||||||
}).then(r => r.isConfirmed && (location = 'reschedule.php?booking=' + id));
|
'<li>L\'annullamento è irreversibile</li>' +
|
||||||
const cancelBooking = id => Swal.fire({
|
'</ul>',
|
||||||
title: 'Annullare prenotazione?',
|
icon: 'warning',
|
||||||
text: 'Riceverai un recupero',
|
showCancelButton: true,
|
||||||
icon: 'warning',
|
confirmButtonColor: '#ef4444',
|
||||||
showCancelButton: true,
|
cancelButtonColor: '#6c757d',
|
||||||
confirmButtonColor: '#ef4444',
|
confirmButtonText: 'Sì, annulla la lezione',
|
||||||
confirmButtonText: 'Sì, annulla',
|
cancelButtonText: 'No, mantienila',
|
||||||
cancelButtonText: 'No'
|
reverseButtons: true
|
||||||
}).then(r => r.isConfirmed && fetch('cancel_booking.php', {
|
}).then((result) => {
|
||||||
method: 'POST',
|
if (result.isConfirmed) {
|
||||||
headers: {
|
fetch('cancel_booking.php', {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
method: 'POST',
|
||||||
},
|
headers: {
|
||||||
body: 'booking_id=' + id
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}).then(r => r.json()).then(d => d.success ? Swal.fire('Annullata!', 'Hai un recupero', 'success').then(() => location.reload()) : Swal.fire('Errore', d.message, 'error')));
|
},
|
||||||
|
body: 'booking_id=' + id
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Prenotazione annullata!',
|
||||||
|
text: 'Il tuo ingresso è tornato disponibile per un recupero/riprogrammazione.',
|
||||||
|
icon: 'success',
|
||||||
|
timer: 2500
|
||||||
|
}).then(() => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message || 'Impossibile annullare la prenotazione.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
Swal.fire('Errore di connessione', 'Impossibile comunicare con il server.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const reschedule = id => {
|
||||||
|
location = 'reschedule.php?booking=' + id;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
include('include/headscript.php'); // ← adatta il path se necessario (da userarea/ sale di due livelli)
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['iduserlogin'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Non autorizzato']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$school_id = (int)($_POST['school_id'] ?? 0);
|
||||||
|
$user_id = (int)$_SESSION['iduserlogin'];
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Scuola non valida']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica iscrizione attiva
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id FROM user_schools
|
||||||
|
WHERE user_id = ? AND school_id = ? AND status = 'active'
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
if (!$stmt->fetch()) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Non sei iscritto a questa scuola']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft-delete: imposta status = 'inactive'
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE user_schools
|
||||||
|
SET status = 'inactive', updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE user_id = ? AND school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
|
||||||
|
// Opzionale: resetta scuola corrente in sessione
|
||||||
|
if (isset($_SESSION['school_id']) && $_SESSION['school_id'] == $school_id) {
|
||||||
|
unset($_SESSION['school_id'], $_SESSION['school_name'], $_SESSION['school_selected']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Opzionale) cancella dati associati - commenta se NON vuoi eliminare
|
||||||
|
// Esempio cancellazione prenotazioni:
|
||||||
|
$pdo->prepare("
|
||||||
|
DELETE sb FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = ? AND cs.school_id = ?
|
||||||
|
")->execute([$user_id, $school_id]);
|
||||||
|
|
||||||
|
// Esempio cancellazione ordini:
|
||||||
|
$pdo->prepare("DELETE FROM orders WHERE user_id = ? AND school_id = ?")
|
||||||
|
->execute([$user_id, $school_id]);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true]);
|
||||||
@@ -0,0 +1,500 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
//setlocale(LC_TIME, 'it_IT.UTF-8', 'it_IT.utf8', 'Italian_Italy.1252', 'it_IT');
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = $_SESSION['school_id'];
|
||||||
|
if (!$school_id) die("Nessuna scuola selezionata");
|
||||||
|
|
||||||
|
$booking_id = (int)($_GET['booking'] ?? 0);
|
||||||
|
if ($booking_id <= 0) die("Prenotazione non valida");
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT name, address_street, address_city, address_province FROM schools WHERE id = ?");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$school = $stmt->fetch();
|
||||||
|
// Recupera dettagli vecchia prenotazione
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
sb.id, sb.order_id,
|
||||||
|
c.name AS class_name, ct.level, ct.class_id, cs.session_date, cs.start_time, cs.end_time
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE sb.id = ? AND sb.user_id = ? AND sb.status = 'booked'
|
||||||
|
");
|
||||||
|
$stmt->execute([$booking_id, $iduserlogin]);
|
||||||
|
$old_booking = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$old_booking) die("Prenotazione non trovata o non modificabile");
|
||||||
|
|
||||||
|
// Recupera impostazioni scuola (per auto-approved)
|
||||||
|
$stmt = $pdo->prepare("SELECT rebooking_auto_approved FROM school_settings WHERE school_id = ?");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$auto_approved = $settings['rebooking_auto_approved'] ?? 1;
|
||||||
|
|
||||||
|
// Lezioni future disponibili (tutte le scheduled future)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
cs.id AS session_id,
|
||||||
|
cs.session_date,
|
||||||
|
cs.start_time,
|
||||||
|
cs.end_time,
|
||||||
|
cs.room_name,
|
||||||
|
cs.max_capacity,
|
||||||
|
c.name AS class_name,
|
||||||
|
ct.level,
|
||||||
|
(SELECT COUNT(*) FROM session_bookings WHERE session_id = cs.id AND status IN ('booked', 'pending')) AS booked_count,
|
||||||
|
(SELECT COUNT(*) FROM session_bookings WHERE session_id = cs.id AND user_id = ? AND status IN ('booked', 'pending')) AS user_booked
|
||||||
|
FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.school_id = ?
|
||||||
|
AND cs.status = 'scheduled'
|
||||||
|
AND cs.session_date >= CURDATE()
|
||||||
|
ORDER BY cs.session_date ASC, cs.start_time ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin, $school_id]);
|
||||||
|
$available_lessons = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Gestione POST: riprogrammazione
|
||||||
|
$success = $error = "";
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['new_session_id'])) {
|
||||||
|
$new_session_id = (int)$_POST['new_session_id'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Verifica disponibilità nuova sessione
|
||||||
|
$stmt_check = $pdo->prepare("
|
||||||
|
SELECT max_capacity,
|
||||||
|
(SELECT COUNT(*) FROM session_bookings WHERE session_id = ? AND status IN ('booked', 'pending')) AS booked_count
|
||||||
|
FROM class_sessions
|
||||||
|
WHERE id = ? AND status = 'scheduled' AND session_date >= CURDATE()
|
||||||
|
");
|
||||||
|
$stmt_check->execute([$new_session_id, $new_session_id]);
|
||||||
|
$new_session = $stmt_check->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!$new_session || $new_session['booked_count'] >= $new_session['max_capacity']) {
|
||||||
|
throw new Exception("Lezione non disponibile o piena");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crea nuova prenotazione
|
||||||
|
$status_new = $auto_approved ? 'booked' : 'pending';
|
||||||
|
$stmt_new = $pdo->prepare("
|
||||||
|
INSERT INTO session_bookings
|
||||||
|
(session_id, user_id, order_id, status, booked_at, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt_new->execute([$new_session_id, $iduserlogin, $old_booking['order_id'], $status_new]);
|
||||||
|
|
||||||
|
// Marca vecchia come rescheduled
|
||||||
|
$stmt_old = $pdo->prepare("
|
||||||
|
UPDATE session_bookings
|
||||||
|
SET status = 'rescheduled', updated_at = NOW()
|
||||||
|
WHERE id = ?
|
||||||
|
");
|
||||||
|
$stmt_old->execute([$booking_id]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
// Redirect to my_lessons.php with a success flag/message
|
||||||
|
$msg = $auto_approved ? "Riprogrammazione completata. Lezione confermata." : "Riprogrammazione inviata. In attesa di approvazione.";
|
||||||
|
header("Location: my_lessons.php?rebook=1&msg=" . urlencode($msg));
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$error = "Errore durante la riprogrammazione: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function it_weekday($dateYmd)
|
||||||
|
{
|
||||||
|
$fmt = new IntlDateFormatter(
|
||||||
|
'it_IT',
|
||||||
|
IntlDateFormatter::FULL,
|
||||||
|
IntlDateFormatter::NONE,
|
||||||
|
'Europe/Rome',
|
||||||
|
IntlDateFormatter::GREGORIAN,
|
||||||
|
'EEEE'
|
||||||
|
);
|
||||||
|
return ucfirst($fmt->format(strtotime($dateYmd)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function it_day_month($dateYmd)
|
||||||
|
{
|
||||||
|
$fmt = new IntlDateFormatter(
|
||||||
|
'it_IT',
|
||||||
|
IntlDateFormatter::NONE,
|
||||||
|
IntlDateFormatter::NONE,
|
||||||
|
'Europe/Rome',
|
||||||
|
IntlDateFormatter::GREGORIAN,
|
||||||
|
'd MMMM'
|
||||||
|
);
|
||||||
|
return ucfirst($fmt->format(strtotime($dateYmd)));
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Riprogramma Lezione - YoGiBook</title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
<style>
|
||||||
|
/* Riutilizzo esatto gli stili da my_lessons.php */
|
||||||
|
.page-content {
|
||||||
|
background: #f9fff9;
|
||||||
|
padding-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.month-header {
|
||||||
|
background: white;
|
||||||
|
padding: 0.9rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.07);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 18px;
|
||||||
|
margin: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.available {
|
||||||
|
border-left: 6px solid #10b981;
|
||||||
|
/* Verde */
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.user-booked {
|
||||||
|
border-left: 6px solid #f59e0b;
|
||||||
|
/* Arancione */
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.full {
|
||||||
|
border-left: 6px solid #6b7280;
|
||||||
|
/* Grigio */
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-header {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
padding: 1.4rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mantieni il verde chiaro originale per date-header delle card disponibili */
|
||||||
|
.lesson-card.available .date-header {
|
||||||
|
background: #d1fae5;
|
||||||
|
/* Verde chiaro originale */
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Date-header più scuro per card già prenotate (arancione) */
|
||||||
|
.lesson-card.user-booked .date-header {
|
||||||
|
background: #ff8c4f;
|
||||||
|
/* Arancione scuro */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Date-header più scuro per card piena (grigio) */
|
||||||
|
.lesson-card.full .date-header {
|
||||||
|
background: linear-gradient(135deg, #4b5563, #6b7280);
|
||||||
|
/* Grigio scuro */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Opzionale: rendi testo data più leggibile su sfondi scuri */
|
||||||
|
.lesson-card.user-booked .date-header .date-num,
|
||||||
|
.lesson-card.full .date-header .date-num {
|
||||||
|
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.user-booked .date-header .date-day,
|
||||||
|
.lesson-card.full .date-header .date-day {
|
||||||
|
color: rgba(255, 255, 255, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-num {
|
||||||
|
font-size: 2.1rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-day {
|
||||||
|
font-size: 1.18rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 5px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-body {
|
||||||
|
padding: 1.4rem 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-title {
|
||||||
|
font-size: 1.38rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin: 0 0 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-meta {
|
||||||
|
font-size: 0.98rem;
|
||||||
|
color: #444;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-meta i {
|
||||||
|
width: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entries-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
margin: 0.8rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-custom {
|
||||||
|
padding: 0.85rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary-custom {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 18px;
|
||||||
|
margin: 1rem;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.09);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.available {
|
||||||
|
border-left: 6px solid #10b981;
|
||||||
|
/* Verde solo bordo sinistro */
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.user-booked {
|
||||||
|
background: #fff7ed !important;
|
||||||
|
/* Arancione chiaro sfondo tutta card */
|
||||||
|
border-left: 6px solid #f59e0b;
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-card.full {
|
||||||
|
background: #f3f4f6 !important;
|
||||||
|
/* Grigio chiaro sfondo tutta card */
|
||||||
|
border-left: 6px solid #6b7280;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="month-header">
|
||||||
|
<h2>Riprogramma lezione</h2>
|
||||||
|
<p class="text-muted small mt-2 mb-1">
|
||||||
|
<?= htmlspecialchars($old_booking['class_name']) ?>
|
||||||
|
<?php if ($old_booking['level']): ?> - <?= ucfirst($old_booking['level']) ?><?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<p class="fw-bold text-primary mb-0">
|
||||||
|
<?= htmlspecialchars(it_day_month($old_booking['session_date'])) ?> <?= date('Y', strtotime($old_booking['session_date'])) ?>
|
||||||
|
dalle <?= date('H:i', strtotime($old_booking['start_time'])) ?>
|
||||||
|
alle <?= date('H:i', strtotime($old_booking['end_time'])) ?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-end mb-3">
|
||||||
|
<a href="my_lessons.php" class="btn btn-outline-secondary">
|
||||||
|
<i class="bx bx-arrow-back me-1"></i> Annulla e torna indietro
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container-fluid px-0">
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show mb-4">
|
||||||
|
<?= htmlspecialchars($success) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show mb-4">
|
||||||
|
<?= htmlspecialchars($error) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<?php if (empty($available_lessons)): ?>
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<i class="bx bx-calendar-x bx-lg text-muted"></i>
|
||||||
|
<h5 class="mt-3 text-secondary">Nessuna lezione disponibile per la riprogrammazione</h5>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($available_lessons as $lesson): ?>
|
||||||
|
<?php
|
||||||
|
$is_full = $lesson['booked_count'] >= $lesson['max_capacity'];
|
||||||
|
$is_user_booked = $lesson['user_booked'] > 0;
|
||||||
|
$card_class = $is_full ? 'full' : ($is_user_booked ? 'user-booked' : 'available');
|
||||||
|
?>
|
||||||
|
<div class="col-md-6 col-lg-4 mb-4">
|
||||||
|
<div class="lesson-card <?= $card_class ?>">
|
||||||
|
<div class="date-header">
|
||||||
|
<div class="date-num"><?= htmlspecialchars(it_day_month($lesson['session_date'])) ?></div>
|
||||||
|
<div class="date-day">
|
||||||
|
<?= htmlspecialchars(it_weekday($lesson['session_date'])) ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lesson-body">
|
||||||
|
<h3 class="lesson-title">
|
||||||
|
<?= htmlspecialchars($lesson['class_name']) ?>
|
||||||
|
<?php if ($lesson['level']): ?>
|
||||||
|
<small style="color:#666;">- <?= ucfirst($lesson['level']) ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="lesson-meta">
|
||||||
|
<i class="bx bx-time"></i>
|
||||||
|
<?= date('H:i', strtotime($lesson['start_time'])) ?> - <?= date('H:i', strtotime($lesson['end_time'])) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lesson-meta">
|
||||||
|
<i class="bx bx-home"></i>
|
||||||
|
<?= htmlspecialchars($school['name']) ?>
|
||||||
|
<?php if ($lesson['room_name']): ?> - <?= htmlspecialchars($lesson['room_name']) ?><?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="lesson-meta">
|
||||||
|
<i class="bx bx-map"></i>
|
||||||
|
<?= htmlspecialchars(trim($school['address_street'] . ', ' . $school['address_city'] . ' ' . $school['address_province'])) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="entries-badge">
|
||||||
|
Posti liberi: <strong><?= max(0, $lesson['max_capacity'] - $lesson['booked_count']) ?></strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($is_full): ?>
|
||||||
|
<div class="text-danger fw-bold text-center mt-4 fs-5">
|
||||||
|
Lezione piena
|
||||||
|
</div>
|
||||||
|
<?php elseif ($is_user_booked): ?>
|
||||||
|
<div class="text-warning fw-bold text-center mt-4 fs-5">
|
||||||
|
Già prenotata da te
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="action-buttons">
|
||||||
|
<form method="POST" class="js-rebook-form"
|
||||||
|
data-date="<?= htmlspecialchars(date('d/m/Y', strtotime($lesson['session_date']))) ?>"
|
||||||
|
data-start="<?= htmlspecialchars(date('H:i', strtotime($lesson['start_time']))) ?>"
|
||||||
|
data-end="<?= htmlspecialchars(date('H:i', strtotime($lesson['end_time']))) ?>"
|
||||||
|
data-class="<?= htmlspecialchars($lesson['class_name']) ?>">
|
||||||
|
<input type="hidden" name="new_session_id" value="<?= (int)$lesson['session_id'] ?>">
|
||||||
|
<button type="submit" class="btn-custom btn-primary-custom">
|
||||||
|
Riprogramma qui
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.querySelectorAll('.js-rebook-form').forEach(function(form) {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const d = form.dataset.date || '';
|
||||||
|
const s = form.dataset.start || '';
|
||||||
|
const en = form.dataset.end || '';
|
||||||
|
const cls = form.dataset.class || '';
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Confermi la riprogrammazione?',
|
||||||
|
html: `
|
||||||
|
<div style="text-align:left">
|
||||||
|
<div><strong>Lezione:</strong> ${cls}</div>
|
||||||
|
<div><strong>Quando:</strong> ${d} ${s} - ${en}</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Sì, riprogramma',
|
||||||
|
cancelButtonText: 'No, annulla',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -17,27 +17,51 @@ if (!isset($iduserlogin)) {
|
|||||||
|
|
||||||
// Recupera i dati della scuola in base all'utente loggato
|
// Recupera i dati della scuola in base all'utente loggato
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, name, website, email, phone, description, address_street, address_city, address_postal_code, address_province, address_country, logo, status
|
SELECT id, name, website, email, phone, description, address_street, address_city, address_postal_code, address_province, address_country, logo, status
|
||||||
FROM schools
|
FROM schools
|
||||||
WHERE owner_id = ?
|
WHERE owner_id = ?
|
||||||
|
ORDER BY id DESC
|
||||||
|
LIMIT 1
|
||||||
");
|
");
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
$school = $stmt->fetch();
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$school) {
|
if (!$school) {
|
||||||
die("Errore: Nessuna scuola trovata per l'utente loggato.");
|
// Owner/admin senza scuola: manda al profilo scuola (creazione)
|
||||||
|
$_SESSION['school_id'] = 0;
|
||||||
|
header("Location: school_profile.php");
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
$school_id = $school['id'];
|
|
||||||
|
// ok: scuola trovata → sincronizza sessione
|
||||||
|
$school_id = (int)$school['id'];
|
||||||
|
$_SESSION['school_id'] = $school_id;
|
||||||
|
|
||||||
$school_name = $school['name'];
|
$school_name = $school['name'];
|
||||||
|
|
||||||
|
|
||||||
// Recupera tutte le categorie disponibili
|
// Recupera tutte le categorie disponibili
|
||||||
$stmt = $pdo->prepare("SELECT id, name FROM class_categories WHERE status = 'active' ORDER BY name");
|
$stmt = $pdo->prepare("SELECT id, name FROM class_categories WHERE status = 'active' ORDER BY name");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$categories = $stmt->fetchAll();
|
$categories = $stmt->fetchAll();
|
||||||
|
|
||||||
// Recupera tutti gli insegnanti della scuola
|
// Recupera tutti gli insegnanti della scuola
|
||||||
$stmt = $pdo->prepare("SELECT id, first_name, last_name FROM teachers WHERE user_id = ? AND status = 'active' ORDER BY first_name, last_name");
|
// Teachers linked to this school (active/pending)
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt = $pdo->prepare("
|
||||||
$teachers = $stmt->fetchAll();
|
SELECT
|
||||||
|
t.id,
|
||||||
|
u.first_name,
|
||||||
|
u.last_name
|
||||||
|
FROM teacher_schools ts
|
||||||
|
JOIN teachers t ON ts.teacher_id = t.id
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE ts.school_id = ?
|
||||||
|
AND ts.status IN ('active','pending')
|
||||||
|
ORDER BY u.first_name, u.last_name
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
// Funzione per ridimensionare l'immagine
|
// Funzione per ridimensionare l'immagine
|
||||||
function resizeImage($source_path, $dest_path, $max_width = 800)
|
function resizeImage($source_path, $dest_path, $max_width = 800)
|
||||||
@@ -840,7 +864,7 @@ $daily_sessions = $stmt->fetchAll();
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex flex-wrap justify-content-center gap-3">
|
<div class="d-flex flex-wrap justify-content-center gap-3">
|
||||||
<!-- Pulsante Situazione Clienti -->
|
<!-- Pulsante Situazione Clienti -->
|
||||||
<a href="#" class="btn btn-primary d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
<a href="clients_situation.php" class="btn btn-primary d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
||||||
<i class="bx bx-user me-2" style="font-size: 20px;"></i>
|
<i class="bx bx-user me-2" style="font-size: 20px;"></i>
|
||||||
<span class="fs-6">Situazione Clienti</span>
|
<span class="fs-6">Situazione Clienti</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -859,9 +883,9 @@ $daily_sessions = $stmt->fetchAll();
|
|||||||
<span class="fs-6">Prodotti</span>
|
<span class="fs-6">Prodotti</span>
|
||||||
</a>
|
</a>
|
||||||
<!-- Pulsante Abbonamenti -->
|
<!-- Pulsante Abbonamenti -->
|
||||||
<a href="#" class="btn btn-info d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
<a href="add_user.php" class="btn btn-info d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
||||||
<i class="bx bx-calendar me-2" style="font-size: 20px;"></i>
|
<i class="bx bx-calendar me-2" style="font-size: 20px;"></i>
|
||||||
<span class="fs-6">Abbonamenti</span>
|
<span class="fs-6">Aggiungi Utente</span>
|
||||||
</a>
|
</a>
|
||||||
<!-- Pulsante Day Off -->
|
<!-- Pulsante Day Off -->
|
||||||
<a href="day_off.php" class="btn btn-danger d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
<a href="day_off.php" class="btn btn-danger d-flex align-items-center px-3 py-2 shadow-sm rounded" style="min-width: 150px;">
|
||||||
@@ -1052,6 +1076,12 @@ $daily_sessions = $stmt->fetchAll();
|
|||||||
data-bs-toggle="tooltip" data-bs-placement="top" title="Propaga">
|
data-bs-toggle="tooltip" data-bs-placement="top" title="Propaga">
|
||||||
<i class="bx bx-broadcast"></i> Propaga
|
<i class="bx bx-broadcast"></i> Propaga
|
||||||
</button>
|
</button>
|
||||||
|
<a href="class_type_associate.php?class_type_id=<?php echo (int)$variation['id']; ?>"
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
data-bs-toggle="tooltip" data-bs-placement="top" title="Associa per riprogrammazione">
|
||||||
|
<i class="bx bx-link"></i> Associa
|
||||||
|
</a>
|
||||||
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#removePropagationModal"
|
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#removePropagationModal"
|
||||||
onclick='fillRemovePropagationModal(<?php echo json_encode([
|
onclick='fillRemovePropagationModal(<?php echo json_encode([
|
||||||
"class_type_id" => $variation['id']
|
"class_type_id" => $variation['id']
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
$user_id = (int)($iduserlogin ?? $_SESSION['iduserlogin'] ?? 0);
|
||||||
|
if ($user_id <= 0) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// solo school_owner (admin ok)
|
||||||
|
if (!(Auth::user()->hasRole('school_owner') || Auth::user()->hasRole('Admin'))) {
|
||||||
|
die("Access denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se ha già una scuola, setta session e vai in dashboard
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM schools WHERE owner_id = ? ORDER BY id DESC LIMIT 1");
|
||||||
|
$stmt->execute([$user_id]);
|
||||||
|
$existing = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($existing && !empty($existing['id'])) {
|
||||||
|
$_SESSION['school_id'] = (int)$existing['id'];
|
||||||
|
header('Location: school_dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSlug($str)
|
||||||
|
{
|
||||||
|
$str = trim(mb_strtolower($str));
|
||||||
|
$str = preg_replace('/[^a-z0-9]+/i', '-', $str);
|
||||||
|
$str = trim($str, '-');
|
||||||
|
return $str ?: 'school';
|
||||||
|
}
|
||||||
|
|
||||||
|
$success_message = null;
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$website = trim($_POST['website'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
|
||||||
|
$address_street = trim($_POST['address_street'] ?? '');
|
||||||
|
$address_city = trim($_POST['address_city'] ?? '');
|
||||||
|
$address_postal_code = trim($_POST['address_postal_code'] ?? '');
|
||||||
|
$address_province = trim($_POST['address_province'] ?? '');
|
||||||
|
$address_country = trim($_POST['address_country'] ?? 'Italy');
|
||||||
|
|
||||||
|
$owner_name = trim($_POST['owner_name'] ?? '');
|
||||||
|
$vat_number = trim($_POST['vat_number'] ?? '');
|
||||||
|
|
||||||
|
if ($name === '' || $email === '' || $address_street === '' || $address_city === '' || $address_postal_code === '' || $address_country === '' || $owner_name === '' || $vat_number === '') {
|
||||||
|
$error = "Compila tutti i campi obbligatori.";
|
||||||
|
} else {
|
||||||
|
$slugBase = makeSlug($name);
|
||||||
|
$slug = $slugBase;
|
||||||
|
|
||||||
|
// slug unico
|
||||||
|
$check = $pdo->prepare("SELECT COUNT(*) FROM schools WHERE slug = ?");
|
||||||
|
$i = 1;
|
||||||
|
while (true) {
|
||||||
|
$check->execute([$slug]);
|
||||||
|
if ((int)$check->fetchColumn() === 0) break;
|
||||||
|
$i++;
|
||||||
|
$slug = $slugBase . '-' . $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$stmtIns = $pdo->prepare("
|
||||||
|
INSERT INTO schools
|
||||||
|
(owner_id, name, website, email, phone, description,
|
||||||
|
address_street, address_city, address_postal_code, address_province, address_country,
|
||||||
|
owner_name, vat_number, status, slug)
|
||||||
|
VALUES
|
||||||
|
(?, ?, ?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?, ?,
|
||||||
|
?, ?, 'active', ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmtIns->execute([
|
||||||
|
$user_id,
|
||||||
|
$name,
|
||||||
|
($website ?: null),
|
||||||
|
$email,
|
||||||
|
($phone ?: null),
|
||||||
|
($description ?: null),
|
||||||
|
$address_street,
|
||||||
|
$address_city,
|
||||||
|
$address_postal_code,
|
||||||
|
($address_province ?: null),
|
||||||
|
$address_country,
|
||||||
|
$owner_name,
|
||||||
|
$vat_number,
|
||||||
|
$slug
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newSchoolId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
// school_settings default
|
||||||
|
$stmtSet = $pdo->prepare("INSERT INTO school_settings (school_id) VALUES (?)");
|
||||||
|
$stmtSet->execute([$newSchoolId]);
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$_SESSION['school_id'] = $newSchoolId;
|
||||||
|
header('Location: school_dashboard.php');
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
$error = "Errore creazione scuola: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Configurazione iniziale scuola</h5>
|
||||||
|
<small class="text-muted">Crea la tua scuola per iniziare a usare il pannello proprietario.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST" class="row g-3">
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Nome scuola *</label>
|
||||||
|
<input type="text" name="name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Email scuola *</label>
|
||||||
|
<input type="email" name="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Telefono</label>
|
||||||
|
<input type="text" name="phone" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Sito web</label>
|
||||||
|
<input type="text" name="website" class="form-control" placeholder="https://...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label class="form-label">Descrizione</label>
|
||||||
|
<textarea name="description" class="form-control" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-2">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Indirizzo *</label>
|
||||||
|
<input type="text" name="address_street" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Città *</label>
|
||||||
|
<input type="text" name="address_city" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">CAP *</label>
|
||||||
|
<input type="text" name="address_postal_code" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Provincia</label>
|
||||||
|
<input type="text" name="address_province" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Nazione *</label>
|
||||||
|
<input type="text" name="address_country" class="form-control" value="Italy" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-2">
|
||||||
|
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Nome intestatario/Proprietario *</label>
|
||||||
|
<input type="text" name="owner_name" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">P.IVA / VAT *</label>
|
||||||
|
<input type="text" name="vat_number" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 d-flex justify-content-end gap-2">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
Crea scuola
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -18,6 +18,15 @@ $stmt = $pdo->prepare("SELECT * FROM school_settings WHERE school_id = ?");
|
|||||||
$stmt->execute([$school_id]);
|
$stmt->execute([$school_id]);
|
||||||
$settings = $stmt->fetch();
|
$settings = $stmt->fetch();
|
||||||
|
|
||||||
|
// Ricarica con default se manca la colonna (per scuole vecchie)
|
||||||
|
if ($settings && !array_key_exists('portal_purchases_enabled', $settings)) {
|
||||||
|
$settings['portal_purchases_enabled'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($settings && !array_key_exists('rebooking_auto_approved', $settings)) {
|
||||||
|
$settings['rebooking_auto_approved'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
$is_new = !$settings;
|
$is_new = !$settings;
|
||||||
|
|
||||||
$success_message = $error = "";
|
$success_message = $error = "";
|
||||||
@@ -48,15 +57,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
if (!empty($_POST['allow_drop_in'])) $product_types[] = 'drop_in';
|
if (!empty($_POST['allow_drop_in'])) $product_types[] = 'drop_in';
|
||||||
$allowed_product_types = !empty($product_types) ? implode(',', $product_types) : 'none';
|
$allowed_product_types = !empty($product_types) ? implode(',', $product_types) : 'none';
|
||||||
|
|
||||||
|
|
||||||
|
$portal_purchases_enabled = !empty($_POST['portal_purchases_enabled']) ? 1 : 0;
|
||||||
|
$rebooking_auto_approved = !empty($_POST['rebooking_auto_approved']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Se acquisti portale disabilitati → forza anche propagate a 0
|
||||||
|
$auto_propagate_on_purchase = $portal_purchases_enabled
|
||||||
|
? (!empty($_POST['auto_propagate_on_purchase']) ? 1 : 0)
|
||||||
|
: 0;
|
||||||
// === SALVATAGGIO ===
|
// === SALVATAGGIO ===
|
||||||
try {
|
try {
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO school_settings (
|
INSERT INTO school_settings (
|
||||||
school_id, header_color, sidebar_color, payment_methods, currency_code, enable_notifications,
|
school_id, header_color, sidebar_color, payment_methods, currency_code, enable_notifications,
|
||||||
allow_freeze_global, freeze_max_days_global, auto_propagate_on_purchase,
|
allow_freeze_global, freeze_max_days_global, auto_propagate_on_purchase,
|
||||||
allow_full_access_rebooking, allowed_product_types
|
allow_full_access_rebooking, allowed_product_types,
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
portal_purchases_enabled, rebooking_auto_approved
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$school_id,
|
$school_id,
|
||||||
@@ -69,15 +87,18 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$freeze_max_days_global,
|
$freeze_max_days_global,
|
||||||
$auto_propagate_on_purchase,
|
$auto_propagate_on_purchase,
|
||||||
$allow_full_access_rebooking,
|
$allow_full_access_rebooking,
|
||||||
$allowed_product_types
|
$allowed_product_types,
|
||||||
|
$portal_purchases_enabled,
|
||||||
|
$rebooking_auto_approved
|
||||||
]);
|
]);
|
||||||
$success_message = "Impostazioni create con successo!";
|
$success_message = "Impostazioni create con successo!";
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE school_settings SET
|
UPDATE school_settings SET
|
||||||
header_color = ?, sidebar_color = ?, payment_methods = ?, currency_code = ?, enable_notifications = ?,
|
header_color = ?, sidebar_color = ?, payment_methods = ?, currency_code = ?, enable_notifications = ?,
|
||||||
allow_freeze_global = ?, freeze_max_days_global = ?, auto_propagate_on_purchase = ?,
|
allow_freeze_global = ?, freeze_max_days_global = ?, auto_propagate_on_purchase = ?,
|
||||||
allow_full_access_rebooking = ?, allowed_product_types = ?
|
allow_full_access_rebooking = ?, allowed_product_types = ?,
|
||||||
|
portal_purchases_enabled = ?, rebooking_auto_approved = ?
|
||||||
WHERE school_id = ?
|
WHERE school_id = ?
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
@@ -91,6 +112,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$auto_propagate_on_purchase,
|
$auto_propagate_on_purchase,
|
||||||
$allow_full_access_rebooking,
|
$allow_full_access_rebooking,
|
||||||
$allowed_product_types,
|
$allowed_product_types,
|
||||||
|
$portal_purchases_enabled,
|
||||||
|
$rebooking_auto_approved,
|
||||||
$school_id
|
$school_id
|
||||||
]);
|
]);
|
||||||
$success_message = "Impostazioni aggiornate con successo!";
|
$success_message = "Impostazioni aggiornate con successo!";
|
||||||
@@ -178,6 +201,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="portal_purchases_enabled" id="portal_enabled"
|
||||||
|
<?php echo ($settings['portal_purchases_enabled'] ?? 1) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="portal_enabled">
|
||||||
|
Acquisti pacchetti attivi nel portale YoGiBook
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Se disattivato, anche la propagazione automatica e i pagamenti vengono forzati a NO.</small>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
<div class="col-12 mb-4">
|
<div class="col-12 mb-4">
|
||||||
<label class="form-label">Metodi di pagamento accettati</label>
|
<label class="form-label">Metodi di pagamento accettati</label>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
@@ -231,6 +265,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" name="allow_full_access_rebooking" id="full_access" <?php echo ($settings['allow_full_access_rebooking'] ?? 1) ? 'checked' : ''; ?>>
|
<input class="form-check-input" type="checkbox" name="allow_full_access_rebooking" id="full_access" <?php echo ($settings['allow_full_access_rebooking'] ?? 1) ? 'checked' : ''; ?>>
|
||||||
@@ -262,6 +298,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-5">
|
||||||
|
|
||||||
|
<!-- NUOVA SEZIONE: Approvazioni e Lezioni -->
|
||||||
|
<h5 class="text-primary mb-4">Approvazioni e Lezioni</h5>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="rebooking_auto_approved" id="rebooking_auto"
|
||||||
|
<?php echo ($settings['rebooking_auto_approved'] ?? 1) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label fw-bold" for="rebooking_auto">
|
||||||
|
Riprogrammazione lezioni automatica (approvata subito)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">
|
||||||
|
Se disattivato, ogni richiesta di riprogrammazione dovrà essere approvata manualmente dalla segreteria.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center mt-5">
|
<div class="text-center mt-5">
|
||||||
<button type="submit" class="btn btn-primary btn-lg px-5">
|
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||||
Salva Impostazioni
|
Salva Impostazioni
|
||||||
@@ -286,6 +340,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
document.querySelector('input[name="freeze_max_days_global"]').disabled = !this.checked;
|
document.querySelector('input[name="freeze_max_days_global"]').disabled = !this.checked;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
const portal = document.getElementById('portal_enabled');
|
||||||
|
const propagate = document.getElementById('auto_propagate');
|
||||||
|
const stripe = document.getElementById('pay_stripe');
|
||||||
|
const paypal = document.getElementById('pay_paypal');
|
||||||
|
const manual = document.getElementById('pay_manual');
|
||||||
|
|
||||||
|
function syncPortalState() {
|
||||||
|
if (!portal) return;
|
||||||
|
|
||||||
|
const isEnabled = portal.checked;
|
||||||
|
|
||||||
|
// Propaga
|
||||||
|
if (propagate) {
|
||||||
|
propagate.disabled = !isEnabled;
|
||||||
|
if (!isEnabled) propagate.checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stripe + PayPal
|
||||||
|
if (stripe) {
|
||||||
|
stripe.disabled = !isEnabled;
|
||||||
|
if (!isEnabled) stripe.checked = false;
|
||||||
|
}
|
||||||
|
if (paypal) {
|
||||||
|
paypal.disabled = !isEnabled;
|
||||||
|
if (!isEnabled) paypal.checked = false;
|
||||||
|
}
|
||||||
|
if (manual) {
|
||||||
|
manual.disabled = !isEnabled;
|
||||||
|
if (!isEnabled) manual.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portal) {
|
||||||
|
portal.addEventListener('change', syncPortalState);
|
||||||
|
// Esegui subito (importante per il caricamento iniziale)
|
||||||
|
syncPortalState();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
|
|
||||||
|
// IMPORTANT: avoid redirect loop caused by require_school_context
|
||||||
|
define('SKIP_SCHOOL_CONTEXT', true);
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
|
||||||
error_log("SELECT_SCHOOL HIT - user=" . ($_SESSION['iduserlogin'] ?? 'NOUSER') . " school=" . var_export($_SESSION['school_id'] ?? null, true));
|
error_log("SELECT_SCHOOL HIT - user=" . ($_SESSION['iduserlogin'] ?? 'NOUSER') . " school=" . var_export($_SESSION['school_id'] ?? null, true));
|
||||||
@@ -160,7 +165,6 @@ if (count($userSchools) === 1) {
|
|||||||
$_SESSION['school_id'] = (int)$userSchools[0]['id'];
|
$_SESSION['school_id'] = (int)$userSchools[0]['id'];
|
||||||
$_SESSION['school_name'] = $userSchools[0]['name'];
|
$_SESSION['school_name'] = $userSchools[0]['name'];
|
||||||
$_SESSION['school_selected'] = 1;
|
$_SESSION['school_selected'] = 1;
|
||||||
|
|
||||||
if (function_exists('session')) {
|
if (function_exists('session')) {
|
||||||
session([
|
session([
|
||||||
'school_id' => (int)$userSchools[0]['id'],
|
'school_id' => (int)$userSchools[0]['id'],
|
||||||
@@ -179,7 +183,7 @@ if (count($userSchools) > 1 && !empty($_SESSION['school_id']) && !empty($_SESSIO
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,802 @@
|
|||||||
|
<?php
|
||||||
|
// sessions_archive.php - Archive Sessions (month-based, lightweight)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
include(dirname(__DIR__) . '/userarea/class/mailer.php'); // Your PHPMailer
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: ID utente non definito.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Optional) Set Italian locale for month names if needed
|
||||||
|
// setlocale(LC_TIME, 'it_IT.UTF-8', 'it_IT', 'Italian_Italy');
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name, logo FROM schools WHERE owner_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) {
|
||||||
|
die("Errore: Nessuna scuola trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = (int)$school['id'];
|
||||||
|
$school_name = $school['name'];
|
||||||
|
|
||||||
|
// === School users for "Add participant" dropdown ===
|
||||||
|
$stmtUsers = $pdo->prepare("
|
||||||
|
SELECT au.id, au.first_name, au.last_name, au.email
|
||||||
|
FROM user_schools us
|
||||||
|
JOIN auth_users au ON au.id = us.user_id
|
||||||
|
WHERE us.school_id = ?
|
||||||
|
AND us.status = 'active'
|
||||||
|
ORDER BY au.first_name, au.last_name
|
||||||
|
");
|
||||||
|
$stmtUsers->execute([$school_id]);
|
||||||
|
$schoolUsers = $stmtUsers->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// === Month filter (ym=YYYY-MM) ===
|
||||||
|
$ym = $_GET['ym'] ?? date('Y-m');
|
||||||
|
if (!preg_match('/^\d{4}\-\d{2}$/', $ym)) {
|
||||||
|
$ym = date('Y-m');
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDate = $ym . '-01';
|
||||||
|
$dtStart = new DateTime($startDate);
|
||||||
|
$dtEnd = (clone $dtStart)->modify('first day of next month');
|
||||||
|
$endDate = $dtEnd->format('Y-m-d');
|
||||||
|
|
||||||
|
// Month navigation
|
||||||
|
$prevYm = (clone $dtStart)->modify('-1 month')->format('Y-m');
|
||||||
|
$nextYm = (clone $dtStart)->modify('+1 month')->format('Y-m');
|
||||||
|
|
||||||
|
// Build months dropdown (last 24 months + next 2 months)
|
||||||
|
$monthsList = [];
|
||||||
|
$base = new DateTime(date('Y-m-01'));
|
||||||
|
for ($i = 24; $i >= 1; $i--) {
|
||||||
|
$m = (clone $base)->modify("-{$i} month")->format('Y-m');
|
||||||
|
$monthsList[] = $m;
|
||||||
|
}
|
||||||
|
$monthsList[] = $base->format('Y-m');
|
||||||
|
$monthsList[] = (clone $base)->modify('+1 month')->format('Y-m');
|
||||||
|
$monthsList[] = (clone $base)->modify('+2 month')->format('Y-m');
|
||||||
|
|
||||||
|
// === Actions (same as future_sessions) ===
|
||||||
|
$feedback = '';
|
||||||
|
$showEmailModal = false;
|
||||||
|
$emailData = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
|
// Delete session (optionally with email)
|
||||||
|
if ($action === 'delete_session') {
|
||||||
|
$session_id = (int)($_POST['session_id'] ?? 0);
|
||||||
|
$send_email = isset($_POST['send_email']) && $_POST['send_email'] === '1';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT cs.id, cs.session_date, cs.start_time, cs.end_time,
|
||||||
|
c.name AS class_name, ct.level
|
||||||
|
FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$session_id, $school_id]);
|
||||||
|
$session = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($session) {
|
||||||
|
$recipients = [];
|
||||||
|
if ($send_email) {
|
||||||
|
$stmt2 = $pdo->prepare("
|
||||||
|
SELECT au.email, au.first_name
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN auth_users au ON sb.user_id = au.id
|
||||||
|
WHERE sb.session_id = ?
|
||||||
|
");
|
||||||
|
$stmt2->execute([$session_id]);
|
||||||
|
$recipients = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If email requested and recipients exist -> open email modal, do not delete yet
|
||||||
|
if ($send_email && !empty($recipients)) {
|
||||||
|
$showEmailModal = true;
|
||||||
|
$emailData = [
|
||||||
|
'session_id' => $session_id,
|
||||||
|
'class_name' => $session['class_name'],
|
||||||
|
'level' => $session['level'] ?? '',
|
||||||
|
'date' => date('d/m/Y', strtotime($session['session_date'])),
|
||||||
|
'time' => substr($session['start_time'], 0, 5) . ' - ' . substr($session['end_time'], 0, 5),
|
||||||
|
'recipients' => $recipients
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
// Delete immediately
|
||||||
|
$stmtDel = $pdo->prepare("
|
||||||
|
DELETE cs FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmtDel->execute([$session_id, $school_id]);
|
||||||
|
|
||||||
|
$feedback = '<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
Lezione cancellata con successo!
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send cancellation email then delete
|
||||||
|
elseif ($action === 'send_cancellation_email') {
|
||||||
|
$session_id = (int)($_POST['session_id'] ?? 0);
|
||||||
|
$body_text = $_POST['email_body'] ?? '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT c.name AS class_name, ct.level, cs.session_date, cs.start_time, cs.end_time
|
||||||
|
FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$session_id, $school_id]);
|
||||||
|
$session = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$class_info = $session ? ($session['class_name'] . ($session['level'] ? ' (' . ucfirst($session['level']) . ')' : '')) : '';
|
||||||
|
$date = $session ? date('d/m/Y', strtotime($session['session_date'])) : '';
|
||||||
|
$time = $session ? substr($session['start_time'], 0, 5) . '-' . substr($session['end_time'], 0, 5) : '';
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT au.email, au.first_name
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN auth_users au ON sb.user_id = au.id
|
||||||
|
WHERE sb.session_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$session_id]);
|
||||||
|
$recipients = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$subject = "Lezione cancellata - {$school_name}";
|
||||||
|
$sent = 0;
|
||||||
|
|
||||||
|
foreach ($recipients as $r) {
|
||||||
|
$personal_body = str_replace(
|
||||||
|
['{nome}', '{classe}', '{data}', '{ora}'],
|
||||||
|
[$r['first_name'] ?: 'Gentile utente', $class_info, $date, $time],
|
||||||
|
$body_text
|
||||||
|
);
|
||||||
|
$result = sendEmail($r['email'], $subject, $personal_body);
|
||||||
|
if (!empty($result['success'])) $sent++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmtDel = $pdo->prepare("
|
||||||
|
DELETE cs FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmtDel->execute([$session_id, $school_id]);
|
||||||
|
|
||||||
|
$feedback = "<div class='alert alert-success alert-dismissible fade show'>
|
||||||
|
Email inviate a {$sent} partecipanti.
|
||||||
|
<button type='button' class='btn-close' data-bs-dismiss='alert'></button>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip email then delete
|
||||||
|
elseif ($action === 'skip_cancellation_email') {
|
||||||
|
$session_id = (int)($_POST['session_id'] ?? 0);
|
||||||
|
|
||||||
|
$stmtDel = $pdo->prepare("
|
||||||
|
DELETE cs FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmtDel->execute([$session_id, $school_id]);
|
||||||
|
|
||||||
|
$feedback = "<div class='alert alert-success alert-dismissible fade show'>
|
||||||
|
Lezione cancellata (email non inviata).
|
||||||
|
<button type='button' class='btn-close' data-bs-dismiss='alert'></button>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add booking
|
||||||
|
elseif ($action === 'add_booking') {
|
||||||
|
$session_id = (int)($_POST['session_id'] ?? 0);
|
||||||
|
$user_id = (int)($_POST['user_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($session_id <= 0 || $user_id <= 0) {
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Dati non validi.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT cs.id, ct.max_capacity,
|
||||||
|
(SELECT COUNT(*) FROM session_bookings sb
|
||||||
|
WHERE sb.session_id = cs.id AND sb.status IN ('booked','attended','rescheduled')
|
||||||
|
) AS booked_count
|
||||||
|
FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE cs.id = ? AND c.school_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$session_id, $school_id]);
|
||||||
|
$info = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$info) {
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Lezione non trovata o non autorizzata.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT 1
|
||||||
|
FROM user_schools
|
||||||
|
WHERE user_id = ? AND school_id = ? AND status = 'active'
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$user_id, $school_id]);
|
||||||
|
$isMember = (bool)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if (!$isMember) {
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Utente non associato a questa scuola.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("SELECT 1 FROM session_bookings WHERE session_id = ? AND user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$session_id, $user_id]);
|
||||||
|
$already = (bool)$stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($already) {
|
||||||
|
$feedback = '<div class="alert alert-warning alert-dismissible fade show">Questo utente è già presente nella lezione.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
} else {
|
||||||
|
$max = (int)($info['max_capacity'] ?? 0);
|
||||||
|
$cnt = (int)($info['booked_count'] ?? 0);
|
||||||
|
|
||||||
|
if ($max > 0 && $cnt >= $max) {
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Lezione piena: capienza massima raggiunta.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
} else {
|
||||||
|
$stmtIns = $pdo->prepare("
|
||||||
|
INSERT INTO session_bookings (session_id, user_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'booked', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmtIns->execute([$session_id, $user_id]);
|
||||||
|
|
||||||
|
$feedback = '<div class="alert alert-success alert-dismissible fade show">Partecipante aggiunto alla lezione!<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark lost
|
||||||
|
elseif ($action === 'mark_lost') {
|
||||||
|
$booking_id = (int)($_POST['booking_id'] ?? 0);
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
SET sb.status = 'missed'
|
||||||
|
WHERE sb.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$booking_id, $school_id]);
|
||||||
|
$feedback = '<div class="alert alert-warning alert-dismissible fade show">Presenza segnata come persa.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel booking
|
||||||
|
elseif ($action === 'cancel_booking') {
|
||||||
|
$booking_id = (int)($_POST['booking_id'] ?? 0);
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
DELETE sb FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
WHERE sb.id = ? AND c.school_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$booking_id, $school_id]);
|
||||||
|
$feedback = '<div class="alert alert-danger alert-dismissible fade show">Prenotazione rimossa.<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep current month in redirect-less flow: we simply render again with the same ym from GET.
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Sessions of selected month ===
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
cs.id AS session_id,
|
||||||
|
cs.session_date,
|
||||||
|
cs.start_time,
|
||||||
|
cs.end_time,
|
||||||
|
c.name AS class_name,
|
||||||
|
ct.level,
|
||||||
|
ct.room_name,
|
||||||
|
ct.max_capacity,
|
||||||
|
t.first_name,
|
||||||
|
t.last_name,
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM session_bookings sb
|
||||||
|
WHERE sb.session_id = cs.id
|
||||||
|
AND sb.status IN ('booked','attended','rescheduled')
|
||||||
|
) AS booked_count,
|
||||||
|
(SELECT GROUP_CONCAT(
|
||||||
|
CONCAT(sb.id,'|||',au.id,'|||',au.first_name,' ',au.last_name,'|||',au.email,'|||',COALESCE(au.phone,''),'|||',sb.status)
|
||||||
|
SEPARATOR ';;;'
|
||||||
|
)
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN auth_users au ON sb.user_id = au.id
|
||||||
|
WHERE sb.session_id = cs.id
|
||||||
|
AND sb.status IN ('booked','attended','rescheduled','missed')
|
||||||
|
) AS booked_students_details
|
||||||
|
FROM class_sessions cs
|
||||||
|
JOIN class_types ct ON cs.class_type_id = ct.id
|
||||||
|
JOIN classes c ON ct.class_id = c.id
|
||||||
|
LEFT JOIN teachers t ON cs.teacher_id = t.id
|
||||||
|
WHERE c.school_id = ?
|
||||||
|
AND cs.session_date >= ?
|
||||||
|
AND cs.session_date < ?
|
||||||
|
ORDER BY cs.session_date, cs.start_time
|
||||||
|
");
|
||||||
|
$stmt->execute([$school_id, $startDate, $endDate]);
|
||||||
|
$sessions = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function humanMonthLabel($ym)
|
||||||
|
{
|
||||||
|
$dt = DateTime::createFromFormat('Y-m', $ym);
|
||||||
|
if (!$dt) return $ym;
|
||||||
|
return ucfirst($dt->format('F Y')); // if locale not set, this will be English
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Archivio Lezioni - <?php echo htmlspecialchars($school_name); ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
<link rel="stylesheet" href="assets/plugins/select2/css/select2.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
|
||||||
|
<div class="card radius-10 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="me-4">
|
||||||
|
<img src="<?php echo $school['logo'] ? htmlspecialchars($school['logo']) : 'assets/images/default-school-logo.png'; ?>"
|
||||||
|
alt="Logo" class="rounded-circle" style="width: 90px; height: 90px; object-fit: cover;">
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h4 class="mb-1">Archivio Lezioni - <?php echo htmlspecialchars($school_name); ?></h4>
|
||||||
|
<p class="mb-0 text-muted">Vedi le lezioni mese per mese con le stesse funzioni (prenotati, P/C, cancellazione con email).</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="future_sessions.php" class="btn btn-outline-primary">← Future</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php echo $feedback; ?>
|
||||||
|
|
||||||
|
<div class="card radius-10 mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-wrap align-items-end justify-content-between gap-3">
|
||||||
|
<div>
|
||||||
|
<div class="fw-bold mb-1">Mese selezionato</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" href="?ym=<?php echo htmlspecialchars($prevYm); ?>">
|
||||||
|
← <?php echo htmlspecialchars($prevYm); ?>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-outline-secondary btn-sm" href="?ym=<?php echo htmlspecialchars($nextYm); ?>">
|
||||||
|
<?php echo htmlspecialchars($nextYm); ?> →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="GET" class="d-flex gap-2 align-items-end">
|
||||||
|
<div>
|
||||||
|
<label class="form-label mb-1">Seleziona mese</label>
|
||||||
|
<select name="ym" class="form-select" style="min-width:220px;">
|
||||||
|
<?php foreach ($monthsList as $m): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($m); ?>" <?php echo $m === $ym ? 'selected' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($m); ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Apri</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($sessions)): ?>
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-body text-center py-5">
|
||||||
|
<h5 class="text-muted">
|
||||||
|
Nessuna lezione trovata per il mese selezionato (<?php echo htmlspecialchars($ym); ?>).
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="card radius-10 mb-4">
|
||||||
|
<div class="card-header bg-primary text-white d-flex align-items-center justify-content-between">
|
||||||
|
<h5 class="mb-0">Archivio mese: <?php echo htmlspecialchars(humanMonthLabel($ym)); ?></h5>
|
||||||
|
<span class="badge bg-light text-dark"><?php echo count($sessions); ?> lezioni</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Orario</th>
|
||||||
|
<th>Classe</th>
|
||||||
|
<th>Livello</th>
|
||||||
|
<th>Sala</th>
|
||||||
|
<th>Insegnante</th>
|
||||||
|
<th>Prenotati</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($sessions as $s): ?>
|
||||||
|
<tr id="<?php echo (int)$s['session_id']; ?>">
|
||||||
|
<td><?php echo date('d/m/Y (l)', strtotime($s['session_date'])); ?></td>
|
||||||
|
<td><?php echo substr($s['start_time'], 0, 5) . ' - ' . substr($s['end_time'], 0, 5); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($s['class_name']); ?></td>
|
||||||
|
<td><?php echo ucfirst($s['level'] ?? '-'); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($s['room_name'] ?? '—'); ?></td>
|
||||||
|
<td><?php echo $s['first_name'] ? htmlspecialchars($s['first_name'] . ' ' . $s['last_name']) : '<em>Non assegnato</em>'; ?></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm <?php echo ((int)$s['booked_count'] > 0) ? 'btn-success' : 'btn-outline-secondary'; ?>"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#studentsModal-<?php echo (int)$s['session_id']; ?>">
|
||||||
|
<?php
|
||||||
|
$bc = (int)$s['booked_count'];
|
||||||
|
$max = (int)($s['max_capacity'] ?? 0);
|
||||||
|
echo ($max > 0) ? "{$bc}/{$max}" : "{$bc}/∞";
|
||||||
|
?>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#deleteModal-<?php echo (int)$s['session_id']; ?>">
|
||||||
|
<i class="bx bx-trash"></i> Cancella
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALS (month-limited, OK) -->
|
||||||
|
<?php foreach ($sessions as $session): ?>
|
||||||
|
<?php
|
||||||
|
$students_list = [];
|
||||||
|
if (!empty($session['booked_students_details'])) {
|
||||||
|
foreach (explode(';;;', $session['booked_students_details']) as $entry) {
|
||||||
|
if (empty(trim($entry))) continue;
|
||||||
|
$parts = explode('|||', $entry);
|
||||||
|
$students_list[] = [
|
||||||
|
'booking_id' => (int)($parts[0] ?? 0),
|
||||||
|
'user_id' => (int)($parts[1] ?? 0),
|
||||||
|
'name' => $parts[2] ?? '',
|
||||||
|
'email' => $parts[3] ?? '',
|
||||||
|
'phone' => $parts[4] ?? '—',
|
||||||
|
'status' => $parts[5] ?? 'booked'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Students modal -->
|
||||||
|
<div class="modal fade" id="studentsModal-<?php echo (int)$session['session_id']; ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
Prenotati - <?php echo htmlspecialchars($session['class_name']); ?>
|
||||||
|
del <?php echo date('d/m/Y', strtotime($session['session_date'])); ?>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<?php
|
||||||
|
$bookedUserIds = [];
|
||||||
|
foreach ($students_list as $st) {
|
||||||
|
if (!empty($st['user_id'])) $bookedUserIds[] = (int)$st['user_id'];
|
||||||
|
}
|
||||||
|
$availableUsers = array_values(array_filter($schoolUsers, function ($u) use ($bookedUserIds) {
|
||||||
|
return !in_array((int)$u['id'], $bookedUserIds, true);
|
||||||
|
}));
|
||||||
|
|
||||||
|
$maxCap = (int)($session['max_capacity'] ?? 0);
|
||||||
|
$bookedCnt = (int)($session['booked_count'] ?? 0);
|
||||||
|
$isFull = ($maxCap > 0 && $bookedCnt >= $maxCap);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="mb-3 p-3 border rounded bg-light">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-2">
|
||||||
|
<strong>Aggiungi partecipante</strong>
|
||||||
|
<small class="text-muted">
|
||||||
|
Capienza: <?php echo $maxCap > 0 ? ($bookedCnt . '/' . $maxCap) : ($bookedCnt . '/∞'); ?>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($isFull): ?>
|
||||||
|
<div class="text-danger small">Lezione piena: non puoi aggiungere altri partecipanti.</div>
|
||||||
|
<?php elseif (empty($availableUsers)): ?>
|
||||||
|
<div class="text-muted small">Nessun utente disponibile (tutti già presenti o nessun utente associato alla scuola).</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<form method="POST" class="row g-2 align-items-end">
|
||||||
|
<input type="hidden" name="action" value="add_booking">
|
||||||
|
<input type="hidden" name="session_id" value="<?php echo (int)$session['session_id']; ?>">
|
||||||
|
|
||||||
|
<div class="col-12 col-md-8">
|
||||||
|
<label class="form-label mb-1">Seleziona utente</label>
|
||||||
|
<select name="user_id" class="form-select select2-user" required data-placeholder="Cerca per nome o email">
|
||||||
|
<option value=""></option>
|
||||||
|
<?php foreach ($availableUsers as $u): ?>
|
||||||
|
<option value="<?php echo (int)$u['id']; ?>">
|
||||||
|
<?php
|
||||||
|
$full = trim(($u['first_name'] ?? '') . ' ' . ($u['last_name'] ?? ''));
|
||||||
|
echo htmlspecialchars($full . ' — ' . ($u['email'] ?? ''));
|
||||||
|
?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<button type="submit" class="btn btn-primary w-100">
|
||||||
|
<i class="bx bx-plus"></i> Aggiungi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($students_list)): ?>
|
||||||
|
<p class="text-center text-muted">Nessuno prenotato.</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Telefono</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($students_list as $stud): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo htmlspecialchars($stud['name']); ?></strong></td>
|
||||||
|
<td><?php echo htmlspecialchars($stud['email']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($stud['phone']); ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge <?php echo $stud['status'] === 'missed' ? 'bg-warning' : 'bg-success'; ?>">
|
||||||
|
<?php echo $stud['status'] === 'missed' ? 'Persa' : 'Prenotato'; ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($stud['status'] !== 'missed'): ?>
|
||||||
|
<button type="button" class="btn btn-sm btn-warning me-1"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#lostModal-<?php echo (int)$stud['booking_id']; ?>">P</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#cancelModal-<?php echo (int)$stud['booking_id']; ?>">C</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- P/C modals -->
|
||||||
|
<?php foreach ($students_list as $stud): ?>
|
||||||
|
<div class="modal fade" id="lostModal-<?php echo (int)$stud['booking_id']; ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning text-dark">
|
||||||
|
<h5 class="modal-title">Segna come Persa</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Confermi di segnare come <strong>persa</strong> la lezione per <strong><?php echo htmlspecialchars($stud['name']); ?></strong>?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<form method="POST" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="mark_lost">
|
||||||
|
<input type="hidden" name="booking_id" value="<?php echo (int)$stud['booking_id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-warning">Sì, segna persa</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="cancelModal-<?php echo (int)$stud['booking_id']; ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h5 class="modal-title">Rimuovi Prenotazione</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Confermi di rimuovere <strong><?php echo htmlspecialchars($stud['name']); ?></strong> dalla lezione?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<form method="POST" style="display:inline;">
|
||||||
|
<input type="hidden" name="action" value="cancel_booking">
|
||||||
|
<input type="hidden" name="booking_id" value="<?php echo (int)$stud['booking_id']; ?>">
|
||||||
|
<button type="submit" class="btn btn-danger">Sì, rimuovi</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<!-- Delete session modal -->
|
||||||
|
<div class="modal fade" id="deleteModal-<?php echo (int)$session['session_id']; ?>" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h5 class="modal-title">Cancella Lezione</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Confermi la cancellazione della lezione:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Classe:</strong> <?php echo htmlspecialchars($session['class_name']); ?></li>
|
||||||
|
<li><strong>Data:</strong> <?php echo date('d/m/Y (l)', strtotime($session['session_date'])); ?></li>
|
||||||
|
<li><strong>Orario:</strong> <?php echo substr($session['start_time'], 0, 5); ?> - <?php echo substr($session['end_time'], 0, 5); ?></li>
|
||||||
|
<li><strong>Prenotati:</strong> <?php echo (int)$session['booked_count']; ?></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<?php if ((int)$session['booked_count'] > 0): ?>
|
||||||
|
<div class="form-check mt-3">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
name="send_email" value="1"
|
||||||
|
id="sendEmail-<?php echo (int)$session['session_id']; ?>">
|
||||||
|
<label class="form-check-label text-primary fw-bold"
|
||||||
|
for="sendEmail-<?php echo (int)$session['session_id']; ?>">
|
||||||
|
Invia email di avviso ai <?php echo (int)$session['booked_count']; ?> partecipanti?
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<input type="hidden" name="action" value="delete_session">
|
||||||
|
<input type="hidden" name="session_id" value="<?php echo (int)$session['session_id']; ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-danger">Cancella Lezione</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<!-- Email modal -->
|
||||||
|
<?php if ($showEmailModal && $emailData): ?>
|
||||||
|
<div class="modal fade show" id="emailModal" style="display:block;" tabindex="-1" aria-modal="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-header bg-warning text-dark">
|
||||||
|
<h5 class="modal-title">Invia Avviso Cancellazione</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Stai per cancellare la lezione. Vuoi inviare l'email a <strong><?php echo count($emailData['recipients']); ?> partecipanti</strong>?</p>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Oggetto</label>
|
||||||
|
<input type="text" class="form-control" value="Lezione cancellata - <?php echo htmlspecialchars($school_name); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Messaggio (modificabile)</label>
|
||||||
|
<textarea name="email_body" class="form-control" rows="12" required>Ciao {nome},
|
||||||
|
|
||||||
|
ti informiamo con dispiacere che la lezione di <?php echo htmlspecialchars($emailData['class_name']); ?><?php echo $emailData['level'] ? ' (' . ucfirst($emailData['level']) . ')' : ''; ?>
|
||||||
|
del <?php echo $emailData['date']; ?> alle ore <?php echo $emailData['time']; ?> è stata cancellata.
|
||||||
|
|
||||||
|
Ci scusiamo per il disagio.
|
||||||
|
|
||||||
|
Grazie per la comprensione,
|
||||||
|
|
||||||
|
<?php echo htmlspecialchars($school_name); ?></textarea>
|
||||||
|
<small class="text-muted">{nome} verrà sostituito con il nome del destinatario.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="session_id" value="<?php echo (int)$emailData['session_id']; ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" name="action" value="skip_cancellation_email" class="btn btn-secondary">
|
||||||
|
Salta invio
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="action" value="send_cancellation_email" class="btn btn-success">
|
||||||
|
Invia Email
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-backdrop fade show"></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="assets/plugins/select2/js/select2.min.js"></script>
|
||||||
|
<script>
|
||||||
|
/* Reminder: Select2 inside any modal must use dropdownParent. */
|
||||||
|
document.addEventListener('shown.bs.modal', function(e) {
|
||||||
|
const modal = e.target;
|
||||||
|
if (!window.jQuery || !jQuery.fn || !jQuery.fn.select2) return;
|
||||||
|
|
||||||
|
modal.querySelectorAll('.select2-user').forEach(function(sel) {
|
||||||
|
const $sel = jQuery(sel);
|
||||||
|
if ($sel.hasClass('select2-hidden-accessible')) return;
|
||||||
|
|
||||||
|
$sel.select2({
|
||||||
|
dropdownParent: jQuery(modal),
|
||||||
|
width: '100%',
|
||||||
|
placeholder: sel.dataset.placeholder || 'Seleziona...'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
define('SKIP_SCHOOL_CONTEXT', true);
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
$pdo = $dbHandler->getConnection();
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
<?php
|
||||||
|
// teacher_list.php file
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
require_once 'class/mailer.php';
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: utente non loggato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera scuola corrente
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name, owner_id, email AS school_email
|
||||||
|
FROM schools
|
||||||
|
WHERE owner_id = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) {
|
||||||
|
die("Errore: nessuna scuola trovata per questo proprietario.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$school_id = $school['id'];
|
||||||
|
$school_name = $school['name'];
|
||||||
|
$school_email = $school['school_email'];
|
||||||
|
|
||||||
|
// Messaggi
|
||||||
|
$success = $_GET['success'] ?? null;
|
||||||
|
$error = $_GET['error'] ?? null;
|
||||||
|
|
||||||
|
// COLLEGAMENTO TRAMITE CODICE
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'link_by_code') {
|
||||||
|
$unique_code = trim($_POST['unique_code'] ?? '');
|
||||||
|
$link_id = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
|
|
||||||
|
if (empty($unique_code)) {
|
||||||
|
$error = "Inserisci un codice univoco valido.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT t.id AS teacher_id, u.first_name, u.last_name, u.email
|
||||||
|
FROM teachers t
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE t.unique_code = ?
|
||||||
|
");
|
||||||
|
$stmt->execute([$unique_code]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$teacher) {
|
||||||
|
$error = "Nessun insegnante trovato con questo codice.";
|
||||||
|
} else {
|
||||||
|
$check = $pdo->prepare("SELECT id FROM teacher_schools WHERE teacher_id = ? AND school_id = ?");
|
||||||
|
$check->execute([$teacher['teacher_id'], $school_id]);
|
||||||
|
|
||||||
|
if ($check->fetch()) {
|
||||||
|
$error = "Insegnante già collegata.";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO teacher_schools
|
||||||
|
(teacher_id, school_id, status, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'pending', NOW(), NOW())
|
||||||
|
");
|
||||||
|
$stmt->execute([$teacher['teacher_id'], $school_id]);
|
||||||
|
|
||||||
|
// Email richiesta
|
||||||
|
$subject = "Richiesta collegamento a {$school_name}";
|
||||||
|
$body = "
|
||||||
|
<h2>Ciao {$teacher['first_name']},</h2>
|
||||||
|
<p>{$school_name} vorrebbe collegarti alla sua scuola su YogiBoook.</p>
|
||||||
|
<p style='margin:30px 0;'>
|
||||||
|
<a href='http://localhost/yogiboook/public/userarea/confirm_link.php?code=" . urlencode($unique_code) . "&school={$school_id}'
|
||||||
|
style='background:#0d6efd;color:white;padding:12px 24px;text-decoration:none;border-radius:6px;'>
|
||||||
|
Accetta
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>Se non riconosci questa richiesta, ignora l'email.</p>
|
||||||
|
";
|
||||||
|
|
||||||
|
$result = sendEmail($teacher['email'], $subject, $body);
|
||||||
|
|
||||||
|
$success = $result['success']
|
||||||
|
? "Richiesta inviata!"
|
||||||
|
: "Collegamento creato, ma errore email: " . $result['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LISTA INSEGNANTI
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.id, t.user_id, t.phone, t.description, t.specializations, t.profile_picture,
|
||||||
|
u.first_name, u.last_name, u.email,
|
||||||
|
ts.status AS link_status, ts.created_at AS linked_at,
|
||||||
|
(t.created_by = ?) AS can_edit
|
||||||
|
FROM teacher_schools ts
|
||||||
|
JOIN teachers t ON ts.teacher_id = t.id
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE ts.school_id = ? AND ts.status IN ('active','pending')
|
||||||
|
ORDER BY u.last_name, u.first_name
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin, $school_id]);
|
||||||
|
$teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Insegnanti - <?= htmlspecialchars($school_name) ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||||
|
<style>
|
||||||
|
.quill-wrapper {
|
||||||
|
min-height: 260px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<h4 class="mb-4">Insegnanti di <?= htmlspecialchars($school_name) ?></h4>
|
||||||
|
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show"><?= $success ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show"><?= htmlspecialchars($error) ?><button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button class="btn btn-primary me-2" data-bs-toggle="modal" data-bs-target="#addTeacherModal">
|
||||||
|
<i class="bx bx-plus"></i> Nuova insegnante
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#linkTeacherModal">
|
||||||
|
<i class="bx bx-link"></i> Collega esistente
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($teachers)): ?>
|
||||||
|
<div class="text-center py-5 text-muted">
|
||||||
|
<i class="bx bx-user-x fs-1 mb-3 opacity-50"></i>
|
||||||
|
<p>Nessuna insegnante collegata.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Foto</th>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Telefono</th>
|
||||||
|
<th>Stato</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($teachers as $t): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<?php if ($t['profile_picture']): ?>
|
||||||
|
<img src="<?= htmlspecialchars($t['profile_picture']) ?>" class="rounded" width="50" height="50" style="object-fit:cover;">
|
||||||
|
<?php else: ?>
|
||||||
|
<i class="bx bx-user-circle fs-3 text-muted"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($t['first_name'] . ' ' . $t['last_name']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($t['email']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($t['phone'] ?: '—') ?></td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-<?= $t['link_status'] === 'active' ? 'success' : 'warning' ?>">
|
||||||
|
<?= ucfirst($t['link_status']) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ($t['can_edit']): ?>
|
||||||
|
<a href="teacher_profile.php?id=<?= $t['id'] ?>" class="btn btn-sm btn-warning"><i class="bx bx-edit"></i></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALE AGGIUNGI -->
|
||||||
|
<div class="modal fade" id="addTeacherModal" tabindex="-1" aria-labelledby="addTeacherLabel">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="addTeacherLabel">Nuova insegnante</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="add_teacher.php" method="POST" enctype="multipart/form-data" id="addTeacherForm">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="school_id" value="<?= $school_id ?>">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6"><label>Nome *</label><input type="text" name="first_name" class="form-control" required></div>
|
||||||
|
<div class="col-md-6"><label>Cognome *</label><input type="text" name="last_name" class="form-control" required></div>
|
||||||
|
<div class="col-md-6"><label>Email *</label><input type="email" name="email" class="form-control" required></div>
|
||||||
|
<div class="col-md-6"><label>Telefono</label><input type="tel" name="phone" class="form-control"></div>
|
||||||
|
<div class="col-12"><label>Specializzazioni</label><textarea name="specializations" class="form-control" rows="2"></textarea></div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label>Descrizione</label>
|
||||||
|
<div class="quill-wrapper">
|
||||||
|
<div id="quill-add-editor"></div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="description" id="add-desc-hidden">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6"><label>Foto</label><input type="file" name="profile_picture" class="form-control" accept="image/*"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Crea</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MODALE COLLEGAMENTO -->
|
||||||
|
<div class="modal fade" id="linkTeacherModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5>Collega insegnante esistente</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="link_by_code">
|
||||||
|
<label>Codice univoco</label>
|
||||||
|
<input type="text" name="unique_code" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Collega</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Quill per modale - inizializza DOPO apertura modale
|
||||||
|
const quillAdd = new Quill('#quill-add-editor', {
|
||||||
|
theme: 'snow',
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{
|
||||||
|
'color': []
|
||||||
|
}, {
|
||||||
|
'background': []
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'list': 'ordered'
|
||||||
|
}, {
|
||||||
|
'list': 'bullet'
|
||||||
|
}],
|
||||||
|
['link', 'clean']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('addTeacherModal').addEventListener('shown.bs.modal', function() {
|
||||||
|
quillAdd.update(); // Forza refresh dopo apertura
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('addTeacherForm').addEventListener('submit', function(e) {
|
||||||
|
document.getElementById('add-desc-hidden').value = quillAdd.root.innerHTML;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,486 @@
|
|||||||
|
<?php
|
||||||
|
// teacher_page.php
|
||||||
|
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// QR Code library
|
||||||
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Endroid\QrCode\QrCode;
|
||||||
|
use Endroid\QrCode\Writer\PngWriter;
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: ID utente non definito.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QR helper compatible with older/newer Endroid versions (best-effort)
|
||||||
|
*/
|
||||||
|
function writeQrPng($text, $filename, $size = 150, $margin = 10)
|
||||||
|
{
|
||||||
|
// Your installed version seems to require text in constructor
|
||||||
|
$qrCode = new \Endroid\QrCode\QrCode($text);
|
||||||
|
|
||||||
|
if (method_exists($qrCode, 'setSize')) {
|
||||||
|
$qrCode->setSize($size);
|
||||||
|
} elseif (method_exists($qrCode, 'setModuleSize')) {
|
||||||
|
$module = max(3, (int)round($size / 25));
|
||||||
|
$qrCode->setModuleSize($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_exists($qrCode, 'setMargin')) {
|
||||||
|
$qrCode->setMargin($margin);
|
||||||
|
} elseif (method_exists($qrCode, 'setPadding')) {
|
||||||
|
$qrCode->setPadding($margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer = new \Endroid\QrCode\Writer\PngWriter();
|
||||||
|
|
||||||
|
if (method_exists($writer, 'writeFile')) {
|
||||||
|
$writer->writeFile($qrCode, $filename);
|
||||||
|
} else {
|
||||||
|
$result = $writer->write($qrCode);
|
||||||
|
if (is_object($result) && method_exists($result, 'saveToFile')) {
|
||||||
|
$result->saveToFile($filename);
|
||||||
|
} else {
|
||||||
|
file_put_contents($filename, (string)$result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateUniqueCode($pdo, $length = 16)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
$code = bin2hex(random_bytes($length / 2));
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM teachers WHERE unique_code = ?");
|
||||||
|
$stmt->execute([$code]);
|
||||||
|
} while ($stmt->fetchColumn() > 0);
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect view mode
|
||||||
|
* - Owner view: teacher_page.php?id=TEACHER_ID (teachers.id)
|
||||||
|
* - Teacher self view: teacher_page.php
|
||||||
|
*/
|
||||||
|
$teacher_id = (int)($_GET['id'] ?? 0);
|
||||||
|
$is_owner_view = ($teacher_id > 0);
|
||||||
|
|
||||||
|
$success_message = $error = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1) LOAD teacher row
|
||||||
|
*/
|
||||||
|
if ($is_owner_view) {
|
||||||
|
// OWNER VIEW: load teacher by teachers.id only if owner has rights
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
u.first_name, u.last_name, u.email
|
||||||
|
FROM teachers t
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
JOIN teacher_schools ts ON ts.teacher_id = t.id
|
||||||
|
JOIN schools s ON s.id = ts.school_id
|
||||||
|
WHERE t.id = ?
|
||||||
|
AND s.owner_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$teacher_id, $iduserlogin]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$teacher) {
|
||||||
|
die("Errore: insegnante non trovata o non hai permessi.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TEACHER SELF VIEW: load by logged user
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
u.first_name, u.last_name, u.email
|
||||||
|
FROM auth_users u
|
||||||
|
LEFT JOIN teachers t ON t.user_id = u.id
|
||||||
|
WHERE u.id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// If not exists in teachers, CREATE IT before showing the form (auto-create)
|
||||||
|
if (empty($teacher['id'])) {
|
||||||
|
// ✅ SOLO QUI: quando auto-crei il profilo teacher (SELF VIEW), aggiungi created_by = iduserlogin
|
||||||
|
|
||||||
|
$unique_code = generateUniqueCode($pdo);
|
||||||
|
|
||||||
|
$stmtIns = $pdo->prepare("
|
||||||
|
INSERT INTO teachers (user_id, unique_code, phone, description, specializations, profile_picture, status, created_by)
|
||||||
|
VALUES (?, ?, NULL, '', '', '', 'active', ?)
|
||||||
|
");
|
||||||
|
$ok = $stmtIns->execute([$iduserlogin, $unique_code, $iduserlogin]);
|
||||||
|
|
||||||
|
|
||||||
|
if (!$ok) {
|
||||||
|
die("Errore: impossibile creare il profilo insegnante.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload teacher after insert
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
u.first_name, u.last_name, u.email
|
||||||
|
FROM teachers t
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE t.user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now teacher MUST exist (in owner view and in self view due to auto-create)
|
||||||
|
*/
|
||||||
|
$is_new = empty($teacher['id']); // should be false at this point
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2) HANDLE POST (save)
|
||||||
|
*/
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
// target user is the teacher being edited
|
||||||
|
$target_user_id = $is_owner_view ? (int)$teacher['user_id'] : (int)$iduserlogin;
|
||||||
|
|
||||||
|
$first_name = trim($_POST['first_name'] ?? '');
|
||||||
|
$last_name = trim($_POST['last_name'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$specializations = trim($_POST['specializations'] ?? '');
|
||||||
|
$status = (($_POST['status'] ?? 'active') === 'active') ? 'active' : 'inactive';
|
||||||
|
|
||||||
|
// Update auth_users names for the target teacher
|
||||||
|
$stmt = $pdo->prepare("UPDATE auth_users SET first_name = ?, last_name = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$first_name, $last_name, $target_user_id]);
|
||||||
|
|
||||||
|
// Photo upload (use target user id in filename)
|
||||||
|
$profile_picture = $teacher['profile_picture'] ?? '';
|
||||||
|
if (!empty($_FILES['profile_picture']['name']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
|
||||||
|
$ext = strtolower(pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$new_name = "phototeachers/{$target_user_id}-" . time() . "-profile.$ext";
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $new_name)) {
|
||||||
|
if ($profile_picture && file_exists($profile_picture)) {
|
||||||
|
@unlink($profile_picture);
|
||||||
|
}
|
||||||
|
$profile_picture = $new_name;
|
||||||
|
} else {
|
||||||
|
$error = "Errore caricamento foto.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Solo JPG, PNG, GIF ammessi.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$error) {
|
||||||
|
// Update teachers row (always exists at this point)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE teachers
|
||||||
|
SET phone = ?, description = ?, specializations = ?, profile_picture = ?, status = ?
|
||||||
|
WHERE user_id = ?
|
||||||
|
");
|
||||||
|
$success = $stmt->execute([
|
||||||
|
$phone ?: null,
|
||||||
|
$description,
|
||||||
|
$specializations,
|
||||||
|
$profile_picture,
|
||||||
|
$status,
|
||||||
|
$target_user_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$success_message = "Dati aggiornati!";
|
||||||
|
|
||||||
|
// Reload teacher (with correct target user)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT t.*, u.first_name, u.last_name, u.email
|
||||||
|
FROM teachers t
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
WHERE t.user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$target_user_id]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
} else {
|
||||||
|
$error = "Errore aggiornamento.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3) QR generation (after teacher is loaded)
|
||||||
|
*/
|
||||||
|
$qr_code_path = null;
|
||||||
|
if (!empty($teacher['unique_code'])) {
|
||||||
|
try {
|
||||||
|
$unique_code = $teacher['unique_code'];
|
||||||
|
|
||||||
|
// IMPORTANT: file name uses target user_id (teacher user), not owner id
|
||||||
|
$qr_user_id = (int)$teacher['user_id'];
|
||||||
|
|
||||||
|
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
|
||||||
|
$qr_filename = "{$base_dir}{$qr_user_id}-{$unique_code}.png";
|
||||||
|
$qr_code_path = "phototeachers/qrcodes/{$qr_user_id}-{$unique_code}.png";
|
||||||
|
|
||||||
|
if (!file_exists($qr_filename)) {
|
||||||
|
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
|
||||||
|
writeQrPng($unique_code, $qr_filename, 150, 10);
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore QR: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Profilo Insegnante</title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.teacher-photo {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 260px;
|
||||||
|
object-fit: contain;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quill-wrapper {
|
||||||
|
min-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-toolbar {
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
border-color: #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="card radius-10">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">Profilo Insegnante</h6>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (!empty($success_message)): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<?php echo htmlspecialchars($success_message); ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST" enctype="multipart/form-data" id="teacherForm">
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-lg-4 text-center">
|
||||||
|
<img src="<?php echo $teacher['profile_picture'] ? htmlspecialchars($teacher['profile_picture']) : 'phototeachers/ndphoto.png'; ?>"
|
||||||
|
alt="Foto Profilo" class="teacher-photo">
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="profile_picture" class="form-label">Carica nuova foto</label>
|
||||||
|
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/jpeg,image/png,image/gif">
|
||||||
|
<small class="text-muted d-block mt-1">Max 2MB – JPG, PNG, GIF</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($teacher['unique_code']) && $qr_code_path): ?>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Codice Univoco</label>
|
||||||
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['unique_code']); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">QR Code</label><br>
|
||||||
|
<img src="<?php echo htmlspecialchars($qr_code_path); ?>" alt="QR Code" class="img-fluid shadow-sm" style="max-width: 180px;">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="first_name" class="form-label">Nome</label>
|
||||||
|
<input type="text" class="form-control" id="first_name" name="first_name"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['first_name'] ?? ''); ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="last_name" class="form-label">Cognome</label>
|
||||||
|
<input type="text" class="form-control" id="last_name" name="last_name"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['last_name'] ?? ''); ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['email'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="phone" class="form-label">Telefono</label>
|
||||||
|
<input type="tel" class="form-control" id="phone" name="phone"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['phone'] ?? ''); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 form-section">
|
||||||
|
<label class="form-label">Descrizione insegnante</label>
|
||||||
|
<div class="quill-wrapper">
|
||||||
|
<div id="quill-editor"></div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="description" id="description-hidden">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 form-section">
|
||||||
|
<label for="specializations" class="form-label">Specializzazioni</label>
|
||||||
|
<textarea class="form-control" id="specializations" name="specializations" rows="3"><?php echo htmlspecialchars($teacher['specializations'] ?? ''); ?></textarea>
|
||||||
|
<small class="text-muted">Es: Hatha Yoga, Vinyasa, Yin, Restorative...</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Stato</label>
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="status" name="status" value="active"
|
||||||
|
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="status">
|
||||||
|
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'Attivo' : 'Inattivo'; ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Creato</label>
|
||||||
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['created_at'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Aggiornato</label>
|
||||||
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['updated_at'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 mt-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||||
|
Salva Modifiche
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay toggle-icon"></div>
|
||||||
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||||
|
<script>
|
||||||
|
const quill = new Quill('#quill-editor', {
|
||||||
|
theme: 'snow',
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
[{
|
||||||
|
'header': [1, 2, 3, false]
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'color': ['#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#808080', '#c0c0c0']
|
||||||
|
}, {
|
||||||
|
'background': []
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'list': 'ordered'
|
||||||
|
}, {
|
||||||
|
'list': 'bullet'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'align': []
|
||||||
|
}],
|
||||||
|
['link', 'clean']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quill.root.innerHTML = `<?php echo addslashes($teacher['description'] ?? ''); ?>`;
|
||||||
|
|
||||||
|
document.getElementById('teacherForm').addEventListener('submit', function() {
|
||||||
|
document.getElementById('description-hidden').value = quill.root.innerHTML;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,42 +1,72 @@
|
|||||||
<?php
|
<?php
|
||||||
// Forza la visualizzazione degli errori
|
// teacher_profile.php
|
||||||
|
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
ini_set('display_startup_errors', 1);
|
ini_set('display_startup_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
|
||||||
// Importa la libreria QR Code
|
// QR Code library
|
||||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
|
||||||
use Endroid\QrCode\Builder\Builder;
|
use Endroid\QrCode\QrCode;
|
||||||
use Endroid\QrCode\Writer\PngWriter;
|
use Endroid\QrCode\Writer\PngWriter;
|
||||||
|
|
||||||
// Connessione al database
|
|
||||||
$dbHandler = DBHandlerSelect::getInstance();
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
$pdo = $dbHandler->getConnection();
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
// ID dell'utente loggato (assumiamo sia definito)
|
|
||||||
if (!isset($iduserlogin)) {
|
if (!isset($iduserlogin)) {
|
||||||
die("Errore: ID utente non definito.");
|
die("Errore: ID utente non definito.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recupera i dati dell'insegnante
|
$teacher_id = (int)($_GET['id'] ?? 0);
|
||||||
$stmt = $pdo->prepare("
|
$is_owner_view = ($teacher_id > 0); // se arrivi da teacher_list.php con ?id=...
|
||||||
SELECT t.*, u.first_name, u.last_name, u.email
|
|
||||||
FROM auth_users u
|
|
||||||
LEFT JOIN teachers t ON t.user_id = u.id
|
|
||||||
WHERE u.id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$iduserlogin]);
|
|
||||||
$teacher = $stmt->fetch();
|
|
||||||
|
|
||||||
if (!$teacher) {
|
|
||||||
die("Errore: Utente non trovato.");
|
if ($teacher_id > 0) {
|
||||||
|
// === OWNER VIEW: carica teacher per teachers.id SOLO se l'owner ha diritto ===
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
u.first_name, u.last_name, u.email
|
||||||
|
FROM teachers t
|
||||||
|
JOIN auth_users u ON t.user_id = u.id
|
||||||
|
JOIN teacher_schools ts ON ts.teacher_id = t.id
|
||||||
|
JOIN schools s ON s.id = ts.school_id
|
||||||
|
WHERE t.id = ?
|
||||||
|
AND s.owner_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$teacher_id, $iduserlogin]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$teacher) {
|
||||||
|
die("Errore: insegnante non trovata o non hai permessi.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// === TEACHER SELF VIEW: carica il profilo dell'utente loggato ===
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT
|
||||||
|
t.*,
|
||||||
|
u.first_name, u.last_name, u.email
|
||||||
|
FROM auth_users u
|
||||||
|
LEFT JOIN teachers t ON t.user_id = u.id
|
||||||
|
WHERE u.id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determina se è un nuovo insegnante
|
|
||||||
$is_new = !isset($teacher['id']);
|
$is_new = empty($teacher['id']); // ok così
|
||||||
|
if ($teacher_id > 0) {
|
||||||
|
$is_new = false; // owner sta editando una teacher esistente
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
$teacher = [
|
$teacher = [
|
||||||
'id' => null,
|
'id' => null,
|
||||||
@@ -49,267 +79,351 @@ if ($is_new) {
|
|||||||
'status' => 'active',
|
'status' => 'active',
|
||||||
'created_at' => '',
|
'created_at' => '',
|
||||||
'updated_at' => '',
|
'updated_at' => '',
|
||||||
'first_name' => $teacher['first_name'],
|
'first_name' => '',
|
||||||
'last_name' => $teacher['last_name'],
|
'last_name' => '',
|
||||||
'email' => $teacher['email']
|
'email' => ''
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Funzione per generare un codice univoco
|
|
||||||
function generateUniqueCode($pdo, $length = 16)
|
function generateUniqueCode($pdo, $length = 16)
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
$code = bin2hex(random_bytes($length / 2));
|
$code = bin2hex(random_bytes($length / 2));
|
||||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM teachers WHERE unique_code = ?");
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM teachers WHERE unique_code = ?");
|
||||||
$stmt->execute([$code]);
|
$stmt->execute([$code]);
|
||||||
$count = $stmt->fetchColumn();
|
} while ($stmt->fetchColumn() > 0);
|
||||||
} while ($count > 0);
|
|
||||||
return $code;
|
return $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generazione del QR Code
|
function writeQrPng($text, $filename, $size = 150, $margin = 10)
|
||||||
$qr_code_path = null;
|
{
|
||||||
if (!$is_new) {
|
// ✅ nella tua versione il costruttore vuole il testo
|
||||||
try {
|
$qrCode = new \Endroid\QrCode\QrCode($text);
|
||||||
$unique_code = $teacher['unique_code'];
|
|
||||||
if (empty($unique_code)) {
|
// size: alcune versioni hanno setSize(), altre setModuleSize()
|
||||||
throw new Exception("Errore: unique_code è vuoto.");
|
if (method_exists($qrCode, 'setSize')) {
|
||||||
|
$qrCode->setSize($size);
|
||||||
|
} elseif (method_exists($qrCode, 'setModuleSize')) {
|
||||||
|
$module = max(3, (int)round($size / 25)); // mapping semplice
|
||||||
|
$qrCode->setModuleSize($module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// margin: alcune versioni setMargin(), altre setPadding()
|
||||||
|
if (method_exists($qrCode, 'setMargin')) {
|
||||||
|
$qrCode->setMargin($margin);
|
||||||
|
} elseif (method_exists($qrCode, 'setPadding')) {
|
||||||
|
$qrCode->setPadding($margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer = new \Endroid\QrCode\Writer\PngWriter();
|
||||||
|
|
||||||
|
if (method_exists($writer, 'writeFile')) {
|
||||||
|
$writer->writeFile($qrCode, $filename);
|
||||||
|
} else {
|
||||||
|
$result = $writer->write($qrCode);
|
||||||
|
if (is_object($result) && method_exists($result, 'saveToFile')) {
|
||||||
|
$result->saveToFile($filename);
|
||||||
|
} else {
|
||||||
|
file_put_contents($filename, (string)$result);
|
||||||
}
|
}
|
||||||
|
|
||||||
$base_dir = __DIR__ . '/../../public/userarea/phototeachers/qrcodes/';
|
|
||||||
$qr_code_filename = "{$base_dir}{$iduserlogin}-{$unique_code}.png";
|
|
||||||
$qr_code_path = "phototeachers/qrcodes/{$iduserlogin}-{$unique_code}.png";
|
|
||||||
|
|
||||||
if (!file_exists($qr_code_filename)) {
|
|
||||||
if (!is_dir($base_dir)) {
|
|
||||||
mkdir($base_dir, 0755, true) or die("Errore: Impossibile creare la directory.");
|
|
||||||
}
|
|
||||||
if (!is_writable($base_dir)) {
|
|
||||||
die("Errore: La directory non è scrivibile.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$builder = new Builder();
|
|
||||||
$result = $builder->build(
|
|
||||||
writer: new PngWriter(),
|
|
||||||
data: $unique_code,
|
|
||||||
size: 150,
|
|
||||||
margin: 10
|
|
||||||
);
|
|
||||||
$result->saveToFile($qr_code_filename);
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$error = "Errore generazione QR Code: " . $e->getMessage();
|
|
||||||
error_log($error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gestione del form
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
||||||
$first_name = $_POST['first_name'];
|
|
||||||
$last_name = $_POST['last_name'];
|
|
||||||
$phone = $_POST['phone'] ?? null;
|
|
||||||
$description = $_POST['description'] ?? null;
|
|
||||||
$specializations = $_POST['specializations'] ?? null;
|
|
||||||
$status = $_POST['status'] === 'active' ? 'active' : 'inactive';
|
|
||||||
|
|
||||||
// Gestione del caricamento della foto
|
|
||||||
$profile_picture = $teacher['profile_picture'];
|
|
||||||
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
|
||||||
$file = $_FILES['profile_picture'];
|
|
||||||
$timestamp = time();
|
|
||||||
$original_name = basename($file['name']);
|
|
||||||
$extension = strtolower(pathinfo($original_name, PATHINFO_EXTENSION));
|
|
||||||
$allowed_extensions = ['jpg', 'jpeg', 'png', 'gif'];
|
|
||||||
|
|
||||||
if (in_array($extension, $allowed_extensions)) {
|
$qr_code_path = null;
|
||||||
$new_filename = "phototeachers/{$iduserlogin}-{$timestamp}-{$original_name}";
|
if (!$is_new && !empty($teacher['unique_code'])) {
|
||||||
if (move_uploaded_file($file['tmp_name'], $new_filename)) {
|
try {
|
||||||
$profile_picture = $new_filename;
|
$unique_code = $teacher['unique_code'];
|
||||||
if ($teacher['profile_picture'] && file_exists($teacher['profile_picture']) && !$is_new) {
|
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
|
||||||
unlink($teacher['profile_picture']);
|
$qr_filename = "{$base_dir}{$iduserlogin}-{$unique_code}.png";
|
||||||
}
|
$qr_code_path = "phototeachers/qrcodes/{$iduserlogin}-{$unique_code}.png";
|
||||||
} else {
|
|
||||||
$error = "Errore durante il caricamento della foto.";
|
if (!file_exists($qr_filename)) {
|
||||||
}
|
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
|
||||||
} else {
|
writeQrPng($unique_code, $qr_filename, 150, 10);
|
||||||
$error = "Estensione del file non consentita. Usa JPG, JPEG, PNG o GIF.";
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log("Errore QR: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$success_message = $error = null;
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// ✅ target: se owner sta editando una teacher (?id=..), salva su QUELLA teacher
|
||||||
|
$target_user_id = ($teacher_id > 0) ? (int)$teacher['user_id'] : (int)$iduserlogin;
|
||||||
|
$target_teacher_id = ($teacher_id > 0) ? (int)$teacher['id'] : (int)($teacher['id'] ?? 0);
|
||||||
|
$first_name = trim($_POST['first_name'] ?? '');
|
||||||
|
$last_name = trim($_POST['last_name'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$specializations = trim($_POST['specializations'] ?? '');
|
||||||
|
$status = ($_POST['status'] ?? 'active') === 'active' ? 'active' : 'inactive';
|
||||||
|
|
||||||
|
$target_user_id = ($teacher_id > 0) ? (int)$teacher['user_id'] : (int)$iduserlogin;
|
||||||
|
|
||||||
// Aggiorna auth_users
|
|
||||||
$stmt = $pdo->prepare("UPDATE auth_users SET first_name = ?, last_name = ? WHERE id = ?");
|
$stmt = $pdo->prepare("UPDATE auth_users SET first_name = ?, last_name = ? WHERE id = ?");
|
||||||
$stmt->execute([$first_name, $last_name, $iduserlogin]);
|
$stmt->execute([$first_name, $last_name, $target_user_id]);
|
||||||
|
|
||||||
|
|
||||||
|
$profile_picture = $teacher['profile_picture'] ?? '';
|
||||||
|
if (!empty($_FILES['profile_picture']['name']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$ext = strtolower(pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION));
|
||||||
|
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$new_name = "phototeachers/{$target_user_id}-" . time() . "-profile.$ext";
|
||||||
|
|
||||||
|
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $new_name)) {
|
||||||
|
if ($profile_picture && file_exists($profile_picture) && !$is_new) @unlink($profile_picture);
|
||||||
|
$profile_picture = $new_name;
|
||||||
|
} else $error = "Errore caricamento foto.";
|
||||||
|
} else $error = "Solo JPG, PNG, GIF ammessi.";
|
||||||
|
}
|
||||||
|
|
||||||
if ($is_new) {
|
if ($is_new) {
|
||||||
$unique_code = generateUniqueCode($pdo);
|
$unique_code = generateUniqueCode($pdo);
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO teachers (user_id, unique_code, phone, description, specializations, profile_picture, status)
|
INSERT INTO teachers
|
||||||
|
(user_id, unique_code, phone, description, specializations, profile_picture, status)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
");
|
");
|
||||||
$success = $stmt->execute([$iduserlogin, $unique_code, $phone, $description, $specializations, $profile_picture, $status]);
|
$success = $stmt->execute([$target_user_id, $unique_code, $phone ?: null, $description, $specializations, $profile_picture, $status]);
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
$success_message = "Insegnante creato con successo!";
|
$success_message = "Profilo creato!";
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("SELECT t.*, u.first_name, u.last_name, u.email
|
||||||
SELECT t.*, u.first_name, u.last_name, u.email
|
FROM teachers t JOIN auth_users u ON t.user_id = u.id
|
||||||
FROM auth_users u
|
WHERE t.user_id = ?");
|
||||||
LEFT JOIN teachers t ON t.user_id = u.id
|
|
||||||
WHERE u.id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
$teacher = $stmt->fetch();
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
$is_new = false;
|
$is_new = false;
|
||||||
|
|
||||||
// Genera QR Code per il nuovo insegnante
|
|
||||||
try {
|
try {
|
||||||
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
|
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
|
||||||
$qr_code_filename = "{$base_dir}{$iduserlogin}-{$unique_code}.png";
|
$qr_filename = "{$base_dir}{$iduserlogin}-{$unique_code}.png";
|
||||||
$qr_code_path = "phototeachers/qrcodes/{$iduserlogin}-{$unique_code}.png";
|
$qr_code_path = "phototeachers/qrcodes/{$iduserlogin}-{$unique_code}.png";
|
||||||
|
if (!file_exists($qr_filename)) {
|
||||||
if (!file_exists($qr_code_filename)) {
|
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
|
||||||
if (!is_dir($base_dir)) {
|
$writer = new PngWriter();
|
||||||
mkdir($base_dir, 0755, true) or die("Errore: Impossibile creare la directory.");
|
if (!file_exists($qr_filename)) {
|
||||||
|
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
|
||||||
|
writeQrPng($unique_code, $qr_filename, 150, 10);
|
||||||
}
|
}
|
||||||
$builder = new Builder();
|
|
||||||
$result = $builder->build(
|
|
||||||
writer: new PngWriter(),
|
$result = $writer->write($qrCode);
|
||||||
data: $unique_code,
|
$result->saveToFile($qr_filename);
|
||||||
size: 150,
|
|
||||||
margin: 10
|
|
||||||
);
|
|
||||||
$result->saveToFile($qr_code_filename);
|
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$error = "Errore generazione QR Code: " . $e->getMessage();
|
error_log("Errore QR: " . $e->getMessage());
|
||||||
error_log($error);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else $error = "Errore creazione.";
|
||||||
$error = "Errore durante la creazione dell'insegnante.";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE teachers
|
UPDATE teachers
|
||||||
SET phone = ?, description = ?, specializations = ?, profile_picture = ?, status = ?
|
SET phone = ?, description = ?, specializations = ?, profile_picture = ?, status = ?
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
");
|
");
|
||||||
$success = $stmt->execute([$phone, $description, $specializations, $profile_picture, $status, $iduserlogin]);
|
$success = $stmt->execute([$phone ?: null, $description, $specializations, $profile_picture, $status, $target_user_id]);
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
$success_message = "Dati aggiornati con successo!";
|
$success_message = "Dati aggiornati!";
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("SELECT t.*, u.first_name, u.last_name, u.email
|
||||||
SELECT t.*, u.first_name, u.last_name, u.email
|
FROM teachers t JOIN auth_users u ON t.user_id = u.id
|
||||||
FROM auth_users u
|
WHERE t.user_id = ?");
|
||||||
LEFT JOIN teachers t ON t.user_id = u.id
|
|
||||||
WHERE u.id = ?
|
|
||||||
");
|
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
$teacher = $stmt->fetch();
|
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
} else {
|
} else $error = "Errore aggiornamento.";
|
||||||
$error = "Errore durante l'aggiornamento dei dati.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="it">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
<title><?php echo $is_new ? 'Crea' : 'Modifica'; ?> Profilo Insegnante</title>
|
||||||
<?php include('cssinclude.php'); ?>
|
<?php include('cssinclude.php'); ?>
|
||||||
<?php include('siteinfo.php'); ?>
|
<?php include('siteinfo.php'); ?>
|
||||||
|
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.teacher-photo {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 260px;
|
||||||
|
object-fit: contain;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quill-wrapper {
|
||||||
|
min-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0 0 0.375rem 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-toolbar {
|
||||||
|
border-radius: 0.375rem 0.375rem 0 0;
|
||||||
|
border-color: #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h6 class="mb-0"><?php echo $is_new ? 'Crea Profilo Insegnante' : 'Profilo Insegnante'; ?></h6>
|
<h6 class="mb-0"><?php echo $is_new ? 'Crea Profilo Insegnante' : 'Profilo Insegnante'; ?></h6>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<?php if (isset($success_message)): ?>
|
<?php if (isset($success_message)): ?>
|
||||||
<div class="alert alert-success" role="alert">
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
<?php echo $success_message; ?>
|
<?php echo htmlspecialchars($success_message); ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if (isset($error)): ?>
|
<?php if (isset($error)): ?>
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger alert-dismissible fade show">
|
||||||
<?php echo $error; ?>
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<form method="POST" enctype="multipart/form-data">
|
|
||||||
<div class="row">
|
<form method="POST" enctype="multipart/form-data" id="teacherForm">
|
||||||
<div class="col-md-4 text-center">
|
<div class="row g-4">
|
||||||
<div class="mb-3">
|
<!-- Colonna sinistra: foto + QR -->
|
||||||
<img src="<?php echo $teacher['profile_picture'] ? htmlspecialchars($teacher['profile_picture']) : 'phototeachers/ndphoto.png'; ?>"
|
<div class="col-lg-4 text-center">
|
||||||
alt="Foto Profilo" class="img-fluid rounded-circle" style="width: 150px; height: 150px; object-fit: cover;">
|
<img src="<?php echo $teacher['profile_picture'] ? htmlspecialchars($teacher['profile_picture']) : 'phototeachers/ndphoto.png'; ?>"
|
||||||
</div>
|
alt="Foto Profilo" class="teacher-photo">
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="mb-4">
|
||||||
<label for="profile_picture" class="form-label">Carica nuova foto</label>
|
<label for="profile_picture" class="form-label">Carica nuova foto</label>
|
||||||
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/*">
|
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/jpeg,image/png,image/gif">
|
||||||
|
<small class="text-muted d-block mt-1">Max 2MB – JPG, PNG, GIF</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if (!$is_new && $qr_code_path): ?>
|
<?php if (!$is_new && $qr_code_path): ?>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Codice Univoco</label>
|
<label class="form-label">Codice Univoco</label>
|
||||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['unique_code']); ?>" readonly>
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['unique_code']); ?>" readonly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">QR Code</label><br>
|
<label class="form-label">QR Code</label><br>
|
||||||
<img src="<?php echo htmlspecialchars($qr_code_path); ?>" alt="QR Code" class="img-fluid" style="width: 150px; height: 150px;">
|
<img src="<?php echo htmlspecialchars($qr_code_path); ?>" alt="QR Code" class="img-fluid shadow-sm" style="max-width: 180px;">
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="row">
|
<!-- Colonna destra: campi organizzati -->
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-lg-8">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="first_name" class="form-label">Nome</label>
|
<label for="first_name" class="form-label">Nome</label>
|
||||||
<input type="text" class="form-control" id="first_name" name="first_name" value="<?php echo htmlspecialchars($teacher['first_name']); ?>" required>
|
<input type="text" class="form-control" id="first_name" name="first_name"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['first_name'] ?? ''); ?>" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
|
<div class="col-md-6">
|
||||||
<label for="last_name" class="form-label">Cognome</label>
|
<label for="last_name" class="form-label">Cognome</label>
|
||||||
<input type="text" class="form-control" id="last_name" name="last_name" value="<?php echo htmlspecialchars($teacher['last_name']); ?>" required>
|
<input type="text" class="form-control" id="last_name" name="last_name"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['last_name'] ?? ''); ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<label for="email" class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" id="email" name="email"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['email'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="phone" class="form-label">Telefono</label>
|
||||||
|
<input type="tel" class="form-control" id="phone" name="phone"
|
||||||
|
value="<?php echo htmlspecialchars($teacher['phone'] ?? ''); ?>">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Editor descrizione -->
|
||||||
|
<div class="col-12 form-section">
|
||||||
|
<label class="form-label">Descrizione insegnante</label>
|
||||||
|
<div class="quill-wrapper">
|
||||||
|
<div id="quill-editor"></div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="description" id="description-hidden">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Specializzazioni -->
|
||||||
|
<div class="col-12 form-section">
|
||||||
|
<label for="specializations" class="form-label">Specializzazioni</label>
|
||||||
|
<textarea class="form-control" id="specializations" name="specializations" rows="3"><?php echo htmlspecialchars($teacher['specializations'] ?? ''); ?></textarea>
|
||||||
|
<small class="text-muted">Es: Hatha Yoga, Vinyasa, Yin, Restorative...</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Stato</label>
|
||||||
|
<div class="form-check form-switch mt-2">
|
||||||
|
<input class="form-check-input" type="checkbox" id="status" name="status" value="active"
|
||||||
|
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="status">
|
||||||
|
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'Attivo' : 'Inattivo'; ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!$is_new): ?>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Data Creazione</label>
|
||||||
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['created_at'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Ultimo Aggiornamento</label>
|
||||||
|
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['updated_at'] ?? ''); ?>" readonly>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="col-12 mt-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||||
|
<?php echo $is_new ? 'Crea Profilo' : 'Salva Modifiche'; ?>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
|
||||||
<label for="email" class="form-label">Email</label>
|
|
||||||
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($teacher['email']); ?>" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="phone" class="form-label">Telefono</label>
|
|
||||||
<input type="text" class="form-control" id="phone" name="phone" value="<?php echo htmlspecialchars($teacher['phone'] ?? ''); ?>">
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="description" class="form-label">Descrizione</label>
|
|
||||||
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($teacher['description'] ?? ''); ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="specializations" class="form-label">Specializzazioni</label>
|
|
||||||
<textarea class="form-control" id="specializations" name="specializations" rows="2"><?php echo htmlspecialchars($teacher['specializations'] ?? ''); ?></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="status" class="form-label">Stato</label>
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" id="status" name="status" value="active" <?php echo $teacher['status'] === 'active' ? 'checked' : ''; ?>>
|
|
||||||
<label class="form-check-label" for="status"><?php echo $teacher['status'] === 'active' ? 'Attivo' : 'Inattivo'; ?></label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php if (!$is_new): ?>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Data Creazione</label>
|
|
||||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['created_at']); ?>" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label">Ultimo Aggiornamento</label>
|
|
||||||
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['updated_at']); ?>" readonly>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="submit" class="btn btn-primary"><?php echo $is_new ? 'Crea Profilo' : 'Salva Modifiche'; ?></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -317,11 +431,51 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overlay toggle-icon"></div>
|
<div class="overlay toggle-icon"></div>
|
||||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||||
<?php include('include/footer.php'); ?>
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<!-- Quill -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const quill = new Quill('#quill-editor', {
|
||||||
|
theme: 'snow',
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
[{
|
||||||
|
'header': [1, 2, 3, false]
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'color': ['#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#808080', '#c0c0c0']
|
||||||
|
}, {
|
||||||
|
'background': []
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'list': 'ordered'
|
||||||
|
}, {
|
||||||
|
'list': 'bullet'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
'align': []
|
||||||
|
}],
|
||||||
|
['link', 'clean']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quill.root.innerHTML = `<?php echo addslashes($teacher['description'] ?? ''); ?>`;
|
||||||
|
|
||||||
|
document.getElementById('teacherForm').addEventListener('submit', function() {
|
||||||
|
document.getElementById('description-hidden').value = quill.root.innerHTML;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'class/mailer.php';
|
||||||
|
|
||||||
|
$result = sendEmail(
|
||||||
|
'info@claudiosironi.com', // Cambia con un tuo indirizzo di test
|
||||||
|
'Test SMTP YogiBoook',
|
||||||
|
'<h1>Funziona!</h1><p>Se vedi questa mail, SMTP OK.</p>'
|
||||||
|
);
|
||||||
|
|
||||||
|
var_dump($result);
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
<?php
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
die("Errore: utente non loggato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 1) Recupera la scuola (come nel tuo esempio)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
// 1) Recupera school_id dalla sessione (studente) oppure fallback owner (admin/owner)
|
||||||
|
$school_id = (int)($_SESSION['school_id'] ?? 0);
|
||||||
|
|
||||||
|
if ($school_id <= 0) {
|
||||||
|
// fallback per owner/admin
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([(int)$iduserlogin]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) die("Scuola non trovata.");
|
||||||
|
$school_id = (int)$school['id'];
|
||||||
|
} else {
|
||||||
|
// carica dati scuola
|
||||||
|
$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$school) die("Scuola non trovata.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 2) Recupera school_settings (per sapere se notifiche sono abilitate globalmente)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
$stmt = $pdo->prepare("SELECT enable_notifications FROM school_settings WHERE school_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id]);
|
||||||
|
$schoolSettings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$school_enable_notifications = isset($schoolSettings['enable_notifications'])
|
||||||
|
? (int)$schoolSettings['enable_notifications']
|
||||||
|
: 1; // default ON se non esiste riga
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 3) Recupera user_settings (per questo utente in questa scuola)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE school_id = ? AND user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id, (int)$iduserlogin]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$is_new = !$settings;
|
||||||
|
|
||||||
|
$success_message = "";
|
||||||
|
$error = "";
|
||||||
|
|
||||||
|
if (!$settings) {
|
||||||
|
$pdo->prepare("
|
||||||
|
INSERT INTO user_settings (school_id, user_id, locale, timezone, created_at, updated_at)
|
||||||
|
VALUES (?, ?, 'it', 'Europe/Rome', NOW(), NOW())
|
||||||
|
")->execute([$school_id, (int)$iduserlogin]);
|
||||||
|
|
||||||
|
header("Location: user-settings.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 4) Salvataggio POST
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
// NOTA: se scuola disabilita notifiche globalmente, forzo tutto a 0
|
||||||
|
$notifications_allowed = ($school_enable_notifications === 1);
|
||||||
|
|
||||||
|
$notify_email = (!empty($_POST['notify_email']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_whatsapp = (!empty($_POST['notify_whatsapp']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_push = (!empty($_POST['notify_push']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
|
||||||
|
$notify_booking_confirm = (!empty($_POST['notify_booking_confirm']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_booking_cancel = (!empty($_POST['notify_booking_cancel']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_session_cancel = (!empty($_POST['notify_session_cancel']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_payment_receipt = (!empty($_POST['notify_payment_receipt']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
$notify_expiration_reminder = (!empty($_POST['notify_expiration_reminder']) && $notifications_allowed) ? 1 : 0;
|
||||||
|
|
||||||
|
// MARKETING
|
||||||
|
$newsletter_opt_in = !empty($_POST['newsletter_opt_in']) ? 1 : 0;
|
||||||
|
$marketing_opt_in = !empty($_POST['marketing_opt_in']) ? 1 : 0;
|
||||||
|
|
||||||
|
// PREFERENZE
|
||||||
|
$locale = trim($_POST['locale'] ?? 'it');
|
||||||
|
$timezone = trim($_POST['timezone'] ?? 'Europe/Rome');
|
||||||
|
|
||||||
|
// whitelist minima (anti valori strani)
|
||||||
|
$allowed_locales = ['it', 'en', 'es'];
|
||||||
|
if (!in_array($locale, $allowed_locales, true)) $locale = 'it';
|
||||||
|
if ($timezone === '') $timezone = 'Europe/Rome';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($is_new) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_settings
|
||||||
|
(school_id, user_id,
|
||||||
|
notify_email, notify_whatsapp, notify_push,
|
||||||
|
notify_booking_confirm, notify_booking_cancel, notify_session_cancel,
|
||||||
|
notify_payment_receipt, notify_expiration_reminder,
|
||||||
|
newsletter_opt_in, marketing_opt_in,
|
||||||
|
locale, timezone)
|
||||||
|
VALUES
|
||||||
|
(?, ?,
|
||||||
|
?, ?, ?,
|
||||||
|
?, ?, ?,
|
||||||
|
?, ?,
|
||||||
|
?, ?,
|
||||||
|
?, ?)
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$school_id,
|
||||||
|
(int)$iduserlogin,
|
||||||
|
$notify_email,
|
||||||
|
$notify_whatsapp,
|
||||||
|
$notify_push,
|
||||||
|
$notify_booking_confirm,
|
||||||
|
$notify_booking_cancel,
|
||||||
|
$notify_session_cancel,
|
||||||
|
$notify_payment_receipt,
|
||||||
|
$notify_expiration_reminder,
|
||||||
|
$newsletter_opt_in,
|
||||||
|
$marketing_opt_in,
|
||||||
|
$locale,
|
||||||
|
$timezone
|
||||||
|
]);
|
||||||
|
|
||||||
|
$success_message = "Impostazioni utente create con successo!";
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE user_settings SET
|
||||||
|
notify_email = ?, notify_whatsapp = ?, notify_push = ?,
|
||||||
|
notify_booking_confirm = ?, notify_booking_cancel = ?, notify_session_cancel = ?,
|
||||||
|
notify_payment_receipt = ?, notify_expiration_reminder = ?,
|
||||||
|
newsletter_opt_in = ?, marketing_opt_in = ?,
|
||||||
|
locale = ?, timezone = ?
|
||||||
|
WHERE school_id = ? AND user_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
$notify_email,
|
||||||
|
$notify_whatsapp,
|
||||||
|
$notify_push,
|
||||||
|
$notify_booking_confirm,
|
||||||
|
$notify_booking_cancel,
|
||||||
|
$notify_session_cancel,
|
||||||
|
$notify_payment_receipt,
|
||||||
|
$notify_expiration_reminder,
|
||||||
|
$newsletter_opt_in,
|
||||||
|
$marketing_opt_in,
|
||||||
|
$locale,
|
||||||
|
$timezone,
|
||||||
|
$school_id,
|
||||||
|
(int)$iduserlogin
|
||||||
|
]);
|
||||||
|
|
||||||
|
$success_message = "Impostazioni utente aggiornate con successo!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ricarica valori aggiornati
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE school_id = ? AND user_id = ? LIMIT 1");
|
||||||
|
$stmt->execute([$school_id, (int)$iduserlogin]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$is_new = !$settings;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Errore database: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| 5) Defaults (se non esiste ancora riga)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
$defaults = [
|
||||||
|
'notify_email' => 1,
|
||||||
|
'notify_whatsapp' => 0,
|
||||||
|
'notify_push' => 0,
|
||||||
|
'notify_booking_confirm' => 1,
|
||||||
|
'notify_booking_cancel' => 1,
|
||||||
|
'notify_session_cancel' => 1,
|
||||||
|
'notify_payment_receipt' => 1,
|
||||||
|
'notify_expiration_reminder' => 1,
|
||||||
|
'newsletter_opt_in' => 0,
|
||||||
|
'marketing_opt_in' => 0,
|
||||||
|
'locale' => 'it',
|
||||||
|
'timezone' => 'Europe/Rome'
|
||||||
|
];
|
||||||
|
|
||||||
|
$settings = $settings ?: $defaults;
|
||||||
|
?>
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Impostazioni Utente - <?php echo htmlspecialchars($school['name']); ?></title>
|
||||||
|
<?php include('cssinclude.php'); ?>
|
||||||
|
<?php include('siteinfo.php'); ?>
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input:checked {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-note {
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-overlay {
|
||||||
|
opacity: .55;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<?php include('include/navbar.php'); ?>
|
||||||
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="page-content">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||||
|
<h4 class="mb-0">Impostazioni Utente</h4>
|
||||||
|
<span class="badge bg-light text-dark">
|
||||||
|
<?php echo htmlspecialchars($school['name']); ?>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<?php if ($success_message): ?>
|
||||||
|
<div class="alert alert-success alert-dismissible fade show">
|
||||||
|
<?php echo $success_message; ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<?php echo htmlspecialchars($error); ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ((int)$school_enable_notifications !== 1): ?>
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Le notifiche sono disattivate a livello di scuola. Le preferenze qui sotto non avranno effetto finché non vengono riattivate.
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
|
||||||
|
<!-- NOTIFICHE -->
|
||||||
|
<h5 class="text-primary mb-3 section-title">Notifiche</h5>
|
||||||
|
|
||||||
|
<div id="notificationsBlock" class="<?php echo ((int)$school_enable_notifications !== 1) ? 'disabled-overlay' : ''; ?>">
|
||||||
|
<div class="row g-4 align-items-center mb-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_email" id="notify_email"
|
||||||
|
<?php echo !empty($settings['notify_email']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_email">Email</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small-note">Conferme, cancellazioni, promemoria.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_whatsapp" id="notify_whatsapp"
|
||||||
|
<?php echo !empty($settings['notify_whatsapp']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_whatsapp">WhatsApp</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small-note">Da attivare quando integri WA.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_push" id="notify_push"
|
||||||
|
<?php echo !empty($settings['notify_push']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_push">Push</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small-note">Da attivare quando integri app/push.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<h6 class="mb-3">Eventi</h6>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_booking_confirm" id="notify_booking_confirm"
|
||||||
|
<?php echo !empty($settings['notify_booking_confirm']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_booking_confirm">Conferma prenotazione</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_booking_cancel" id="notify_booking_cancel"
|
||||||
|
<?php echo !empty($settings['notify_booking_cancel']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_booking_cancel">Cancellazione prenotazione</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_session_cancel" id="notify_session_cancel"
|
||||||
|
<?php echo !empty($settings['notify_session_cancel']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_session_cancel">Lezione cancellata dalla scuola</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_payment_receipt" id="notify_payment_receipt"
|
||||||
|
<?php echo !empty($settings['notify_payment_receipt']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_payment_receipt">Ricevuta / conferma pagamento</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_expiration_reminder" id="notify_expiration_reminder"
|
||||||
|
<?php echo !empty($settings['notify_expiration_reminder']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="notify_expiration_reminder">Promemoria scadenza abbonamento</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-5">
|
||||||
|
|
||||||
|
<!-- MARKETING -->
|
||||||
|
<h5 class="text-primary mb-3 section-title">Newsletter e comunicazioni</h5>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="newsletter_opt_in" id="newsletter_opt_in"
|
||||||
|
<?php echo !empty($settings['newsletter_opt_in']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="newsletter_opt_in">Newsletter</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small-note">Novità, eventi, contenuti.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="marketing_opt_in" id="marketing_opt_in"
|
||||||
|
<?php echo !empty($settings['marketing_opt_in']) ? 'checked' : ''; ?>>
|
||||||
|
<label class="form-check-label" for="marketing_opt_in">Promo e offerte</label>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small-note">Sconti, pacchetti speciali, promozioni.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-5">
|
||||||
|
|
||||||
|
<!-- PREFERENZE -->
|
||||||
|
<h5 class="text-primary mb-3 section-title">Preferenze</h5>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Lingua</label>
|
||||||
|
<select name="locale" class="form-select">
|
||||||
|
<option value="it" <?php echo ($settings['locale'] ?? 'it') === 'it' ? 'selected' : ''; ?>>Italiano</option>
|
||||||
|
<option value="en" <?php echo ($settings['locale'] ?? '') === 'en' ? 'selected' : ''; ?>>English</option>
|
||||||
|
<option value="es" <?php echo ($settings['locale'] ?? '') === 'es' ? 'selected' : ''; ?>>Español</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Timezone</label>
|
||||||
|
<input type="text" name="timezone" class="form-control"
|
||||||
|
value="<?php echo htmlspecialchars($settings['timezone'] ?? 'Europe/Rome'); ?>">
|
||||||
|
<div class="text-muted small-note">Esempio: Europe/Rome</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mt-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg px-5">
|
||||||
|
Salva Impostazioni
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -90,6 +90,8 @@ if (count($userSchools) === 1 && empty($_SESSION['school_id'])) {
|
|||||||
$_SESSION['school_id'] = (int)$userSchools[0]['id'];
|
$_SESSION['school_id'] = (int)$userSchools[0]['id'];
|
||||||
$_SESSION['school_name'] = $userSchools[0]['name'];
|
$_SESSION['school_name'] = $userSchools[0]['name'];
|
||||||
$_SESSION['school_selected'] = 1;
|
$_SESSION['school_selected'] = 1;
|
||||||
|
echo $_SESSION['school_name'];
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caso: più scuole -> OBBLIGO selezione esplicita
|
// Caso: più scuole -> OBBLIGO selezione esplicita
|
||||||
@@ -126,8 +128,18 @@ if ($school_id) {
|
|||||||
|
|
||||||
if ($school) {
|
if ($school) {
|
||||||
$school_name = $school['name'];
|
$school_name = $school['name'];
|
||||||
if (!empty($school['logo']) && file_exists("photoschool/" . $school['logo'])) {
|
$logoRaw = trim($school['logo'] ?? '');
|
||||||
$school_logo_path = "photoschool/" . $school['logo'];
|
if (!empty($logoRaw)) {
|
||||||
|
// Percorso fisico per verificare esistenza
|
||||||
|
$physicalPath = __DIR__ . '/../' . $logoRaw; // da userarea/ sale a public/ + photoschool/...
|
||||||
|
|
||||||
|
if (file_exists($physicalPath)) {
|
||||||
|
// Percorso web corretto (root-relative)
|
||||||
|
$school_logo_path = '/' . $logoRaw;
|
||||||
|
} else {
|
||||||
|
// Debug: scrivi nel log se il file non esiste
|
||||||
|
error_log("LOGO SCUOLA NON TROVATO - school_id: $school_id | path fisico: $physicalPath");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,6 +159,72 @@ $stmt = $pdo->prepare("
|
|||||||
$stmt->execute([$iduserlogin, $school_id]);
|
$stmt->execute([$iduserlogin, $school_id]);
|
||||||
$orders = $stmt->fetchAll();
|
$orders = $stmt->fetchAll();
|
||||||
|
|
||||||
|
// Lezioni ACQUISTATE totali
|
||||||
|
$stmt_acquistate = $pdo->prepare("
|
||||||
|
SELECT COALESCE(SUM(total_entries), 0) AS acquistate
|
||||||
|
FROM orders
|
||||||
|
WHERE user_id = ? AND school_id = ? AND status = 'completed'
|
||||||
|
");
|
||||||
|
$stmt_acquistate->execute([$iduserlogin, $school_id]);
|
||||||
|
$acquistate = (int) $stmt_acquistate->fetchColumn();
|
||||||
|
|
||||||
|
// Da PRATICARE (booked future)
|
||||||
|
$stmt_da_praticare = $pdo->prepare("
|
||||||
|
SELECT COUNT(sb.id) AS da_praticare
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = ?
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'booked'
|
||||||
|
AND cs.session_date >= CURDATE()
|
||||||
|
");
|
||||||
|
$stmt_da_praticare->execute([$iduserlogin, $school_id]);
|
||||||
|
$da_praticare = (int) $stmt_da_praticare->fetchColumn();
|
||||||
|
|
||||||
|
// PRATICATE (attended + booked passate)
|
||||||
|
$stmt_praticate = $pdo->prepare("
|
||||||
|
SELECT COUNT(sb.id) AS praticate
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = ?
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND (
|
||||||
|
sb.status = 'attended'
|
||||||
|
OR (sb.status = 'booked' AND cs.session_date < CURDATE())
|
||||||
|
)
|
||||||
|
");
|
||||||
|
$stmt_praticate->execute([$iduserlogin, $school_id]);
|
||||||
|
$praticate = (int) $stmt_praticate->fetchColumn();
|
||||||
|
|
||||||
|
// PERSE (missed + data passata)
|
||||||
|
$stmt_perse = $pdo->prepare("
|
||||||
|
SELECT COUNT(sb.id) AS perse
|
||||||
|
FROM session_bookings sb
|
||||||
|
JOIN class_sessions cs ON sb.session_id = cs.id
|
||||||
|
WHERE sb.user_id = ?
|
||||||
|
AND cs.school_id = ?
|
||||||
|
AND sb.status = 'missed'
|
||||||
|
AND cs.session_date < CURDATE()
|
||||||
|
");
|
||||||
|
$stmt_perse->execute([$iduserlogin, $school_id]);
|
||||||
|
$perse = (int) $stmt_perse->fetchColumn();
|
||||||
|
|
||||||
|
// DA PROGRAMMARE (residuo = acquistate - perse - praticate - da_praticare)
|
||||||
|
$da_programmare = max(0, $acquistate - $perse - $praticate - $da_praticare);;
|
||||||
|
|
||||||
|
// === CONTROLLA CERTIFICATI VALIDI ===
|
||||||
|
$stmt_cert = $pdo->prepare("
|
||||||
|
SELECT COUNT(*) AS valid_count
|
||||||
|
FROM user_medical_certificates
|
||||||
|
WHERE user_id = ?
|
||||||
|
AND expiry_date IS NOT NULL
|
||||||
|
AND expiry_date >= CURDATE()
|
||||||
|
AND is_valid = 1
|
||||||
|
");
|
||||||
|
$stmt_cert->execute([$iduserlogin]);
|
||||||
|
$cert_result = $stmt_cert->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$has_valid_cert = ($cert_result['valid_count'] > 0);
|
||||||
|
|
||||||
// === STATISTICHE RAPIDE ===
|
// === STATISTICHE RAPIDE ===
|
||||||
$total_spent = array_sum(array_column($orders, 'price'));
|
$total_spent = array_sum(array_column($orders, 'price'));
|
||||||
$total_entries = array_sum(array_column($orders, 'total_entries'));
|
$total_entries = array_sum(array_column($orders, 'total_entries'));
|
||||||
@@ -228,8 +306,9 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
|
|||||||
<div class="card-body text-center py-5">
|
<div class="card-body text-center py-5">
|
||||||
|
|
||||||
<!-- Logo solo se esiste -->
|
<!-- Logo solo se esiste -->
|
||||||
<?php if ($school_logo_path): ?>
|
<?php
|
||||||
<img src="<?php echo htmlspecialchars($school_logo_path); ?>"
|
if ($logoRaw): ?>
|
||||||
|
<img src="<?php echo htmlspecialchars($logoRaw); ?>"
|
||||||
alt="Logo <?php echo htmlspecialchars($school_name); ?>"
|
alt="Logo <?php echo htmlspecialchars($school_name); ?>"
|
||||||
class="mb-4 rounded-3 shadow"
|
class="mb-4 rounded-3 shadow"
|
||||||
style="height: 90px; object-fit: contain;">
|
style="height: 90px; object-fit: contain;">
|
||||||
@@ -256,42 +335,64 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- PROFILO + STATISTICHE -->
|
<?php if (!$has_valid_cert): ?>
|
||||||
<div class="row g-4 mb-5">
|
<div class="alert alert-danger alert-dismissible fade show mb-4 shadow" role="alert">
|
||||||
<div class="col-lg-4">
|
<strong>Attenzione!</strong> Non hai un certificato medico valido caricato.
|
||||||
<div class="card stat-card text-center h-100">
|
<br>Ti potrebbe essere vietato l'accesso alle lezioni/pratiche.
|
||||||
<div class="card-body">
|
<a href="my_certificates.php" class="alert-link fw-bold ms-2">
|
||||||
<img src="<?php echo $avatar; ?>" alt="Avatar" class="rounded-circle avatar-circle mb-3">
|
Caricalo subito qui →
|
||||||
<h5><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h5>
|
</a>
|
||||||
<p class="text-muted"><?php echo htmlspecialchars($user['email']); ?></p>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<!-- STATISTICHE - 5 BOX CHE OCCUPANO TUTTA LA RIGA SU DESKTOP -->
|
||||||
|
<div class="row g-3 mb-5">
|
||||||
|
<!-- 1. Lezioni acquistate -->
|
||||||
|
<div class="col-6 col-sm-4 col-md">
|
||||||
|
<div class="card stat-card text-center h-100 shadow-sm" style="background: linear-gradient(135deg, #d1fae5, #a7f3d0); border: none;">
|
||||||
|
<div class="card-body py-4 px-3">
|
||||||
|
<h4 class="fw-bold text-success mb-1"><?= number_format($acquistate) ?></h4>
|
||||||
|
<p class="text-dark mb-0 small">Acquistate</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="row g-4">
|
<!-- 2. Praticate -->
|
||||||
<div class="col-md-4">
|
<div class="col-6 col-sm-4 col-md">
|
||||||
<div class="card stat-card text-center h-100">
|
<div class="card stat-card text-center h-100 shadow-sm" style="background: linear-gradient(135deg, #e0f2fe, #bae6fd); border: none;">
|
||||||
<div class="card-body">
|
<div class="card-body py-4 px-3">
|
||||||
<h3 class="text-primary fw-bold"><?php echo count($orders); ?></h3>
|
<h4 class="fw-bold text-info mb-1"><?= number_format($praticate) ?></h4>
|
||||||
<p class="mb-0">Ordini totali</p>
|
<p class="text-dark mb-0 small">Praticate</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
</div>
|
||||||
<div class="card stat-card text-center h-100">
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="text-success fw-bold">€<?php echo number_format($total_spent, 2); ?></h3>
|
<!-- 3. Da praticare -->
|
||||||
<p class="mb-0">Speso in totale</p>
|
<div class="col-6 col-sm-4 col-md">
|
||||||
</div>
|
<div class="card stat-card text-center h-100 shadow-sm" style="background: linear-gradient(135deg, #dbeafe, #bfdbfe); border: none;">
|
||||||
</div>
|
<div class="card-body py-4 px-3">
|
||||||
|
<h4 class="fw-bold text-primary mb-1"><?= number_format($da_praticare) ?></h4>
|
||||||
|
<p class="text-dark mb-0 small">Da praticare</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
</div>
|
||||||
<div class="card stat-card text-center h-100">
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="text-info fw-bold"><?php echo $available_entries; ?></h3>
|
<!-- 4. Perse -->
|
||||||
<p class="mb-0">Ingressi disponibili</p>
|
<div class="col-6 col-sm-4 col-md">
|
||||||
</div>
|
<div class="card stat-card text-center h-100 shadow-sm" style="background: linear-gradient(135deg, #fee2e2, #fecaca); border: none;">
|
||||||
</div>
|
<div class="card-body py-4 px-3">
|
||||||
|
<h4 class="fw-bold text-danger mb-1"><?= number_format($perse) ?></h4>
|
||||||
|
<p class="text-dark mb-0 small">Perse</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 5. Da programmare -->
|
||||||
|
<div class="col-6 col-sm-4 col-md">
|
||||||
|
<div class="card stat-card text-center h-100 shadow-sm" style="background: linear-gradient(135deg, #fef3c7, #fde68a); border: none;">
|
||||||
|
<div class="card-body py-4 px-3">
|
||||||
|
<h4 class="fw-bold text-warning mb-1"><?= number_format($da_programmare) ?></h4>
|
||||||
|
<p class="text-dark mb-0 small">Da programmare</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -370,6 +471,18 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- PULSANTE RIMUOVI SCUOLA (in fondo, evidente) -->
|
||||||
|
<!-- Link discreto per rimuoversi dalla scuola (in fondo a destra) -->
|
||||||
|
<div class="text-end mt-5 mb-5 pe-4">
|
||||||
|
<a href="#" class="text-danger small text-decoration-none"
|
||||||
|
onclick="confirmRemoveSchool(<?= $school_id ?>, '<?= addslashes(htmlspecialchars($school_name)) ?>'); return false;">
|
||||||
|
<i class="bx bx-log-out bx-sm me-1"></i>
|
||||||
|
Rimuovimi da questa scuola
|
||||||
|
</a>
|
||||||
|
<p class="text-muted fst-italic small mt-1" style="font-size:0.8rem;">
|
||||||
|
(azione irreversibile: perderai lezioni, crediti e storico associato)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -377,6 +490,57 @@ $active_orders = count(array_filter($orders, fn($o) => $o['status'] === 'complet
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<script>
|
||||||
|
const confirmRemoveSchool = (schoolId, schoolName) => {
|
||||||
|
Swal.fire({
|
||||||
|
title: `Abbandonare ${schoolName}?`,
|
||||||
|
html: `Stai per rimuoverti completamente da <strong>${schoolName}</strong>.<br><br>` +
|
||||||
|
`<strong class="text-danger">Attenzione:</strong> perderai irreversibilmente:<br>` +
|
||||||
|
`<ul style="text-align:left; margin:15px 0 0 30px; font-size:0.95rem;">` +
|
||||||
|
`<li>Tutte le tue lezioni prenotate</li>` +
|
||||||
|
`<li>Crediti, ingressi e recuperi residui</li>` +
|
||||||
|
`<li>Storico ordini e archivio personale legato a questa scuola</li>` +
|
||||||
|
`</ul><br>` +
|
||||||
|
`Questa azione <u>non può essere annullata</u>.`,
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#d33',
|
||||||
|
cancelButtonColor: '#3085d6',
|
||||||
|
confirmButtonText: 'Sì, rimuovimi definitivamente',
|
||||||
|
cancelButtonText: 'No, mantienimi iscritto',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
fetch('remove_school.php', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: 'school_id=' + schoolId
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Rimozione completata',
|
||||||
|
text: `Non fai più parte di ${schoolName}.`,
|
||||||
|
icon: 'success',
|
||||||
|
timer: 3000
|
||||||
|
}).then(() => {
|
||||||
|
location.reload(); // o redirect a select_school.php
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Swal.fire('Errore', data.message || 'Impossibile rimuoverti dalla scuola.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Swal.fire('Errore', 'Problema di connessione con il server.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
<!-- Modal Cambia Scuola -->
|
<!-- Modal Cambia Scuola -->
|
||||||
<div class="modal fade" id="changeSchoolModal" tabindex="-1">
|
<div class="modal fade" id="changeSchoolModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
|
|||||||