fixed subscription

This commit is contained in:
Claudio 2026-01-23 08:13:41 +01:00
parent 7f7dff32d9
commit 1551e6aca7
10 changed files with 1638 additions and 8 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

View File

@ -0,0 +1,557 @@
<?php
// admin_subscription_plans.php
// Show errors (remove in production)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
// DB connection
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Check login
if (!isset($iduserlogin)) {
die("Errore: ID utente non definito.");
}
/**
* Check if the current user is admin.
* IMPORTANT: set your real admin role_id(s) here.
*/
function isAdmin(PDO $pdo, int $userId): bool
{
// TODO: adjust these role ids according to your system
$adminRoleIds = [1]; // e.g. 1 = superadmin
$stmt = $pdo->prepare("SELECT role_id FROM auth_users WHERE id = ?");
$stmt->execute([$userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return false;
return in_array((int)$row['role_id'], $adminRoleIds, true);
}
if (!isAdmin($pdo, (int)$iduserlogin)) {
die("Accesso negato: pagina riservata all'amministratore.");
}
function formatMoneyFromCents(int $cents, string $currency): string
{
$amount = number_format($cents / 100, 2, ',', '.');
return $amount . ' ' . strtoupper($currency);
}
// Handle POST actions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
// Common fields
$code = trim($_POST['code'] ?? '');
$name = trim($_POST['name'] ?? '');
$description = trim($_POST['description'] ?? '');
$stripe_product_id = trim($_POST['stripe_product_id'] ?? '');
$stripe_price_id = trim($_POST['stripe_price_id'] ?? '');
$currency = strtoupper(trim($_POST['currency'] ?? 'EUR'));
$unit_amount = (int)($_POST['unit_amount'] ?? 0); // cents
$interval = in_array(($_POST['interval'] ?? ''), ['day', 'week', 'month', 'year'], true) ? $_POST['interval'] : 'month';
$interval_count = max(1, (int)($_POST['interval_count'] ?? 1));
$trial_days = max(0, (int)($_POST['trial_days'] ?? 0));
$is_active = isset($_POST['is_active']) ? 1 : 0;
// ADD
if ($action === 'add_plan') {
if ($code === '' || $name === '') {
$error = "Code e Nome sono obbligatori.";
} elseif ($stripe_price_id === '') {
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
} elseif (strlen($currency) !== 3) {
$error = "Currency deve essere nel formato ISO (es. EUR).";
} elseif ($unit_amount < 0) {
$error = "Unit amount non può essere negativo.";
} else {
try {
$stmt = $pdo->prepare("
INSERT INTO billing_plans
(code, name, description, stripe_product_id, stripe_price_id, currency, unit_amount, `interval`, interval_count, trial_days, is_active, created_at, updated_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
");
$stmt->execute([
$code,
$name,
$description ?: null,
$stripe_product_id ?: null,
$stripe_price_id,
$currency,
$unit_amount,
$interval,
$interval_count,
$trial_days,
$is_active
]);
$success_message = "Piano creato con successo.";
} catch (PDOException $e) {
$error = "Errore durante la creazione del piano: " . $e->getMessage();
}
}
}
// EDIT
if ($action === 'edit_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} elseif ($code === '' || $name === '') {
$error = "Code e Nome sono obbligatori.";
} elseif ($stripe_price_id === '') {
$error = "Stripe Price ID è obbligatorio (campo NOT NULL in tabella).";
} elseif (strlen($currency) !== 3) {
$error = "Currency deve essere nel formato ISO (es. EUR).";
} elseif ($unit_amount < 0) {
$error = "Unit amount non può essere negativo.";
} else {
try {
$stmt = $pdo->prepare("
UPDATE billing_plans
SET
code = ?,
name = ?,
description = ?,
stripe_product_id = ?,
stripe_price_id = ?,
currency = ?,
unit_amount = ?,
`interval` = ?,
interval_count = ?,
trial_days = ?,
is_active = ?,
updated_at = NOW()
WHERE id = ?
");
$stmt->execute([
$code,
$name,
$description ?: null,
$stripe_product_id ?: null,
$stripe_price_id,
$currency,
$unit_amount,
$interval,
$interval_count,
$trial_days,
$is_active,
$id
]);
$success_message = "Piano aggiornato con successo.";
} catch (PDOException $e) {
$error = "Errore durante l'aggiornamento del piano: " . $e->getMessage();
}
}
}
// DISABLE
if ($action === 'disable_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} else {
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 0, updated_at = NOW() WHERE id = ?");
$stmt->execute([$id]);
$success_message = "Piano disattivato.";
}
}
// ENABLE
if ($action === 'enable_plan') {
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) {
$error = "ID piano non valido.";
} else {
$stmt = $pdo->prepare("UPDATE billing_plans SET is_active = 1, updated_at = NOW() WHERE id = ?");
$stmt->execute([$id]);
$success_message = "Piano riattivato.";
}
}
}
// Fetch plans
$stmt = $pdo->prepare("SELECT * FROM billing_plans ORDER BY is_active DESC, name ASC");
$stmt->execute();
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<?php include('siteinfo.php'); ?>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10 mb-4">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h5 class="mb-1">Subscription Plans (Admin)</h5>
<p class="mb-0 text-muted">Gestione piani abbonamento delle scuole</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPlanModal">
<i class="bx bx-plus"></i> Nuovo Piano
</button>
</div>
</div>
</div>
</div>
<?php if (isset($error)): ?>
<div class="alert alert-danger" role="alert"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<?php if (isset($success_message)): ?>
<div class="alert alert-success" role="alert"><?php echo htmlspecialchars($success_message); ?></div>
<?php endif; ?>
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Elenco Piani</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="plansTable" class="table table-striped table-bordered">
<thead>
<tr>
<th>Code</th>
<th>Nome</th>
<th>Prezzo</th>
<th>Intervallo</th>
<th>Trial</th>
<th>Stripe Product</th>
<th>Stripe Price *</th>
<th>Stato</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
<?php foreach ($plans as $p): ?>
<tr>
<td><span class="badge bg-dark"><?php echo htmlspecialchars($p['code']); ?></span></td>
<td>
<div class="fw-bold"><?php echo htmlspecialchars($p['name']); ?></div>
<?php if (!empty($p['description'])): ?>
<div class="text-muted small"><?php echo nl2br(htmlspecialchars($p['description'])); ?></div>
<?php endif; ?>
</td>
<td><?php echo formatMoneyFromCents((int)$p['unit_amount'], $p['currency']); ?></td>
<td><?php echo (int)$p['interval_count'] . ' / ' . htmlspecialchars($p['interval']); ?></td>
<td><?php echo (int)$p['trial_days'] > 0 ? ((int)$p['trial_days'] . ' gg') : '—'; ?></td>
<td>
<?php if (!empty($p['stripe_product_id'])): ?>
<span class="badge bg-secondary"><?php echo htmlspecialchars($p['stripe_product_id']); ?></span>
<?php else: ?>
<span class="text-muted"></span>
<?php endif; ?>
</td>
<td>
<span class="badge bg-info"><?php echo htmlspecialchars($p['stripe_price_id']); ?></span>
</td>
<td>
<span class="badge <?php echo ((int)$p['is_active'] === 1) ? 'bg-success' : 'bg-danger'; ?>">
<?php echo ((int)$p['is_active'] === 1) ? 'Attivo' : 'Disattivo'; ?>
</span>
</td>
<td class="text-nowrap">
<button class="btn btn-sm btn-warning"
data-bs-toggle="modal"
data-bs-target="#editPlanModal"
onclick='fillEditPlanModal(<?php echo json_encode([
"id" => (int)$p["id"],
"code" => $p["code"],
"name" => $p["name"],
"description" => $p["description"],
"stripe_product_id" => $p["stripe_product_id"],
"stripe_price_id" => $p["stripe_price_id"],
"currency" => $p["currency"],
"unit_amount" => (int)$p["unit_amount"],
"interval" => $p["interval"],
"interval_count" => (int)$p["interval_count"],
"trial_days" => (int)$p["trial_days"],
"is_active" => (int)$p["is_active"],
]); ?>)'>
<i class="bx bx-edit"></i>
</button>
<?php if ((int)$p['is_active'] === 1): ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Disattivare questo piano?');">
<input type="hidden" name="action" value="disable_plan">
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
<button class="btn btn-sm btn-danger" type="submit" title="Disattiva">
<i class="bx bx-power-off"></i>
</button>
</form>
<?php else: ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Riattivare questo piano?');">
<input type="hidden" name="action" value="enable_plan">
<input type="hidden" name="id" value="<?php echo (int)$p['id']; ?>">
<button class="btn btn-sm btn-success" type="submit" title="Riattiva">
<i class="bx bx-check-circle"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="mt-3 text-muted small">
<strong>Nota:</strong> `unit_amount` è in centesimi (es. 2990 = 29,90 EUR)
</div>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script>
$(document).ready(function() {
$('#plansTable').DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.10.25/i18n/Italian.json"
},
"pageLength": 10,
"lengthMenu": [10, 25, 50, 100],
"order": [
[1, "asc"]
]
});
});
function fillEditPlanModal(p) {
document.getElementById('edit_id').value = p.id;
document.getElementById('edit_code').value = p.code || '';
document.getElementById('edit_name').value = p.name || '';
document.getElementById('edit_description').value = p.description || '';
document.getElementById('edit_stripe_product_id').value = p.stripe_product_id || '';
document.getElementById('edit_stripe_price_id').value = p.stripe_price_id || '';
document.getElementById('edit_currency').value = p.currency || 'EUR';
document.getElementById('edit_unit_amount').value = (p.unit_amount ?? 0);
document.getElementById('edit_interval').value = p.interval || 'month';
document.getElementById('edit_interval_count').value = p.interval_count || 1;
document.getElementById('edit_trial_days').value = p.trial_days || 0;
document.getElementById('edit_is_active').checked = (parseInt(p.is_active, 10) === 1);
}
</script>
<!-- ADD MODAL -->
<div class="modal fade" id="addPlanModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nuovo Piano</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="action" value="add_plan">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Code *</label>
<input type="text" name="code" class="form-control" required>
</div>
<div class="col-md-8 mb-3">
<label class="form-label">Nome *</label>
<input type="text" name="name" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descrizione</label>
<textarea name="description" class="form-control" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Valuta</label>
<input type="text" name="currency" class="form-control" value="EUR" maxlength="3">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Unit amount (centesimi) *</label>
<input type="number" name="unit_amount" class="form-control" min="0" value="0" required>
<small class="text-muted">es: 2990 = 29,90</small>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Trial days</label>
<input type="number" name="trial_days" class="form-control" min="0" value="0">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Interval</label>
<select name="interval" class="form-control">
<option value="day">day</option>
<option value="week">week</option>
<option value="month" selected>month</option>
<option value="year">year</option>
</select>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Interval count</label>
<input type="number" name="interval_count" class="form-control" min="1" value="1">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Stato</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" name="is_active" value="1" checked>
<label class="form-check-label">Attivo</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Stripe product id</label>
<input type="text" name="stripe_product_id" class="form-control" placeholder="prod_... (optional)">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Stripe price id *</label>
<input type="text" name="stripe_price_id" class="form-control" placeholder="price_..." required>
<small class="text-muted">Obbligatorio (NOT NULL)</small>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="submit" class="btn btn-primary">Crea Piano</button>
</div>
</form>
</div>
</div>
</div>
<!-- EDIT MODAL -->
<div class="modal fade" id="editPlanModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modifica Piano</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Chiudi"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="action" value="edit_plan">
<input type="hidden" name="id" id="edit_id">
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Code *</label>
<input type="text" name="code" id="edit_code" class="form-control" required>
</div>
<div class="col-md-8 mb-3">
<label class="form-label">Nome *</label>
<input type="text" name="name" id="edit_name" class="form-control" required>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descrizione</label>
<textarea name="description" id="edit_description" class="form-control" rows="3"></textarea>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Valuta</label>
<input type="text" name="currency" id="edit_currency" class="form-control" maxlength="3">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Unit amount (centesimi) *</label>
<input type="number" name="unit_amount" id="edit_unit_amount" class="form-control" min="0" required>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Trial days</label>
<input type="number" name="trial_days" id="edit_trial_days" class="form-control" min="0">
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Interval</label>
<select name="interval" id="edit_interval" class="form-control">
<option value="day">day</option>
<option value="week">week</option>
<option value="month">month</option>
<option value="year">year</option>
</select>
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Interval count</label>
<input type="number" name="interval_count" id="edit_interval_count" class="form-control" min="1">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Stato</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active" value="1">
<label class="form-check-label">Attivo</label>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Stripe product id</label>
<input type="text" name="stripe_product_id" id="edit_stripe_product_id" class="form-control">
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Stripe price id *</label>
<input type="text" name="stripe_price_id" id="edit_stripe_price_id" class="form-control" required>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Chiudi</button>
<button type="submit" class="btn btn-primary">Salva</button>
</div>
</form>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,544 @@
<?php
// admin_subscriptions.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
// DB
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// ---- Auth check ----
if (!isset($iduserlogin)) {
die("Access denied.");
}
// ---- Admin check (Vanguard usually uses role_id=1 for admin) ----
// Adjust role_id list if needed.
$stmt = $pdo->prepare("SELECT role_id, email FROM auth_users WHERE id = ? LIMIT 1");
$stmt->execute([$iduserlogin]);
$me = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$me || !in_array((int)$me['role_id'], [1])) {
die("Access denied: admin only.");
}
// ---- Handle POST actions ----
$success_message = null;
$error = null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$action = $_POST['action'];
// Update school status (local app status)
if ($action === 'update_school_status') {
$school_id = (int)($_POST['school_id'] ?? 0);
$new_status = $_POST['status'] ?? 'active';
$allowed = ['active', 'inactive', 'suspended'];
if ($school_id <= 0 || !in_array($new_status, $allowed, true)) {
$error = "Invalid request.";
} else {
$stmt = $pdo->prepare("UPDATE schools SET status = ? WHERE id = ?");
if ($stmt->execute([$new_status, $school_id])) {
$success_message = "School status updated.";
} else {
$error = "Failed updating school status.";
}
}
}
// NOTE: Stripe actions (cancel, resume, change plan) should call Stripe API.
// Here we provide placeholders so UI is ready.
if ($action === 'flag_cancel_at_period_end') {
$sub_id = (int)($_POST['subscription_row_id'] ?? 0);
$flag = (int)($_POST['flag'] ?? 0);
$school_id = (int)($_POST['school_id'] ?? 0);
if ($sub_id <= 0 || $school_id <= 0 || !in_array($flag, [0, 1], true)) {
$error = "Invalid request.";
} else {
$stmt = $pdo->prepare("
UPDATE school_subscriptions
SET cancel_at_period_end = ?
WHERE id = ? AND school_id = ?
");
$ok = $stmt->execute([$flag, $sub_id, $school_id]);
if ($ok) {
$success_message = $flag ? "Marked cancel at period end (LOCAL)." : "Unmarked cancel at period end (LOCAL).";
} else {
$error = "Failed updating subscription flag.";
}
}
}
}
// ---- Filters (GET) ----
$q = trim($_GET['q'] ?? '');
$plan_id = (int)($_GET['plan_id'] ?? 0);
$sub_status = trim($_GET['sub_status'] ?? ''); // e.g. active, trialing, past_due, canceled, incomplete, unpaid
$school_status = trim($_GET['school_status'] ?? ''); // active, inactive, suspended
$has_sub = $_GET['has_sub'] ?? ''; // '1' or '0' or ''
$where = [];
$params = [];
// Search by school name/email/owner email
if ($q !== '') {
$where[] = "(s.name LIKE ? OR s.email LIKE ? OR ou.email LIKE ? OR CONCAT(ou.first_name,' ',ou.last_name) LIKE ?)";
$like = '%' . $q . '%';
$params[] = $like;
$params[] = $like;
$params[] = $like;
$params[] = $like;
}
if ($plan_id > 0) {
$where[] = "ss.plan_id = ?";
$params[] = $plan_id;
}
if ($sub_status !== '') {
$where[] = "ss.status = ?";
$params[] = $sub_status;
}
if ($school_status !== '') {
$where[] = "s.status = ?";
$params[] = $school_status;
}
if ($has_sub === '1') {
$where[] = "ss.id IS NOT NULL";
} elseif ($has_sub === '0') {
$where[] = "ss.id IS NULL";
}
$sqlWhere = '';
if (!empty($where)) {
$sqlWhere = "WHERE " . implode(" AND ", $where);
}
// ---- Load plans for filter dropdown ----
$stmt = $pdo->prepare("SELECT id, code, name, currency, unit_amount, `interval`, interval_count, is_active FROM billing_plans ORDER BY is_active DESC, unit_amount ASC, name ASC");
$stmt->execute();
$plans = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ---- Main query: schools + subscription + owner + plan ----
$stmt = $pdo->prepare("
SELECT
s.id AS school_id,
s.name AS school_name,
s.email AS school_email,
s.status AS school_status,
s.created_at AS school_created_at,
ou.id AS owner_id,
ou.first_name AS owner_first_name,
ou.last_name AS owner_last_name,
ou.email AS owner_email,
ss.id AS subscription_row_id,
ss.stripe_customer_id,
ss.stripe_subscription_id,
COALESCE(ss.status, 'none') AS subscription_status,
ss.current_period_start,
ss.current_period_end,
ss.trial_start,
ss.trial_end,
ss.cancel_at_period_end,
ss.updated_at AS subscription_updated_at,
bp.id AS plan_id,
bp.code AS plan_code,
bp.name AS plan_name,
bp.currency,
bp.unit_amount,
bp.`interval`,
bp.interval_count
FROM schools s
JOIN auth_users ou ON ou.id = s.owner_id
LEFT JOIN school_subscriptions ss ON ss.school_id = s.id
LEFT JOIN billing_plans bp ON bp.id = ss.plan_id
$sqlWhere
ORDER BY
-- Put schools without subscription at the bottom
CASE WHEN ss.id IS NULL THEN 1 ELSE 0 END ASC,
-- Show problematic subscriptions first
FIELD(ss.status, 'past_due','unpaid','incomplete','incomplete_expired','canceled','paused','trialing','active') ASC,
s.created_at DESC
");
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ---- Helpers ----
function h($v)
{
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}
function moneyFmt($unit_amount, $currency)
{
if ($unit_amount === null || $currency === null) return '—';
$amount = ((int)$unit_amount) / 100;
return number_format($amount, 2, ',', '.') . ' ' . strtoupper($currency);
}
function dateFmt($dt)
{
if (!$dt) return '—';
// Accept both timestamp and datetime strings
$ts = is_numeric($dt) ? (int)$dt : strtotime($dt);
if (!$ts) return '—';
return date('d/m/Y', $ts);
}
function badgeClassForSub($status)
{
$map = [
'active' => 'bg-success',
'trialing' => 'bg-info',
'past_due' => 'bg-warning',
'unpaid' => 'bg-danger',
'incomplete' => 'bg-warning',
'incomplete_expired' => 'bg-danger',
'canceled' => 'bg-secondary',
'paused' => 'bg-secondary',
'none' => 'bg-secondary',
];
return $map[$status] ?? 'bg-dark';
}
function badgeClassForSchool($status)
{
$map = [
'active' => 'bg-success',
'inactive' => 'bg-secondary',
'suspended' => 'bg-danger',
];
return $map[$status] ?? 'bg-dark';
}
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
<?php include('cssinclude.php'); ?>
<?php include('siteinfo.php'); ?>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10 mb-3">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<h5 class="mb-0">Admin Subscriptions</h5>
<div class="text-muted small">Schools + Stripe subscription status overview</div>
</div>
<div class="d-flex gap-2">
<a href="admin_billing_plans.php" class="btn btn-outline-primary">
<i class="bx bx-list-ul"></i> Billing Plans
</a>
</div>
</div>
</div>
</div>
<?php if ($success_message): ?>
<div class="alert alert-success"><?php echo h($success_message); ?></div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger"><?php echo h($error); ?></div>
<?php endif; ?>
<!-- Filters -->
<div class="card radius-10 mb-4">
<div class="card-body">
<form method="GET" class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label">Search</label>
<input type="text" class="form-control" name="q" value="<?php echo h($q); ?>" placeholder="School / email / owner">
</div>
<div class="col-md-3">
<label class="form-label">Plan</label>
<select class="form-control" name="plan_id">
<option value="0">All plans</option>
<?php foreach ($plans as $p): ?>
<option value="<?php echo (int)$p['id']; ?>" <?php echo ((int)$p['id'] === $plan_id) ? 'selected' : ''; ?>>
<?php
$label = $p['name'] . ' (' . $p['code'] . ') - ' . moneyFmt($p['unit_amount'], $p['currency']);
$label .= ' / ' . $p['interval_count'] . ' ' . $p['interval'];
if (!(int)$p['is_active']) $label .= ' [inactive]';
echo h($label);
?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Subscription status</label>
<select class="form-control" name="sub_status">
<option value="">All</option>
<?php foreach (['none', 'active', 'trialing', 'past_due', 'unpaid', 'incomplete', 'incomplete_expired', 'canceled', 'paused'] as $st): ?>
<option value="<?php echo h($st); ?>" <?php echo ($sub_status === $st) ? 'selected' : ''; ?>>
<?php echo h($st); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label">School status</label>
<select class="form-control" name="school_status">
<option value="">All</option>
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
<option value="<?php echo h($st); ?>" <?php echo ($school_status === $st) ? 'selected' : ''; ?>>
<?php echo h($st); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-1">
<label class="form-label">Has sub</label>
<select class="form-control" name="has_sub">
<option value="" <?php echo ($has_sub === '') ? 'selected' : ''; ?>>All</option>
<option value="1" <?php echo ($has_sub === '1') ? 'selected' : ''; ?>>Yes</option>
<option value="0" <?php echo ($has_sub === '0') ? 'selected' : ''; ?>>No</option>
</select>
</div>
<div class="col-md-12 d-flex gap-2 mt-2">
<button class="btn btn-primary" type="submit">
<i class="bx bx-filter"></i> Apply
</button>
<a class="btn btn-outline-secondary" href="admin_subscriptions.php">Reset</a>
</div>
</form>
</div>
</div>
<!-- Results -->
<div class="card radius-10">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-3">
<div>
<h6 class="mb-0">Schools</h6>
<div class="text-muted small"><?php echo (int)count($rows); ?> rows</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle mb-0">
<thead>
<tr>
<th style="min-width:260px;">School</th>
<th style="min-width:220px;">Owner</th>
<th style="min-width:190px;">School status</th>
<th style="min-width:160px;">Subscription</th>
<th style="min-width:220px;">Plan</th>
<th style="min-width:200px;">Period</th>
<th style="min-width:200px;">Trial</th>
<th style="min-width:140px;">Cancel at period end</th>
<th style="min-width:260px;">Stripe IDs</th>
<th style="min-width:160px;">Updated</th>
<th style="min-width:180px;">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $row): ?>
<?php
$hasSubscription = !empty($row['subscription_row_id']);
$subStatus = $row['subscription_status'] ?? 'none';
?>
<tr>
<td>
<div class="fw-bold"><?php echo h($row['school_name']); ?></div>
<div class="text-muted small">
ID: <?php echo (int)$row['school_id']; ?> · <?php echo h($row['school_email']); ?>
</div>
<div class="text-muted small">
Created: <?php echo h(dateFmt($row['school_created_at'])); ?>
</div>
</td>
<td>
<div class="fw-bold"><?php echo h(trim(($row['owner_first_name'] ?? '') . ' ' . ($row['owner_last_name'] ?? ''))); ?></div>
<div class="text-muted small"><?php echo h($row['owner_email']); ?></div>
</td>
<td>
<form method="POST" class="d-flex gap-2 align-items-center">
<input type="hidden" name="action" value="update_school_status">
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
<select name="status" class="form-control form-control-sm" style="min-width:140px;">
<?php foreach (['active', 'inactive', 'suspended'] as $st): ?>
<option value="<?php echo h($st); ?>" <?php echo ($row['school_status'] === $st) ? 'selected' : ''; ?>>
<?php echo h($st); ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit" class="btn btn-sm btn-outline-primary">
Save
</button>
</form>
<div class="mt-2">
<span class="badge <?php echo h(badgeClassForSchool($row['school_status'])); ?>">
<?php echo h($row['school_status']); ?>
</span>
</div>
</td>
<td>
<span class="badge <?php echo h(badgeClassForSub($subStatus)); ?>">
<?php echo h($subStatus); ?>
</span>
<?php if ($hasSubscription): ?>
<div class="text-muted small mt-2">
Qty: <?php echo (int)($row['quantity'] ?? 1); ?>
</div>
<?php endif; ?>
</td>
<td>
<?php if ($hasSubscription && !empty($row['plan_id'])): ?>
<div class="fw-bold"><?php echo h($row['plan_name']); ?></div>
<div class="text-muted small">
<?php echo h($row['plan_code']); ?> · <?php echo h(moneyFmt($row['unit_amount'], $row['currency'])); ?>
</div>
<div class="text-muted small">
Every <?php echo (int)$row['interval_count']; ?> <?php echo h($row['interval']); ?>
</div>
<?php else: ?>
<?php endif; ?>
</td>
<td>
<?php if ($hasSubscription): ?>
<div class="text-muted small">
Start: <?php echo h(dateFmt($row['current_period_start'])); ?>
</div>
<div class="text-muted small">
End: <?php echo h(dateFmt($row['current_period_end'])); ?>
</div>
<?php else: ?>
<?php endif; ?>
</td>
<td>
<?php if ($hasSubscription): ?>
<div class="text-muted small">
Start: <?php echo h(dateFmt($row['trial_start'])); ?>
</div>
<div class="text-muted small">
End: <?php echo h(dateFmt($row['trial_end'])); ?>
</div>
<?php else: ?>
<?php endif; ?>
</td>
<td>
<?php if ($hasSubscription): ?>
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
<span class="badge bg-warning">Yes</span>
<?php else: ?>
<span class="badge bg-secondary">No</span>
<?php endif; ?>
<?php else: ?>
<?php endif; ?>
</td>
<td>
<?php if ($hasSubscription): ?>
<div class="text-muted small">
cust: <?php echo h($row['stripe_customer_id'] ?: '—'); ?>
</div>
<div class="text-muted small">
sub: <?php echo h($row['stripe_subscription_id'] ?: '—'); ?>
</div>
<?php else: ?>
<?php endif; ?>
</td>
<td>
<?php echo h(dateFmt($row['subscription_updated_at'])); ?>
</td>
<td>
<?php if ($hasSubscription): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="action" value="flag_cancel_at_period_end">
<input type="hidden" name="subscription_row_id" value="<?php echo (int)$row['subscription_row_id']; ?>">
<input type="hidden" name="school_id" value="<?php echo (int)$row['school_id']; ?>">
<input type="hidden" name="flag" value="<?php echo ((int)$row['cancel_at_period_end'] === 1) ? 0 : 1; ?>">
<?php if ((int)$row['cancel_at_period_end'] === 1): ?>
<button type="submit" class="btn btn-sm btn-outline-success">
Unmark
</button>
<?php else: ?>
<button type="submit" class="btn btn-sm btn-outline-warning">
Mark
</button>
<?php endif; ?>
</form>
<?php else: ?>
<span class="text-muted"></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($rows)): ?>
<tr>
<td colspan="11" class="text-center text-muted py-4">No results</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div> <!-- /page-content -->
</div> <!-- /page-wrapper -->
<?php if (file_exists('include/footer.php')) include('include/footer.php'); ?>
</div> <!-- /wrapper -->
<?php if (file_exists('jsinclude.php')) include('jsinclude.php'); ?>
</body>
</html>

View File

@ -145,11 +145,12 @@ if (!empty($_SESSION['school_id'])) {
</a>
</li>
<?php endif; ?>
<li class="menu-label">Insegnanti</li>
<?php
//menù teacher
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
<li class="menu-label">Insegnanti</li>
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin'))) : ?>
<li>
<a href="teacher_list.php">
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
@ -159,12 +160,38 @@ if (!empty($_SESSION['school_id'])) {
<?php endif; ?>
<?php
if ((Auth::user()->hasRole('school_owner')) || (Auth::user()->hasRole('Admin')) || (Auth::user()->hasRole('teacher'))) : ?>
<li>
<a href="teacher_page.php">
<div class="parent-icon"><i class="bx bx-chalkboard"></i></div>
<div class="menu-title">Il mio profilo</div>
</a>
</li>
<?php endif; ?>
<?php
//menù admin only
if ((Auth::user()->hasRole('Admin'))) : ?>
<li class="menu-label">Subscription Area</li>
<li>
<a href="admin_subscriptions.php" target="">
<div class="parent-icon"><i class="bx bx-layout"></i></div>
<div class="menu-title">Subscription</div>
</a>
</li>
<li>
<a href="admin_subscription_plans.php" target="">
<div class="parent-icon"><i class="bx bx-layout"></i></div>
<div class="menu-title">Subscription Plan</div>
</a>
</li>
<li class="menu-label">Others</li>
<li>
<a href="emplate/index.html" target="_blank">
<a href="template/index.html" target="_blank">
<div class="parent-icon"><i class="bx bx-layout"></i></div>
<div class="menu-title">Template</div>
</a>

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -35,9 +35,22 @@ $stmt->execute();
$categories = $stmt->fetchAll();
// Recupera tutti gli insegnanti della scuola
$stmt = $pdo->prepare("SELECT id, first_name, last_name FROM teachers WHERE user_id = ? AND status = 'active' ORDER BY first_name, last_name");
$stmt->execute([$iduserlogin]);
$teachers = $stmt->fetchAll();
// 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)

View File

@ -120,6 +120,7 @@ $teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
<title>Insegnanti - <?= htmlspecialchars($school_name) ?></title>
<?php include('cssinclude.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
<style>
.quill-wrapper {
min-height: 260px;
@ -238,7 +239,9 @@ $teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
<div class="col-12">
<label>Descrizione</label>
<div id="quill-add-editor" style="min-height:260px;"></div>
<div class="quill-wrapper">
<div id="quill-add-editor"></div>
</div>
<input type="hidden" name="description" id="add-desc-hidden">
</div>

View File

@ -0,0 +1,486 @@
<?php
// teacher_page.php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
// QR Code library
require_once __DIR__ . '/../../vendor/autoload.php';
use Endroid\QrCode\QrCode;
use Endroid\QrCode\Writer\PngWriter;
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
if (!isset($iduserlogin)) {
die("Errore: ID utente non definito.");
}
/**
* QR helper compatible with older/newer Endroid versions (best-effort)
*/
function writeQrPng($text, $filename, $size = 150, $margin = 10)
{
// Your installed version seems to require text in constructor
$qrCode = new \Endroid\QrCode\QrCode($text);
if (method_exists($qrCode, 'setSize')) {
$qrCode->setSize($size);
} elseif (method_exists($qrCode, 'setModuleSize')) {
$module = max(3, (int)round($size / 25));
$qrCode->setModuleSize($module);
}
if (method_exists($qrCode, 'setMargin')) {
$qrCode->setMargin($margin);
} elseif (method_exists($qrCode, 'setPadding')) {
$qrCode->setPadding($margin);
}
$writer = new \Endroid\QrCode\Writer\PngWriter();
if (method_exists($writer, 'writeFile')) {
$writer->writeFile($qrCode, $filename);
} else {
$result = $writer->write($qrCode);
if (is_object($result) && method_exists($result, 'saveToFile')) {
$result->saveToFile($filename);
} else {
file_put_contents($filename, (string)$result);
}
}
}
function generateUniqueCode($pdo, $length = 16)
{
do {
$code = bin2hex(random_bytes($length / 2));
$stmt = $pdo->prepare("SELECT COUNT(*) FROM teachers WHERE unique_code = ?");
$stmt->execute([$code]);
} while ($stmt->fetchColumn() > 0);
return $code;
}
/**
* Detect view mode
* - Owner view: teacher_page.php?id=TEACHER_ID (teachers.id)
* - Teacher self view: teacher_page.php
*/
$teacher_id = (int)($_GET['id'] ?? 0);
$is_owner_view = ($teacher_id > 0);
$success_message = $error = null;
/**
* 1) LOAD teacher row
*/
if ($is_owner_view) {
// OWNER VIEW: load teacher by teachers.id only if owner has rights
$stmt = $pdo->prepare("
SELECT
t.*,
u.first_name, u.last_name, u.email
FROM teachers t
JOIN auth_users u ON t.user_id = u.id
JOIN teacher_schools ts ON ts.teacher_id = t.id
JOIN schools s ON s.id = ts.school_id
WHERE t.id = ?
AND s.owner_id = ?
LIMIT 1
");
$stmt->execute([$teacher_id, $iduserlogin]);
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$teacher) {
die("Errore: insegnante non trovata o non hai permessi.");
}
} else {
// TEACHER SELF VIEW: load by logged user
$stmt = $pdo->prepare("
SELECT
t.*,
u.first_name, u.last_name, u.email
FROM auth_users u
LEFT JOIN teachers t ON t.user_id = u.id
WHERE u.id = ?
LIMIT 1
");
$stmt->execute([$iduserlogin]);
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
// If not exists in teachers, CREATE IT before showing the form (auto-create)
if (empty($teacher['id'])) {
// ✅ SOLO QUI: quando auto-crei il profilo teacher (SELF VIEW), aggiungi created_by = iduserlogin
$unique_code = generateUniqueCode($pdo);
$stmtIns = $pdo->prepare("
INSERT INTO teachers (user_id, unique_code, phone, description, specializations, profile_picture, status, created_by)
VALUES (?, ?, NULL, '', '', '', 'active', ?)
");
$ok = $stmtIns->execute([$iduserlogin, $unique_code, $iduserlogin]);
if (!$ok) {
die("Errore: impossibile creare il profilo insegnante.");
}
// Reload teacher after insert
$stmt = $pdo->prepare("
SELECT
t.*,
u.first_name, u.last_name, u.email
FROM teachers t
JOIN auth_users u ON t.user_id = u.id
WHERE t.user_id = ?
LIMIT 1
");
$stmt->execute([$iduserlogin]);
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
}
}
/**
* Now teacher MUST exist (in owner view and in self view due to auto-create)
*/
$is_new = empty($teacher['id']); // should be false at this point
/**
* 2) HANDLE POST (save)
*/
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// target user is the teacher being edited
$target_user_id = $is_owner_view ? (int)$teacher['user_id'] : (int)$iduserlogin;
$first_name = trim($_POST['first_name'] ?? '');
$last_name = trim($_POST['last_name'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$description = trim($_POST['description'] ?? '');
$specializations = trim($_POST['specializations'] ?? '');
$status = (($_POST['status'] ?? 'active') === 'active') ? 'active' : 'inactive';
// Update auth_users names for the target teacher
$stmt = $pdo->prepare("UPDATE auth_users SET first_name = ?, last_name = ? WHERE id = ?");
$stmt->execute([$first_name, $last_name, $target_user_id]);
// Photo upload (use target user id in filename)
$profile_picture = $teacher['profile_picture'] ?? '';
if (!empty($_FILES['profile_picture']['name']) && $_FILES['profile_picture']['error'] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($_FILES['profile_picture']['name'], PATHINFO_EXTENSION));
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
$new_name = "phototeachers/{$target_user_id}-" . time() . "-profile.$ext";
if (move_uploaded_file($_FILES['profile_picture']['tmp_name'], $new_name)) {
if ($profile_picture && file_exists($profile_picture)) {
@unlink($profile_picture);
}
$profile_picture = $new_name;
} else {
$error = "Errore caricamento foto.";
}
} else {
$error = "Solo JPG, PNG, GIF ammessi.";
}
}
if (!$error) {
// Update teachers row (always exists at this point)
$stmt = $pdo->prepare("
UPDATE teachers
SET phone = ?, description = ?, specializations = ?, profile_picture = ?, status = ?
WHERE user_id = ?
");
$success = $stmt->execute([
$phone ?: null,
$description,
$specializations,
$profile_picture,
$status,
$target_user_id
]);
if ($success) {
$success_message = "Dati aggiornati!";
// Reload teacher (with correct target user)
$stmt = $pdo->prepare("
SELECT t.*, u.first_name, u.last_name, u.email
FROM teachers t
JOIN auth_users u ON t.user_id = u.id
WHERE t.user_id = ?
LIMIT 1
");
$stmt->execute([$target_user_id]);
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
} else {
$error = "Errore aggiornamento.";
}
}
}
/**
* 3) QR generation (after teacher is loaded)
*/
$qr_code_path = null;
if (!empty($teacher['unique_code'])) {
try {
$unique_code = $teacher['unique_code'];
// IMPORTANT: file name uses target user_id (teacher user), not owner id
$qr_user_id = (int)$teacher['user_id'];
$base_dir = __DIR__ . '/../../public/phototeachers/qrcodes/';
$qr_filename = "{$base_dir}{$qr_user_id}-{$unique_code}.png";
$qr_code_path = "phototeachers/qrcodes/{$qr_user_id}-{$unique_code}.png";
if (!file_exists($qr_filename)) {
if (!is_dir($base_dir)) mkdir($base_dir, 0755, true);
writeQrPng($unique_code, $qr_filename, 150, 10);
}
} catch (Exception $e) {
error_log("Errore QR: " . $e->getMessage());
}
}
?>
<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Profilo Insegnante</title>
<?php include('cssinclude.php'); ?>
<?php include('siteinfo.php'); ?>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
<style>
.teacher-photo {
max-width: 100%;
height: auto;
max-height: 260px;
object-fit: contain;
border: 1px solid #dee2e6;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
background: #fff;
padding: 10px;
margin-bottom: 1.5rem;
}
.quill-wrapper {
min-height: 300px;
display: flex;
flex-direction: column;
}
.ql-container {
flex: 1;
font-size: 15px;
border: 1px solid #ced4da;
border-radius: 0 0 0.375rem 0.375rem;
}
.ql-editor {
min-height: 220px;
}
.ql-toolbar {
border-radius: 0.375rem 0.375rem 0 0;
border-color: #ced4da;
}
.form-section {
margin-bottom: 2rem;
}
.form-label {
font-weight: 500;
margin-bottom: 0.5rem;
}
</style>
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<div class="card radius-10">
<div class="card-header">
<h6 class="mb-0">Profilo Insegnante</h6>
</div>
<div class="card-body">
<?php if (!empty($success_message)): ?>
<div class="alert alert-success alert-dismissible fade show">
<?php echo htmlspecialchars($success_message); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger alert-dismissible fade show">
<?php echo htmlspecialchars($error); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" enctype="multipart/form-data" id="teacherForm">
<div class="row g-4">
<div class="col-lg-4 text-center">
<img src="<?php echo $teacher['profile_picture'] ? htmlspecialchars($teacher['profile_picture']) : 'phototeachers/ndphoto.png'; ?>"
alt="Foto Profilo" class="teacher-photo">
<div class="mb-4">
<label for="profile_picture" class="form-label">Carica nuova foto</label>
<input type="file" class="form-control" id="profile_picture" name="profile_picture" accept="image/jpeg,image/png,image/gif">
<small class="text-muted d-block mt-1">Max 2MB JPG, PNG, GIF</small>
</div>
<?php if (!empty($teacher['unique_code']) && $qr_code_path): ?>
<div class="mb-3">
<label class="form-label">Codice Univoco</label>
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['unique_code']); ?>" readonly>
</div>
<div class="mb-3">
<label class="form-label">QR Code</label><br>
<img src="<?php echo htmlspecialchars($qr_code_path); ?>" alt="QR Code" class="img-fluid shadow-sm" style="max-width: 180px;">
</div>
<?php endif; ?>
</div>
<div class="col-lg-8">
<div class="row g-3">
<div class="col-md-6">
<label for="first_name" class="form-label">Nome</label>
<input type="text" class="form-control" id="first_name" name="first_name"
value="<?php echo htmlspecialchars($teacher['first_name'] ?? ''); ?>" required>
</div>
<div class="col-md-6">
<label for="last_name" class="form-label">Cognome</label>
<input type="text" class="form-control" id="last_name" name="last_name"
value="<?php echo htmlspecialchars($teacher['last_name'] ?? ''); ?>" required>
</div>
<div class="col-12">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email"
value="<?php echo htmlspecialchars($teacher['email'] ?? ''); ?>" readonly>
</div>
<div class="col-md-6">
<label for="phone" class="form-label">Telefono</label>
<input type="tel" class="form-control" id="phone" name="phone"
value="<?php echo htmlspecialchars($teacher['phone'] ?? ''); ?>">
</div>
<div class="col-12 form-section">
<label class="form-label">Descrizione insegnante</label>
<div class="quill-wrapper">
<div id="quill-editor"></div>
</div>
<input type="hidden" name="description" id="description-hidden">
</div>
<div class="col-12 form-section">
<label for="specializations" class="form-label">Specializzazioni</label>
<textarea class="form-control" id="specializations" name="specializations" rows="3"><?php echo htmlspecialchars($teacher['specializations'] ?? ''); ?></textarea>
<small class="text-muted">Es: Hatha Yoga, Vinyasa, Yin, Restorative...</small>
</div>
<div class="col-md-6">
<label class="form-label">Stato</label>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" id="status" name="status" value="active"
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'checked' : ''; ?>>
<label class="form-check-label" for="status">
<?php echo ($teacher['status'] ?? 'active') === 'active' ? 'Attivo' : 'Inattivo'; ?>
</label>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Creato</label>
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['created_at'] ?? ''); ?>" readonly>
</div>
<div class="col-md-6">
<label class="form-label">Aggiornato</label>
<input type="text" class="form-control" value="<?php echo htmlspecialchars($teacher['updated_at'] ?? ''); ?>" readonly>
</div>
<div class="col-12 mt-5">
<button type="submit" class="btn btn-primary btn-lg px-5">
Salva Modifiche
</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="overlay toggle-icon"></div>
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
<script>
const quill = new Quill('#quill-editor', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{
'header': [1, 2, 3, false]
}],
[{
'color': ['#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', '#808080', '#c0c0c0']
}, {
'background': []
}],
[{
'list': 'ordered'
}, {
'list': 'bullet'
}],
[{
'align': []
}],
['link', 'clean']
]
}
});
quill.root.innerHTML = `<?php echo addslashes($teacher['description'] ?? ''); ?>`;
document.getElementById('teacherForm').addEventListener('submit', function() {
document.getElementById('description-hidden').value = quill.root.innerHTML;
});
</script>
</body>
</html>