-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
- Giorni di Chiusura -
-
-
- Torna alla Dashboard
-
+
+
+
Giorni di Chiusura - = htmlspecialchars($shop_name) ?>
+
+
+
+ Dashboard
+
-
-
-
-
+
+
+
+ = htmlspecialchars($success_message) ?>
+
+
+
+
+ = htmlspecialchars($error_message) ?>
+
+
+
-
-
-
+
+
+
+ Non hai ancora impostato giorni di chiusura.
+ Aggiungine uno per bloccare le prenotazioni in quei giorni.
-
- + Aggiungine uno per bloccare le prenotazioni in quei giorni.
-
-
-
+ = htmlspecialchars($day['date']) ?>
+ = htmlspecialchars($day['title'] ?: $day['description'] ?: 'Chiusura') ?>
+
+
+ Sì (ogni anno)
+
+ No
+
+
+
+
+
+
+
+
+
+
+
+
+
| Data Inizio | -Data Fine | -Descrizione | -Azioni | -
|---|
| - | - | - | - - - | +Data | +Titolo / Descrizione | +Ricorrente | +Azioni |
|---|
+
+
-
-
-
+
@@ -260,34 +249,31 @@ $days_off = $stmt->fetchAll();
Aggiungi Giorno di Chiusura
- +
+
+
-
+
+
+
+
+
+
+
+
@@ -296,21 +282,22 @@ $days_off = $stmt->fetchAll();
-
diff --git a/public/userarea/include/headscript.php b/public/userarea/include/headscript.php
index 693ec61..b5cf0cd 100644
--- a/public/userarea/include/headscript.php
+++ b/public/userarea/include/headscript.php
@@ -68,8 +68,8 @@ $photouser = $_SESSION["photouser"];
// include school settings
-include('schoolid_select.php');
+//include('schoolid_select.php');
// include school settings
-include('school_settings_loader.php');
+//include('school_settings_loader.php');
diff --git a/public/userarea/include/navbar.php b/public/userarea/include/navbar.php
index ec3ca73..ab9e96d 100644
--- a/public/userarea/include/navbar.php
+++ b/public/userarea/include/navbar.php
@@ -1,135 +1,129 @@
-prepare("SELECT name, logo FROM schools WHERE id = ?");
- $stmt_school->execute([$school_id]);
- $current_school = $stmt_school->fetch(PDO::FETCH_ASSOC);
- echo $current_school['name'];
- echo "Ciao";
- 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
- }
- }
- }
-}
-?>
+
Modifica Giorno di Chiusura
- +
-
+
-
- YogiBoook
+HairBook
+
-
-
+
-
-
-
-
- Utente +
- Cliente
-
-Dashboard Utente+Dashboard
-
-
+
+ +I miei appuntamenti+ +
+
-
+
-Le mie prenotazioni+Prodotti & Servizi
-
-
- -Certificati- -
-
-
-
-
- -Shop- -
-
-
-
-
+
Carrello
-
-
- -Impostazioni+ ++Il mio profilo
- hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?>
-
- Proprietario Scuola + + hasRole('Owner') || Auth::user()->hasRole('Admin')): ?> +
- Titolare Salone
-
-
- -Dashboard Scuola+ ++Dashboard Salone
-
-
- -Calendario Lezioni+ ++Appuntamenti+ +
+
-
+
+ +Clienti+ +
+
-
+
+ +Staff / Parrucchieri+ +
+
-
+
+ +Servizi+ +
+
-
+
+ +Incassi & Cassa
- @@ -139,77 +133,44 @@ if (!empty($_SESSION['school_id'])) {
-
-
+
+ +Giorni chiusi+ +
+
-
+
-Impostazioni+Impostazioni Salone
-
- Insegnanti - hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?> + + hasRole('Admin')): ?> +
- Amministrazione
-
-
- -Profilo insegnanti+ ++Gestione Utenti
-
+
- hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
-
-
-
-
- -Il mio profilo- -
-
-
-
-
- hasRole('Admin'))) : ?>
-
- Subscription Area -
-
-
- -Subscription- -
-
-
-
- -Subscription Plan- -
-
- Others -
-
-
- -Template- -
-
-
-
- -Documentation- -
-
-
-
- -Support- -
-
+
+
- Altro +
-
+
+ +Supporto+ +
+
-
+
+ +Esci+ +
-
-
-
-
\ No newline at end of file
diff --git a/public/userarea/onboarding_salon.php b/public/userarea/onboarding_salon.php
new file mode 100644
index 0000000..712bbda
--- /dev/null
+++ b/public/userarea/onboarding_salon.php
@@ -0,0 +1,200 @@
+getConnection();
+
+if (!isset($iduserlogin)) {
+ header("Location: login.php"); // o la tua pagina di login
+ exit;
+}
+
+// Se ha già un salone → torna alla dashboard
+$stmt = $pdo->prepare("SELECT id FROM shops WHERE owner_id = ? LIMIT 1");
+$stmt->execute([$iduserlogin]);
+if ($stmt->fetch()) {
+ header("Location: salon_dashboard.php");
+ exit;
+}
+
+$errors = [];
+$success = false;
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $name = trim($_POST['name'] ?? '');
+ $address = trim($_POST['address'] ?? '');
+ $city = trim($_POST['city'] ?? '');
+ $province = strtoupper(trim($_POST['province'] ?? ''));
+ $zip_code = trim($_POST['zip_code'] ?? '');
+ $phone = trim($_POST['phone'] ?? '');
+ $email = trim($_POST['email'] ?? '');
+
+ // Validazioni minime
+ if (empty($name)) $errors[] = "Il nome del salone è obbligatorio";
+ if (empty($city)) $errors[] = "La città è obbligatoria";
+ if (empty($phone) && empty($email)) {
+ $errors[] = "Inserisci almeno telefono oppure email";
+ }
+
+ if (empty($errors)) {
+ // Slug generico ma unico abbastanza
+ $slug_base = strtolower(preg_replace('/[^a-z0-9]+/', '-', $name));
+ $slug = $slug_base . '-' . substr(md5(time()), 0, 6);
+
+ $stmt = $pdo->prepare("
+ INSERT INTO shops (
+ owner_id, name, slug, address, city, province, zip_code,
+ phone, email, active, created_at, updated_at
+ ) VALUES (
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NOW(), NOW()
+ )
+ ");
+
+ $ok = $stmt->execute([
+ $iduserlogin,
+ $name,
+ $slug,
+ $address,
+ $city,
+ $province,
+ $zip_code,
+ $phone,
+ $email
+ ]);
+
+ if ($ok) {
+ $success = true;
+ // Opzionale: crea orari di default
+ $new_shop_id = $pdo->lastInsertId();
+
+ // Esempio orari base (lun-ven 9-19, sab 9-13, dom chiuso)
+ $default_hours = [
+ ['day_of_week' => 1, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '19:00:00'],
+ ['day_of_week' => 2, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '19:00:00'],
+ ['day_of_week' => 3, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '19:00:00'],
+ ['day_of_week' => 4, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '19:00:00'],
+ ['day_of_week' => 5, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '19:00:00'],
+ ['day_of_week' => 6, 'is_open' => 1, 'open_time' => '09:00:00', 'close_time' => '13:00:00'],
+ ['day_of_week' => 0, 'is_open' => 0],
+ ];
+
+ $stmt_hour = $pdo->prepare("
+ INSERT INTO shop_hours (shop_id, day_of_week, is_open, open_time, close_time)
+ VALUES (?, ?, ?, ?, ?)
+ ");
+ foreach ($default_hours as $h) {
+ $stmt_hour->execute([
+ $new_shop_id,
+ $h['day_of_week'],
+ $h['is_open'],
+ $h['open_time'] ?? null,
+ $h['close_time'] ?? null
+ ]);
+ }
+
+ // Reindirizza dopo 2 secondi
+ header("Refresh: 2; url=salon_dashboard.php");
+ } else {
+ $errors[] = "Errore durante la creazione del salone. Riprova.";
+ }
+ }
+}
+?>
+
+
+
+
+
+
+
+
+
-
-
-
-
- = htmlspecialchars($school_display_name) ?>
-
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($shop_name ?? 'Il mio salone') ?>
- hasRole('User')) || (Auth::user()->hasRole('Admin'))) : ?>
- -
+
+
+ hasRole('User') || Auth::user()->hasRole('Admin')): ?>
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/userarea/salon_dashboard.php b/public/userarea/salon_dashboard.php
new file mode 100644
index 0000000..fc07c24
--- /dev/null
+++ b/public/userarea/salon_dashboard.php
@@ -0,0 +1,336 @@
+getConnection();
+
+// Verifica utente loggato
+if (!isset($iduserlogin)) {
+ die("Errore: ID utente non definito.");
+}
+
+// =========================================================================
+// Controllo se l'utente ha almeno un salone
+// =========================================================================
+$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
+$stmt->execute([$iduserlogin]);
+$hasShop = $stmt->fetchColumn() > 0;
+
+if (!$hasShop) {
+ // Nessun salone → vai alla creazione
+ header("Location: onboarding_salon.php");
+ exit;
+}
+
+// =========================================================================
+// Carichiamo il salone (prendiamo il primo creato o quello più recente)
+// =========================================================================
+$stmt = $pdo->prepare("
+ SELECT
+ id, name, slug, address, city, province, zip_code,
+ phone, email, instagram, logo, description, active
+ FROM shops
+ WHERE owner_id = ?
+ ORDER BY created_at ASC
+ LIMIT 1
+");
+$stmt->execute([$iduserlogin]);
+$shop = $stmt->fetch(PDO::FETCH_ASSOC);
+
+if (!$shop) {
+ // Dovrebbe essere impossibile, ma per sicurezza
+ die("Errore: salone non trovato nonostante il conteggio dicesse di sì.");
+}
+
+$shop_id = $shop['id'];
+$shop_name = $shop['name'];
+
+// Servizi attivi
+$stmt = $pdo->prepare("
+ SELECT id, name, duration_minutes, price, category, color_hex
+ FROM services
+ WHERE shop_id = ? AND is_active = 1
+ ORDER BY category, `order`, name
+");
+$stmt->execute([$shop_id]);
+$services = $stmt->fetchAll();
+
+// Staff attivo e visibile online
+$stmt = $pdo->prepare("
+ SELECT s.id, s.first_name, s.last_name, s.nickname, s.color_hex,
+ u.avatar
+ FROM staff s
+ LEFT JOIN auth_users u ON s.user_id = u.id
+ WHERE s.shop_id = ? AND s.is_active = 1 AND s.can_book_online = 1
+ ORDER BY s.first_name, s.last_name
+");
+$stmt->execute([$shop_id]);
+$staff_members = $stmt->fetchAll();
+
+// Intervallo appuntamenti (default: oggi)
+$start_date = $_GET['start_date'] ?? date('Y-m-d');
+$end_date = $_GET['end_date'] ?? $start_date;
+
+// Appuntamenti
+$stmt = $pdo->prepare("
+ SELECT
+ a.id,
+ a.start_at,
+ a.end_at,
+ a.status,
+ a.notes,
+ a.price_paid,
+ c.first_name AS customer_first,
+ c.last_name AS customer_last,
+ c.phone AS customer_phone,
+ s.name AS service_name,
+ s.color_hex AS service_color,
+ st.first_name AS staff_first,
+ st.last_name AS staff_last,
+ st.color_hex AS staff_color
+ FROM appointments a
+ LEFT JOIN customers c ON a.customer_id = c.id
+ LEFT JOIN services s ON a.service_id = s.id
+ LEFT JOIN staff st ON a.staff_id = st.id
+ WHERE a.shop_id = ?
+ AND DATE(a.start_at) BETWEEN ? AND ?
+ ORDER BY a.start_at
+");
+$stmt->execute([$shop_id, $start_date, $end_date]);
+$appointments = $stmt->fetchAll();
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Benvenuto nel tuo gestionale!
+Crea il tuo salone per iniziare
+
+
+
+
+
+
+
+
+ Salone creato con successo!
+Stai per essere reindirizzato alla dashboard...
+ +
+
+
+
+
+
+
+ × = htmlspecialchars($err) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/userarea/school_dashboard.php b/public/userarea/school_dashboard.php
new file mode 100644
index 0000000..95d6edb
--- /dev/null
+++ b/public/userarea/school_dashboard.php
@@ -0,0 +1,1982 @@
+getConnection();
+
+// Verifica che iduserlogin sia definito
+if (!isset($iduserlogin)) {
+ die("Errore: ID utente non definito.");
+}
+
+// Recupera i dati della scuola in base all'utente loggato
+$stmt = $pdo->prepare("
+ SELECT id, name, website, email, phone, description, address_street, address_city, address_postal_code, address_province, address_country, logo, status
+ FROM schools
+ WHERE owner_id = ?
+");
+$stmt->execute([$iduserlogin]);
+$school = $stmt->fetch();
+if (!$school) {
+ die("Errore: Nessuna scuola trovata per l'utente loggato.");
+}
+$school_id = $school['id'];
+$school_name = $school['name'];
+
+// Recupera tutte le categorie disponibili
+$stmt = $pdo->prepare("SELECT id, name FROM class_categories WHERE status = 'active' ORDER BY name");
+$stmt->execute();
+$categories = $stmt->fetchAll();
+
+// Recupera tutti gli insegnanti della scuola
+// Teachers linked to this school (active/pending)
+$stmt = $pdo->prepare("
+ 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
+function resizeImage($source_path, $dest_path, $max_width = 800)
+{
+ list($width, $height, $type) = getimagesize($source_path);
+ if ($width <= $max_width) {
+ copy($source_path, $dest_path);
+ return;
+ }
+
+ $new_width = $max_width;
+ $new_height = (int)(($height * $new_width) / $width);
+
+ switch ($type) {
+ case IMAGETYPE_JPEG:
+ $source = imagecreatefromjpeg($source_path);
+ break;
+ case IMAGETYPE_PNG:
+ $source = imagecreatefrompng($source_path);
+ break;
+ case IMAGETYPE_GIF:
+ $source = imagecreatefromgif($source_path);
+ break;
+ default:
+ throw new Exception("Formato immagine non supportato.");
+ }
+
+ $dest = imagecreatetruecolor($new_width, $new_height);
+ if ($type == IMAGETYPE_PNG) {
+ imagealphablending($dest, false);
+ imagesavealpha($dest, true);
+ }
+ imagecopyresampled($dest, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
+
+ switch ($type) {
+ case IMAGETYPE_JPEG:
+ imagejpeg($dest, $dest_path, 90);
+ break;
+ case IMAGETYPE_PNG:
+ imagepng($dest, $dest_path);
+ break;
+ case IMAGETYPE_GIF:
+ imagegif($dest, $dest_path);
+ break;
+ }
+
+ imagedestroy($source);
+ imagedestroy($dest);
+}
+
+// Gestione delle azioni (aggiunta, modifica, cancellazione)
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ if (isset($_POST['action'])) {
+ $action = $_POST['action'];
+
+ // Aggiunta di una nuova classe
+ if ($action === 'add') {
+ $category_id = $_POST['category_id'] ?? 0;
+ $name = $_POST['name'] ?? '';
+ $description = $_POST['description'] ?? null;
+ $requirements = $_POST['requirements'] ?? null;
+ $status = $_POST['status'] === 'active' ? 'active' : 'inactive';
+
+ if (empty($name) || $category_id <= 0) {
+ $error = "I campi obbligatori non sono stati compilati.";
+ } else {
+ $photo = null;
+ if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
+ $file = $_FILES['photo'];
+ $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)) {
+ $new_filename = "photoclass/{$school_id}-{$timestamp}-{$original_name}";
+ $temp_path = $file['tmp_name'];
+ try {
+ resizeImage($temp_path, $new_filename);
+ $photo = $new_filename;
+ } catch (Exception $e) {
+ $error = "Errore durante il ridimensionamento della foto: " . $e->getMessage();
+ }
+ } else {
+ $error = "Estensione del file non consentita. Usa JPG, JPEG, PNG o GIF.";
+ }
+ }
+
+ if (!isset($error)) {
+ $stmt = $pdo->prepare("
+ INSERT INTO classes (school_id, category_id, name, description, photo, requirements, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ ");
+ $success = $stmt->execute([
+ $school_id,
+ $category_id,
+ $name,
+ $description,
+ $photo,
+ $requirements,
+ $status
+ ]);
+
+ if ($success) {
+ $success_message = "Classe aggiunta con successo!";
+ } else {
+ $error = "Errore durante l'aggiunta della classe.";
+ }
+ }
+ }
+ }
+
+ // Modifica di una classe esistente
+ if ($action === 'edit') {
+ $id = $_POST['id'] ?? 0;
+ $category_id = $_POST['category_id'] ?? 0;
+ $name = $_POST['name'] ?? '';
+ $description = $_POST['description'] ?? null;
+ $requirements = $_POST['requirements'] ?? null;
+ $status = $_POST['status'] === 'active' ? 'active' : 'inactive';
+
+ if (empty($name) || $category_id <= 0) {
+ $error = "I campi obbligatori non sono stati compilati.";
+ } else {
+ // Recupera la classe esistente per ottenere il percorso della foto attuale
+ $stmt = $pdo->prepare("SELECT photo FROM classes WHERE id = ? AND school_id = ?");
+ $stmt->execute([$id, $school_id]);
+ $class = $stmt->fetch();
+ if (!$class) {
+ $error = "Classe non trovata.";
+ } else {
+ $photo = $class['photo'];
+ if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
+ $file = $_FILES['photo'];
+ $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)) {
+ $new_filename = "photoclass/{$school_id}-{$timestamp}-{$original_name}";
+ $temp_path = $file['tmp_name'];
+ try {
+ resizeImage($temp_path, $new_filename);
+ $photo = $new_filename;
+ if ($class['photo'] && file_exists($class['photo'])) {
+ unlink($class['photo']);
+ }
+ } catch (Exception $e) {
+ $error = "Errore durante il ridimensionamento della foto: " . $e->getMessage();
+ }
+ } else {
+ $error = "Estensione del file non consentita. Usa JPG, JPEG, PNG o GIF.";
+ }
+ }
+
+ if (!isset($error)) {
+ $stmt = $pdo->prepare("
+ UPDATE classes
+ SET category_id = ?, name = ?, description = ?, photo = ?, requirements = ?, status = ?
+ WHERE id = ? AND school_id = ?
+ ");
+ $success = $stmt->execute([
+ $category_id,
+ $name,
+ $description,
+ $photo,
+ $requirements,
+ $status,
+ $id,
+ $school_id
+ ]);
+
+ if ($success) {
+ $success_message = "Classe aggiornata con successo!";
+ } else {
+ $error = "Errore durante l'aggiornamento della classe.";
+ }
+ }
+ }
+ }
+ }
+
+ // Cancellazione di una classe
+ if ($action === 'delete') {
+ $id = $_POST['id'] ?? 0;
+ $stmt = $pdo->prepare("SELECT photo FROM classes WHERE id = ? AND school_id = ?");
+ $stmt->execute([$id, $school_id]);
+ $class = $stmt->fetch();
+ if ($class) {
+ if ($class['photo'] && file_exists($class['photo'])) {
+ unlink($class['photo']);
+ }
+ $stmt = $pdo->prepare("DELETE FROM classes WHERE id = ? AND school_id = ?");
+ $success = $stmt->execute([$id, $school_id]);
+
+ if ($success) {
+ $success_message = "Classe eliminata con successo!";
+ } else {
+ $error = "Errore durante l'eliminazione della classe.";
+ }
+ } else {
+ $error = "Classe non trovata.";
+ }
+ }
+
+ // Aggiunta di una variazione
+ if ($action === 'add_variation') {
+ $class_id = $_POST['class_id'] ?? 0;
+ $level = in_array($_POST['level'], ['beginner', 'intermediate', 'advanced']) ? $_POST['level'] : 'beginner';
+ $typical_duration = $_POST['typical_duration'] ? (int)$_POST['typical_duration'] : null;
+ $max_capacity = $_POST['max_capacity'] ? (int)$_POST['max_capacity'] : 0;
+ $day_of_week = in_array($_POST['day_of_week'], ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']) ? $_POST['day_of_week'] : null;
+ $start_time = $_POST['start_time'] ?? '';
+ $room_name = $_POST['room_name'] ?? null;
+ $notes = $_POST['notes'] ?? null;
+ $status = $_POST['status'] === 'active' ? 'active' : 'inactive';
+
+ if ($class_id <= 0 || empty($day_of_week) || empty($start_time)) {
+ $error = "I campi obbligatori non sono stati compilati.";
+ } else {
+ $photo = null;
+ if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
+ $file = $_FILES['photo'];
+ $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)) {
+ $new_filename = "photoclass/{$school_id}-{$timestamp}-{$original_name}";
+ $temp_path = $file['tmp_name'];
+ try {
+ resizeImage($temp_path, $new_filename);
+ $photo = $new_filename;
+ } catch (Exception $e) {
+ $error = "Errore durante il ridimensionamento della foto: " . $e->getMessage();
+ }
+ } else {
+ $error = "Estensione del file non consentita. Usa JPG, JPEG, PNG o GIF.";
+ }
+ }
+
+ if (!isset($error)) {
+ $stmt = $pdo->prepare("
+ INSERT INTO class_types (class_id, school_id, level, typical_duration, max_capacity, day_of_week, start_time, room_name, notes, photo, status)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ");
+ $success = $stmt->execute([
+ $class_id,
+ $school_id,
+ $level,
+ $typical_duration,
+ $max_capacity,
+ $day_of_week,
+ $start_time,
+ $room_name,
+ $notes,
+ $photo,
+ $status
+ ]);
+
+ if ($success) {
+ $success_message = "Variazione aggiunta con successo!";
+ } else {
+ $error = "Errore durante l'aggiunta della variazione.";
+ }
+ }
+ }
+ }
+
+ // Modifica di una variazione
+ if ($action === 'edit_variation') {
+ $id = $_POST['id'] ?? 0;
+ $class_id = $_POST['class_id'] ?? 0;
+ $level = in_array($_POST['level'], ['beginner', 'intermediate', 'advanced']) ? $_POST['level'] : 'beginner';
+ $typical_duration = $_POST['typical_duration'] ? (int)$_POST['typical_duration'] : null;
+ $max_capacity = $_POST['max_capacity'] ? (int)$_POST['max_capacity'] : 0;
+ $day_of_week = in_array($_POST['day_of_week'], ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']) ? $_POST['day_of_week'] : null;
+ $start_time = $_POST['start_time'] ?? '';
+ $room_name = $_POST['room_name'] ?? null;
+ $notes = $_POST['notes'] ?? null;
+ $status = $_POST['status'] === 'active' ? 'active' : 'inactive';
+
+ if ($id <= 0 || $class_id <= 0 || empty($day_of_week) || empty($start_time)) {
+ $error = "I campi obbligatori non sono stati compilati.";
+ } else {
+ // Recupera la variazione esistente per ottenere il percorso della foto attuale
+ $stmt = $pdo->prepare("SELECT photo FROM class_types WHERE id = ? AND school_id = ? AND class_id = ?");
+ $stmt->execute([$id, $school_id, $class_id]);
+ $variation = $stmt->fetch();
+ if (!$variation) {
+ $error = "Variazione non trovata.";
+ } else {
+ $photo = $variation['photo'];
+ if (isset($_FILES['photo']) && $_FILES['photo']['error'] === UPLOAD_ERR_OK) {
+ $file = $_FILES['photo'];
+ $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)) {
+ $new_filename = "photoclass/{$school_id}-{$timestamp}-{$original_name}";
+ $temp_path = $file['tmp_name'];
+ try {
+ resizeImage($temp_path, $new_filename);
+ $photo = $new_filename;
+ if ($variation['photo'] && file_exists($variation['photo'])) {
+ unlink($variation['photo']);
+ }
+ } catch (Exception $e) {
+ $error = "Errore durante il ridimensionamento della foto: " . $e->getMessage();
+ }
+ } else {
+ $error = "Estensione del file non consentita. Usa JPG, JPEG, PNG o GIF.";
+ }
+ }
+
+ if (!isset($error)) {
+ $stmt = $pdo->prepare("
+ UPDATE class_types
+ SET level = ?, typical_duration = ?, max_capacity = ?, day_of_week = ?, start_time = ?, room_name = ?, notes = ?, photo = ?, status = ?
+ WHERE id = ? AND school_id = ? AND class_id = ?
+ ");
+ $success = $stmt->execute([
+ $level,
+ $typical_duration,
+ $max_capacity,
+ $day_of_week,
+ $start_time,
+ $room_name,
+ $notes,
+ $photo,
+ $status,
+ $id,
+ $school_id,
+ $class_id
+ ]);
+
+ if ($success) {
+ $success_message = "Variazione aggiornata con successo!";
+ } else {
+ $error = "Errore durante l'aggiornamento della variazione.";
+ }
+ }
+ }
+ }
+ }
+
+ // Cancellazione di una variazione
+ if ($action === 'delete_variation') {
+ $id = $_POST['id'] ?? 0;
+ $class_id = $_POST['class_id'] ?? 0;
+ $stmt = $pdo->prepare("SELECT photo FROM class_types WHERE id = ? AND school_id = ? AND class_id = ?");
+ $stmt->execute([$id, $school_id, $class_id]);
+ $variation = $stmt->fetch();
+ if ($variation) {
+ if ($variation['photo'] && file_exists($variation['photo'])) {
+ unlink($variation['photo']);
+ }
+ $stmt = $pdo->prepare("DELETE FROM class_types WHERE id = ? AND school_id = ? AND class_id = ?");
+ $success = $stmt->execute([$id, $school_id, $class_id]);
+
+ if ($success) {
+ $success_message = "Variazione eliminata con successo!";
+ } else {
+ $error = "Errore durante l'eliminazione della variazione.";
+ }
+ } else {
+ $error = "Variazione non trovata.";
+ }
+ }
+
+ // Assegnazione di un insegnante
+ if ($action === 'assign_teacher') {
+ $class_type_id = $_POST['class_type_id'] ?? 0;
+ $teacher_id = !empty($_POST['teacher_id']) ? (int)$_POST['teacher_id'] : null;
+
+ // Verifica che la variazione appartenga alla scuola
+ $stmt = $pdo->prepare("SELECT id FROM class_types WHERE id = ? AND school_id = ?");
+ $stmt->execute([$class_type_id, $school_id]);
+ if (!$stmt->fetch()) {
+ $error = "Variazione non trovata.";
+ } else {
+ // Se teacher_id è null, rimuoviamo l'assegnazione; altrimenti, assegniamo l'insegnante
+ $stmt = $pdo->prepare("UPDATE class_types SET teacher_id = ? WHERE id = ?");
+ $success = $stmt->execute([$teacher_id, $class_type_id]);
+
+ if ($success) {
+ $success_message = "Insegnante assegnato con successo!";
+ } else {
+ $error = "Errore durante l'assegnazione dell'insegnante.";
+ }
+ }
+ }
+
+ // Propagazione delle sessioni
+ if ($action === 'propagate_sessions') {
+ $class_type_id = $_POST['class_type_id'] ?? 0;
+ $start_date = $_POST['start_date'] ?? '';
+ $end_date = $_POST['end_date'] ?? '';
+
+ // Validazione delle date
+ if (empty($start_date) || empty($end_date)) {
+ $error = "Le date di inizio e fine sono obbligatorie.";
+ } elseif (strtotime($end_date) < strtotime($start_date)) {
+ $error = "La data di fine non può essere precedente alla data di inizio.";
+ } else {
+ // Verifica che la variazione appartenga alla scuola
+ $stmt = $pdo->prepare("
+ SELECT ct.day_of_week, ct.start_time, ct.typical_duration, ct.teacher_id, ct.class_id, ct.school_id, ct.max_capacity, ct.notes
+ FROM class_types ct
+ WHERE ct.id = ? AND ct.school_id = ?
+ ");
+ $stmt->execute([$class_type_id, $school_id]);
+ $variation = $stmt->fetch();
+ if (!$variation) {
+ $error = "Variazione non trovata.";
+ } else {
+ // Recupera i giorni di chiusura della scuola
+ $stmt = $pdo->prepare("
+ SELECT start_date, end_date
+ FROM day_off
+ WHERE school_id = ? AND (
+ (start_date BETWEEN ? AND ?) OR
+ (end_date BETWEEN ? AND ?) OR
+ (start_date <= ? AND end_date >= ?)
+ )
+ ");
+ $stmt->execute([
+ $school_id,
+ $start_date,
+ $end_date,
+ $start_date,
+ $end_date,
+ $start_date,
+ $end_date
+ ]);
+ $days_off = $stmt->fetchAll();
+
+ // Crea un array di giorni di chiusura
+ $off_dates = [];
+ foreach ($days_off as $day_off) {
+ $current = new DateTime($day_off['start_date']);
+ $end = new DateTime($day_off['end_date']);
+ while ($current <= $end) {
+ $off_dates[] = $current->format('Y-m-d');
+ $current->modify('+1 day');
+ }
+ }
+
+ // Mappa i giorni della settimana per il confronto
+ $days_map = [
+ 'monday' => 'Monday',
+ 'tuesday' => 'Tuesday',
+ 'wednesday' => 'Wednesday',
+ 'thursday' => 'Thursday',
+ 'friday' => 'Friday',
+ 'saturday' => 'Saturday',
+ 'sunday' => 'Sunday'
+ ];
+ $day_of_week_english = $days_map[$variation['day_of_week']];
+
+ // Calcola l'orario di fine
+ $start_time = new DateTime($variation['start_time']);
+ $end_time = clone $start_time;
+ if ($variation['typical_duration']) {
+ $end_time->modify("+{$variation['typical_duration']} minutes");
+ } else {
+ $end_time->modify("+60 minutes"); // Default: 1 ora se la durata non è specificata
+ }
+
+ // Genera un propagation_id univoco
+ $propagation_id = uniqid('prop_', true);
+
+ // Genera le sessioni
+ $current_date = new DateTime($start_date);
+ $end_date_dt = new DateTime($end_date);
+ $end_date_dt->setTime(23, 59, 59); // Include l'ultimo giorno
+
+ // Prepara la query per verificare se una sessione esiste già
+ $checkStmt = $pdo->prepare("
+ SELECT id
+ FROM class_sessions
+ WHERE class_type_id = ? AND session_date = ?
+");
+
+ // Prepara la query di inserimento
+ $stmt = $pdo->prepare("
+ INSERT INTO class_sessions (class_id, school_id, class_type_id, session_date, start_time, end_time, teacher_id, max_capacity, notes, status, propagation_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'scheduled', ?)
+");
+
+ $sessions_created = 0;
+ $sessions_skipped = 0;
+ while ($current_date <= $end_date_dt) {
+ $day_of_week = $current_date->format('l');
+ $current_date_str = $current_date->format('Y-m-d');
+
+ if (!in_array($current_date_str, $off_dates) && $day_of_week === $day_of_week_english) {
+ $session_date = $current_date->format('Y-m-d');
+ $start_time_str = $start_time->format('H:i:s');
+ $end_time_str = $end_time->format('H:i:s');
+
+ // Verifica se una sessione esiste già per questa variazione e data
+ $checkStmt->execute([$class_type_id, $session_date]);
+ if ($checkStmt->fetch()) {
+ $sessions_skipped++;
+ continue; // Salta se esiste già
+ }
+
+ try {
+ $stmt->execute([
+ $variation['class_id'],
+ $variation['school_id'],
+ $class_type_id,
+ $session_date,
+ $start_time_str,
+ $end_time_str,
+ $variation['teacher_id'],
+ $variation['max_capacity'],
+ $variation['notes'],
+ $propagation_id
+ ]);
+ $sessions_created++;
+ } catch (PDOException $e) {
+ $error = "Errore durante la propagazione delle sessioni: " . $e->getMessage();
+ break;
+ }
+ }
+ $current_date->modify('+1 day');
+ }
+
+ if (!isset($error)) {
+ $success_message = "Propagate $sessions_created sessioni con successo! (Saltate $sessions_skipped sessioni già esistenti) (ID Propagazione: $propagation_id)";
+ echo "";
+ }
+
+ if (!isset($error)) {
+ $success_message = "Propagate $sessions_created sessioni con successo! (ID Propagazione: $propagation_id)";
+ echo "";
+ }
+ }
+ }
+ }
+
+ // Rimozione di una propagazione
+ if ($action === 'remove_propagation') {
+ $propagation_id = $_POST['propagation_id'] ?? '';
+ $class_type_id = $_POST['class_type_id'] ?? 0;
+
+ if (empty($propagation_id) || $class_type_id <= 0) {
+ $error = "ID di propagazione o variazione non validi.";
+ } else {
+ // Verifica che la variazione appartenga alla scuola
+ $stmt = $pdo->prepare("SELECT id FROM class_types WHERE id = ? AND school_id = ?");
+ $stmt->execute([$class_type_id, $school_id]);
+ if (!$stmt->fetch()) {
+ $error = "Variazione non trovata.";
+ } else {
+ // Elimina tutte le sessioni associate a questa propagazione
+ $stmt = $pdo->prepare("
+ DELETE FROM class_sessions
+ WHERE propagation_id = ? AND class_type_id = ?
+ ");
+ $stmt->execute([$propagation_id, $class_type_id]);
+
+ $deleted_rows = $stmt->rowCount();
+ $success_message = "Propagazione rimossa con successo! ($deleted_rows sessioni eliminate)";
+ }
+ }
+ }
+ // Cancellazione di una sessione
+ if ($action === 'delete_session') {
+ $id = $_POST['id'] ?? 0;
+ // Verifica che la sessione appartenga alla scuola
+ $stmt = $pdo->prepare("
+ SELECT cs.id
+ 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([$id, $school_id]);
+ if ($stmt->fetch()) {
+ $stmt = $pdo->prepare("DELETE FROM class_sessions WHERE id = ?");
+ $success = $stmt->execute([$id]);
+
+ if ($success) {
+ $success_message = "Sessione eliminata con successo!";
+ } else {
+ $error = "Errore durante l'eliminazione della sessione.";
+ }
+ } else {
+ $error = "Sessione non trovata.";
+ }
+ }
+
+ // Modifica di una sessione
+ if ($action === 'edit_session') {
+ $id = $_POST['id'] ?? 0;
+ $class_type_id = $_POST['class_type_id'] ?? 0;
+ $session_date = $_POST['session_date'] ?? '';
+ $start_time = $_POST['start_time'] ?? '';
+ $end_time = $_POST['end_time'] ?? '';
+ $teacher_id = !empty($_POST['teacher_id']) ? (int)$_POST['teacher_id'] : null;
+ $status = in_array($_POST['status'], ['scheduled', 'completed', 'cancelled']) ? $_POST['status'] : 'scheduled';
+
+ if (empty($session_date) || empty($start_time) || empty($end_time)) {
+ $error = "I campi obbligatori non sono stati compilati.";
+ } elseif (strtotime($end_time) <= strtotime($start_time)) {
+ $error = "L'orario di fine non può essere precedente o uguale all'orario di inizio.";
+ } else {
+ // Verifica che la sessione appartenga alla scuola
+ $stmt = $pdo->prepare("
+ SELECT cs.id
+ 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([$id, $school_id]);
+ if (!$stmt->fetch()) {
+ $error = "Sessione non trovata.";
+ } else {
+ $stmt = $pdo->prepare("
+ UPDATE class_sessions
+ SET session_date = ?, start_time = ?, end_time = ?, teacher_id = ?, status = ?
+ WHERE id = ?
+ ");
+ $success = $stmt->execute([
+ $session_date,
+ $start_time,
+ $end_time,
+ $teacher_id,
+ $status,
+ $id
+ ]);
+
+ if ($success) {
+ $success_message = "Sessione aggiornata con successo!";
+ } else {
+ $error = "Errore durante l'aggiornamento della sessione.";
+ }
+ }
+ }
+ }
+ // Non Reindirizza per evitare il doppio invio del form
+
+ }
+}
+
+// Recupera tutte le classi della scuola con le loro variazioni
+$stmt = $pdo->prepare("
+ SELECT c.*, cc.name AS category_name
+ FROM classes c
+ LEFT JOIN class_categories cc ON c.category_id = cc.id
+ WHERE c.school_id = ?
+ ORDER BY c.created_at DESC
+");
+$stmt->execute([$school_id]);
+$classes = $stmt->fetchAll();
+
+// Recupera le variazioni per ogni classe
+$class_variations = [];
+foreach ($classes as $class) {
+ $stmt = $pdo->prepare("
+ SELECT ct.*, t.first_name AS teacher_first_name, t.last_name AS teacher_last_name
+ FROM class_types ct
+ LEFT JOIN teachers t ON ct.teacher_id = t.id
+ WHERE ct.class_id = ? AND ct.school_id = ?
+ ORDER BY ct.day_of_week, ct.start_time
+ ");
+ $stmt->execute([$class['id'], $school_id]);
+ $class_variations[$class['id']] = $stmt->fetchAll();
+}
+
+// Recupera la data selezionata per le sessioni della giornata (default: oggi)
+$selected_date = $_GET['session_date'] ?? date('Y-m-d');
+$selected_date_dt = new DateTime($selected_date);
+$day_of_week_english = $selected_date_dt->format('l'); // Giorno della settimana in inglese (es. Monday)
+
+// Mappa i giorni della settimana per il confronto
+$days_map_reverse = [
+ 'Monday' => 'monday',
+ 'Tuesday' => 'tuesday',
+ 'Wednesday' => 'wednesday',
+ 'Thursday' => 'thursday',
+ 'Friday' => 'friday',
+ 'Saturday' => 'saturday',
+ 'Sunday' => 'sunday'
+];
+$day_of_week = $days_map_reverse[$day_of_week_english];
+
+// Recupera le sessioni per la data selezionata
+$stmt = $pdo->prepare("
+ SELECT cs.*,
+ ct.level, ct.day_of_week, ct.start_time, ct.room_name, ct.teacher_id,
+ c.name AS class_name, c.photo AS class_photo,
+ ct.photo AS variation_photo,
+ t.first_name AS teacher_first_name, t.last_name AS teacher_last_name
+ 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 cs.session_date = ?
+ AND ct.day_of_week = ?
+ AND c.school_id = ?
+ ORDER BY cs.start_time
+");
+$stmt->execute([$selected_date, $day_of_week, $school_id]);
+$daily_sessions = $stmt->fetchAll();
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($shop_name) ?>
++ Indirizzo: + = htmlspecialchars(implode(', ', array_filter([ + $shop['address'], + $shop['city'], + $shop['zip_code'], + $shop['province'] + ]))) ?: '—' ?> +
+Telefono: = htmlspecialchars($shop['phone'] ?: '—') ?>
+ +IG: @= htmlspecialchars($shop['instagram']) ?>
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + Appuntamenti = ($start_date == $end_date) ? date('d/m/Y', strtotime($start_date)) : "dal " . date('d/m', strtotime($start_date)) . " al " . date('d/m/Y', strtotime($end_date)) ?> +
+
+
+ Oggi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Calendario Appuntamenti
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Situazione Clienti
+
+
+
+
+ Ordini
+
+
+
+
+ Finanze
+
+
+
+ Prodotti
+
+
+
+
+ Aggiungi Utente
+
+
+
+
+ Giorni di Chiusura
+
+
+
+
+ Impostazioni
+
+
+
+
+
+
+
+
+
+
+
+ Classi della scuola
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | + | Categoria | +Nome | +Descrizione | +Foto | +Stato | +Azioni | +||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + + | ++ | + | + |
+
+ |
+ + + + + | + ++ + + + | +||||||||||||||||||
|
+
+
+
+
+
+ Nessuna variazione disponibile per questa classe. + +
+
+
+
|
+ ||||||||||||||||||||||||
+
+
+
+
+
+ Sessioni Programmata
+
+
+
+
+
+
+
+
+ La data di fine non può essere precedente alla data di inizio.
';
+ $end_date = $start_date;
+ }
+
+ // Recupera tutte le sessioni nell'intervallo
+ // === NUOVA QUERY CON CONTEGGIO E LISTA PRENOTATI ===
+ $stmt = $pdo->prepare("
+ SELECT
+ cs.*,
+ ct.level, ct.day_of_week, ct.start_time AS ct_start_time, ct.room_name, ct.teacher_id,
+ c.name AS class_name, c.photo AS class_photo,
+ ct.photo AS variation_photo,
+ t.first_name AS teacher_first_name, t.last_name AS teacher_last_name,
+
+ -- Contiamo gli studenti prenotati (solo booked, attended, rescheduled)
+ (SELECT COUNT(*)
+ FROM session_bookings sb
+ WHERE sb.session_id = cs.id
+ AND sb.status IN ('booked', 'attended', 'rescheduled')
+ ) AS booked_count,
+
+ -- Capacità massima (da class_sessions o da class_types se null)
+ COALESCE(cs.max_capacity, ct.max_capacity, 0) AS effective_capacity,
+
+ -- Lista studenti prenotati (per il modale)
+ (SELECT GROUP_CONCAT(
+ CONCAT(au.first_name, ' ', au.last_name, '|', au.email, '|', COALESCE(au.phone, ''))
+ 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')
+ ) AS booked_students
+ 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 cs.session_date BETWEEN ? AND ?
+ AND c.school_id = ?
+ ORDER BY cs.session_date, cs.start_time
+");
+ $stmt->execute([$start_date, $end_date, $school_id]);
+ $range_sessions = $stmt->fetchAll();
+ ?>
+
+
+
+
+ Sessioni del
+
+ Sessioni dal
+ al
+ ( totali)
+
+
+
+
+
+
+
+
+
+ | Data | +Classe | +Livello | +Orario | +Sala | +Insegnante | +Stato | +Prenotati | +Azioni | +
|---|---|---|---|---|---|---|---|---|
| + Nessuna sessione programmata in questo intervallo. + | +||||||||
| + + | +||||||||
| + | + | + | - | ++ | + Non assegnato'; ?> + | ++ + + + | ++ 0 ? $session['effective_capacity'] : '∞'; + $color = ($capacity !== '∞' && $booked >= $capacity) ? 'danger' : 'success'; + ?> + + | ++ + + | +
+
+
+
+
+
+
+
+
+
+
+ Aggiungi Classe
+ +
+
+
+
+
+
+
+
+
+
+
+ Modifica Classe
+ +
+
+
+
+
+
+
+
+
+
+
+ Assegna Insegnante
+ +
+
+
+
+
+
+
+
+
+
+ Propaga Sessioni
+ +
+
+
+
+
+
+
+
+
+
+ Rimuovi Propagazione
+ +
+
+
+
+
+
+
+
+
+
+
+ Aggiungi Variazione
+ +
+
+
+
+
+
+
+
+
+
+ Modifica Variazione
+ +
+
+
+
+
+
+
+
+
+
+ Modifica Sessione
+ +
+
+
+
+
+
+
+
+
+
+
+ Propagazione Completata
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0): ?>
+
+
+
+
+
+
+ + Calendario Lezioni +
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file
+
+
+
+
+
+ + Prenotati - + + del + ore +
+ +
+
+
+
+
+
+
+ | Nome | +Telefono | +Stato | +|
|---|---|---|---|
| + | + | + | Prenotato | +
+
+
+