fixed bugs
This commit is contained in:
parent
6124e7e8fa
commit
2254ca90ff
132
public/userarea/api/_bootstrap.php
Normal file
132
public/userarea/api/_bootstrap.php
Normal file
@ -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
|
||||
}
|
||||
175
public/userarea/api/api_my_lessons.php
Normal file
175
public/userarea/api/api_my_lessons.php
Normal file
@ -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()
|
||||
]);
|
||||
}
|
||||
88
public/userarea/api/api_user_schools.php
Normal file
88
public/userarea/api/api_user_schools.php
Normal file
@ -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()
|
||||
]);
|
||||
}
|
||||
341
public/userarea/class_type_associate.php
Normal file
341
public/userarea/class_type_associate.php
Normal file
@ -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>
|
||||
@ -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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1052,6 +1052,12 @@ $daily_sessions = $stmt->fetchAll();
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title="Propaga">
|
||||
<i class="bx bx-broadcast"></i> Propaga
|
||||
</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"
|
||||
onclick='fillRemovePropagationModal(<?php echo json_encode([
|
||||
"class_type_id" => $variation['id']
|
||||
|
||||
802
public/userarea/sessions_archive.php
Normal file
802
public/userarea/sessions_archive.php
Normal file
@ -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>
|
||||
413
public/userarea/user-settings.php
Normal file
413
public/userarea/user-settings.php
Normal file
@ -0,0 +1,413 @@
|
||||
<?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)
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
$stmt = $pdo->prepare("SELECT id, name FROM schools WHERE owner_id = ? LIMIT 1");
|
||||
$stmt->execute([$iduserlogin]);
|
||||
$school = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$school) {
|
||||
die("Scuola non trovata.");
|
||||
}
|
||||
|
||||
$school_id = (int)$school['id'];
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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 = "";
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 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>
|
||||
Loading…
x
Reference in New Issue
Block a user