464 lines
19 KiB
PHP
464 lines
19 KiB
PHP
<?php
|
|
require_once('include/headscript.php');
|
|
require_once('include/csrf.php');
|
|
SchoolContext::requireSchoolAdmin();
|
|
|
|
/**
|
|
* Catalogo prodotti - elenco della scuola corrente.
|
|
* Azioni: Modifica (product_form.php), Attiva/Disattiva ed Elimina (POST + CSRF a product_action.php).
|
|
*/
|
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
|
|
|
function e($value): string
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function euro($v): string
|
|
{
|
|
return number_format((float) $v, 2, ',', '.') . ' €';
|
|
}
|
|
|
|
$message = $_GET['message'] ?? '';
|
|
|
|
$st = $pdo->prepare(
|
|
"SELECT p.*,
|
|
(SELECT COUNT(*) FROM product_variants v WHERE v.idproduct = p.idproduct AND v.active = 'Y') AS nvariants,
|
|
(SELECT MIN(v.price) FROM product_variants v WHERE v.idproduct = p.idproduct AND v.active = 'Y') AS price_from,
|
|
(SELECT COUNT(*) FROM product_services ps WHERE ps.idproduct = p.idproduct) AS nclasses,
|
|
(SELECT COUNT(*) FROM orderbook o WHERE o.idproduct = p.idproduct AND o.idschool = p.idschool) AS nsold
|
|
FROM products p
|
|
WHERE p.idschool = ?
|
|
ORDER BY FIELD(p.kind, 'lessons', 'membership', 'voucher'), p.sort_order, p.title"
|
|
);
|
|
$st->execute([$idschool]);
|
|
$products = $st->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$kindLabels = [
|
|
'lessons' => ['Lezioni', 'bx-calendar-check'],
|
|
'membership' => ['Tessere', 'bx-id-card'],
|
|
'voucher' => ['Buoni regalo', 'bx-gift'],
|
|
];
|
|
|
|
function rulesSummary(array $p): string
|
|
{
|
|
if ($p['kind'] === 'membership') {
|
|
return 'Tessera / quota associativa';
|
|
}
|
|
if ($p['kind'] === 'voucher') {
|
|
return 'Regala un pacchetto';
|
|
}
|
|
switch ($p['booking_mode']) {
|
|
case 'fixed':
|
|
$s = (int) $p['fixed_slots'] === 2 ? 'Classi fisse · 2 a settimana' : 'Classe fissa · 1 a settimana';
|
|
break;
|
|
case 'single':
|
|
$s = 'Lezione scelta all\'acquisto';
|
|
break;
|
|
default:
|
|
$s = 'Libera';
|
|
if (!empty($p['weekly_limit'])) {
|
|
$s .= ' · max ' . (int) $p['weekly_limit'] . '/sett.';
|
|
}
|
|
}
|
|
if ($p['once_per_student'] === 'Y') {
|
|
$s .= ' · una volta per allieva';
|
|
}
|
|
return $s;
|
|
}
|
|
?>
|
|
<!doctype html>
|
|
<html lang="it">
|
|
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>YogiBook - Prodotti</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta content="YogiBook" name="description" />
|
|
<link rel="shortcut icon" href="assets/images/favicon.ico">
|
|
<link href="assets/css/bootstrap.min.css" id="bootstrap-style" rel="stylesheet" type="text/css" />
|
|
<link href="assets/css/icons.min.css" rel="stylesheet" type="text/css" />
|
|
<link href="assets/css/app.min.css" id="app-style" rel="stylesheet" type="text/css" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
<style>
|
|
.admin-card {
|
|
border: none;
|
|
border-radius: 14px;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, .06);
|
|
}
|
|
|
|
.admin-card .card-body {
|
|
padding: 26px 30px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
letter-spacing: .04em;
|
|
text-transform: uppercase;
|
|
color: #6b7280;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.section-lead {
|
|
color: #9ca3af;
|
|
font-size: 14px;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.list-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.btn-new {
|
|
background: #2F7F84;
|
|
border: none;
|
|
color: #fff;
|
|
border-radius: 8px;
|
|
padding: 9px 18px;
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn-new:hover {
|
|
background: #1E4A4F;
|
|
color: #fff;
|
|
}
|
|
|
|
.prod-table thead th {
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: .03em;
|
|
color: #6b7280;
|
|
border-bottom: 2px solid #eef0f3;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.prod-table td {
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.kind-group td {
|
|
background: #F4FAFA;
|
|
font-weight: 700;
|
|
color: #1E4A4F;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.kind-group i {
|
|
color: #2F7F84;
|
|
margin-right: 6px;
|
|
}
|
|
|
|
.prod-name {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.prod-thumb {
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 10px;
|
|
object-fit: cover;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.prod-thumb.ph {
|
|
background: #DBF6F9;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #2F7F84;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.prod-title {
|
|
font-weight: 600;
|
|
color: #1f2937;
|
|
}
|
|
|
|
.prod-sub {
|
|
color: #9ca3af;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.badge-tag {
|
|
display: inline-block;
|
|
background: #F8EFE4;
|
|
color: #8a6a3f;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
padding: 1px 8px;
|
|
border-radius: 10px;
|
|
margin-left: 6px;
|
|
}
|
|
|
|
.pill {
|
|
display: inline-block;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
padding: 3px 10px;
|
|
border-radius: 20px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.pill.on {
|
|
background: #e7f7ee;
|
|
color: #1a7f52;
|
|
}
|
|
|
|
.pill.off {
|
|
background: #eef0f3;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.pill.web {
|
|
background: #DBF6F9;
|
|
color: #1E4A4F;
|
|
}
|
|
|
|
.btn-act {
|
|
border: 1px solid #e2e4e9;
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 6px 12px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: #374151;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
}
|
|
|
|
.btn-act:hover {
|
|
background: #f3f4f6;
|
|
color: #111827;
|
|
}
|
|
|
|
.btn-act.danger {
|
|
color: #c0392b;
|
|
border-color: #f2c9c4;
|
|
}
|
|
|
|
.btn-act.danger:hover {
|
|
background: #fdecea;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 44px 10px;
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.empty-state i {
|
|
font-size: 34px;
|
|
margin-bottom: 10px;
|
|
display: block;
|
|
color: #cfe3e4;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="layout-wrapper">
|
|
<header id="page-topbar" class="isvertical-topbar">
|
|
<div class="navbar-header">
|
|
<div class="d-flex">
|
|
<?php include('include/logoarea.php'); ?>
|
|
<button type="button" class="btn btn-sm px-3 font-size-24 header-item waves-effect vertical-menu-btn">
|
|
<i class="bx bx-menu align-middle"></i>
|
|
</button>
|
|
<div class="page-title-box align-self-center d-none d-md-block">
|
|
<h4 class="page-title mb-0">Prodotti</h4>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex">
|
|
<?php include('include/schoolswitcher.php'); ?><?php include('include/languageselection.php'); ?>
|
|
<?php include('include/profiletopbar.php'); ?>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<?php include('include/sidebar.php'); ?>
|
|
<header class="ishorizontal-topbar">
|
|
<div class="navbar-header">
|
|
<div class="d-flex"></div>
|
|
</div>
|
|
<div class="topnav">
|
|
<div class="container-fluid">
|
|
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="main-content">
|
|
<div class="page-content">
|
|
<div class="container-fluid">
|
|
|
|
<?php if ($message === 'saved') : ?>
|
|
<div class="alert alert-success">Prodotto salvato.</div>
|
|
<?php elseif ($message === 'saved_kept') : ?>
|
|
<div class="alert alert-info">Prodotto salvato. Le opzioni già vendute non sono state eliminate ma disattivate, per non perdere lo storico.</div>
|
|
<?php elseif ($message === 'toggled') : ?>
|
|
<div class="alert alert-success">Stato del prodotto aggiornato.</div>
|
|
<?php elseif ($message === 'deleted') : ?>
|
|
<div class="alert alert-warning">Prodotto eliminato.</div>
|
|
<?php elseif ($message === 'inuse') : ?>
|
|
<div class="alert alert-warning">Questo prodotto è già stato venduto o è usato in un buono regalo: non si può eliminare. Puoi disattivarlo.</div>
|
|
<?php elseif ($message === 'error') : ?>
|
|
<div class="alert alert-danger">Si è verificato un errore. Riprova.</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="card admin-card">
|
|
<div class="card-body">
|
|
<div class="list-head">
|
|
<div>
|
|
<div class="section-title">Prodotti</div>
|
|
<p class="section-lead">Quello che la scuola vende: pacchetti di lezioni, carnet, flexi, lezioni singole e di prova, tessere e buoni regalo.</p>
|
|
</div>
|
|
<a href="product_form.php" class="btn btn-new"><i class="bx bx-plus align-middle"></i> Nuovo prodotto</a>
|
|
</div>
|
|
|
|
<div class="table-responsive mt-3">
|
|
<table class="table prod-table align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Prodotto</th>
|
|
<th>Regole</th>
|
|
<th>Opzioni</th>
|
|
<th>Classi</th>
|
|
<th>Venduti</th>
|
|
<th>Stato</th>
|
|
<th class="text-end">Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($products)) : ?>
|
|
<tr>
|
|
<td colspan="7">
|
|
<div class="empty-state">
|
|
<i class="bx bx-purchase-tag-alt"></i>
|
|
Nessun prodotto ancora. Crea il primo con "Nuovo prodotto".
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php else :
|
|
$currentKind = null;
|
|
foreach ($products as $p) :
|
|
if ($p['kind'] !== $currentKind) :
|
|
$currentKind = $p['kind'];
|
|
[$kLabel, $kIcon] = $kindLabels[$currentKind] ?? [$currentKind, 'bx-box']; ?>
|
|
<tr class="kind-group">
|
|
<td colspan="7"><i class="bx <?php echo e($kIcon); ?>"></i><?php echo e($kLabel); ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
<tr>
|
|
<td>
|
|
<div class="prod-name">
|
|
<?php if (!empty($p['cover_image'])) : ?>
|
|
<img class="prod-thumb" src="<?php echo e($p['cover_image']); ?>" alt="">
|
|
<?php else : ?>
|
|
<span class="prod-thumb ph"><i class="bx bx-image"></i></span>
|
|
<?php endif; ?>
|
|
<div>
|
|
<div class="prod-title">
|
|
<?php echo e($p['title']); ?>
|
|
<?php if (!empty($p['badge'])) : ?><span class="badge-tag"><?php echo e($p['badge']); ?></span><?php endif; ?>
|
|
</div>
|
|
<?php if (!empty($p['subtitle'])) : ?><div class="prod-sub"><?php echo e($p['subtitle']); ?></div><?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td><?php echo e(rulesSummary($p)); ?></td>
|
|
<td>
|
|
<?php echo (int) $p['nvariants']; ?>
|
|
<?php if ($p['price_from'] !== null) : ?>
|
|
<span class="prod-sub">· da <?php echo e(euro($p['price_from'])); ?></span>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td><?php echo $p['kind'] === 'lessons' ? (int) $p['nclasses'] : '—'; ?></td>
|
|
<td><?php echo (int) $p['nsold']; ?></td>
|
|
<td>
|
|
<span class="pill <?php echo $p['active'] === 'Y' ? 'on' : 'off'; ?>"><?php echo $p['active'] === 'Y' ? 'Attivo' : 'Disattivo'; ?></span>
|
|
<?php if ($p['visible_online'] === 'Y') : ?><span class="pill web" title="Visibile nell'acquisto online">Online</span><?php endif; ?>
|
|
</td>
|
|
<td class="text-end">
|
|
<a href="product_form.php?id=<?php echo (int) $p['idproduct']; ?>" class="btn-act"><i class="bx bx-edit"></i> Modifica</a>
|
|
<button type="button" class="btn-act btn-toggle" data-id="<?php echo (int) $p['idproduct']; ?>">
|
|
<i class="bx <?php echo $p['active'] === 'Y' ? 'bx-hide' : 'bx-show'; ?>"></i>
|
|
<?php echo $p['active'] === 'Y' ? 'Disattiva' : 'Attiva'; ?>
|
|
</button>
|
|
<?php if ((int) $p['nsold'] === 0) : ?>
|
|
<button type="button" class="btn-act danger btn-delete" data-id="<?php echo (int) $p['idproduct']; ?>" data-title="<?php echo e($p['title']); ?>"><i class="bx bx-trash"></i> Elimina</button>
|
|
<?php endif; ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form id="action-form" action="product_action.php" method="POST" style="display:none;">
|
|
<?php echo yb_csrf_field(); ?>
|
|
<input type="hidden" name="id" id="action-id">
|
|
<input type="hidden" name="action" id="action-name">
|
|
</form>
|
|
|
|
</div>
|
|
<?php include('include/footer.php'); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="assets/libs/bootstrap/js/bootstrap.bundle.min.js"></script>
|
|
<script src="assets/libs/metismenujs/metismenujs.min.js"></script>
|
|
<script src="assets/libs/simplebar/simplebar.min.js"></script>
|
|
<script src="assets/libs/eva-icons/eva.min.js"></script>
|
|
<script src="assets/js/app.js"></script>
|
|
|
|
<script>
|
|
function sendAction(id, action) {
|
|
document.getElementById('action-id').value = id;
|
|
document.getElementById('action-name').value = action;
|
|
document.getElementById('action-form').submit();
|
|
}
|
|
|
|
document.querySelectorAll('.btn-toggle').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
sendAction(btn.getAttribute('data-id'), 'toggle');
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll('.btn-delete').forEach(function(btn) {
|
|
btn.addEventListener('click', function() {
|
|
var title = document.createElement('div');
|
|
title.textContent = btn.getAttribute('data-title');
|
|
Swal.fire({
|
|
title: 'Eliminare il prodotto?',
|
|
html: 'Stai per eliminare <strong>' + title.innerHTML + '</strong> con tutte le sue opzioni.',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Elimina',
|
|
cancelButtonText: 'Chiudi',
|
|
confirmButtonColor: '#e74c3c',
|
|
reverseButtons: true
|
|
}).then(function(r) {
|
|
if (r.isConfirmed) sendAction(btn.getAttribute('data-id'), 'delete');
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|