added modify to orders
This commit is contained in:
+445
-346
@@ -1,314 +1,143 @@
|
|||||||
<?php require_once('include/headscript.php'); ?>
|
<?php require_once('include/headscript.php'); ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Creazione della connessione
|
/**
|
||||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
* Riepilogo ordini (amministratore)
|
||||||
|
* - filtro Attivi / Scaduti (server)
|
||||||
|
* - ricerca istantanea per nome, cognome, email, n. ordine, classe (client)
|
||||||
|
* - modifica al click di: scadenza, max riprogrammazioni, riprogrammazioni effettuate
|
||||||
|
*/
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
// Verifica della connessione
|
if (!function_exists('e')) {
|
||||||
if ($conn->connect_error) {
|
function e($value): string
|
||||||
die("Connessione fallita: " . $conn->connect_error);
|
{
|
||||||
}
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
// Gestione del messaggio GET
|
|
||||||
if (isset($_GET['message'])) {
|
|
||||||
$message = $_GET['message'];
|
|
||||||
} else {
|
|
||||||
$message = 'n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID dell'insegnante (per il form DayOff)
|
|
||||||
$idTeacher = 1;
|
|
||||||
|
|
||||||
// Gestione dell'inserimento del DayOff
|
|
||||||
if (isset($_POST['submit'])) {
|
|
||||||
$dayoffdate = $_POST['dayoff'];
|
|
||||||
$insertQuery = "INSERT INTO dayoff (idteacher, dayoffdate) VALUES (?, ?)";
|
|
||||||
$stmt = $conn->prepare($insertQuery);
|
|
||||||
$stmt->bind_param("is", $idTeacher, $dayoffdate);
|
|
||||||
if ($stmt->execute()) {
|
|
||||||
$message = 'success_dayoff';
|
|
||||||
} else {
|
|
||||||
$message = 'error_dayoff';
|
|
||||||
}
|
|
||||||
$stmt->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gestione del filtro per gli ordini
|
|
||||||
$filter = isset($_GET['filter']) ? $_GET['filter'] : 'all';
|
|
||||||
$today = date("Y-m-d");
|
|
||||||
$whereClause = "";
|
|
||||||
if ($filter == 'active') {
|
|
||||||
$whereClause = "WHERE expireon > '$today'";
|
|
||||||
} elseif ($filter == 'expired') {
|
|
||||||
$whereClause = "WHERE expireon <= '$today'";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query SQL per recuperare tutti gli ordini con join su service
|
|
||||||
$query = "SELECT o.idorderbook, o.first_name, o.last_name, s.servicename, o.expireon, o.status, o.nticket
|
|
||||||
FROM orderbook o
|
|
||||||
LEFT JOIN service s ON o.idservice = s.idservice
|
|
||||||
$whereClause
|
|
||||||
ORDER BY o.idorderbook DESC";
|
|
||||||
$result = $conn->query($query);
|
|
||||||
$orders = [];
|
|
||||||
if ($result->num_rows > 0) {
|
|
||||||
while ($row = $result->fetch_assoc()) {
|
|
||||||
$orders[] = $row;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query SQL per i DayOff
|
function reschedBadge($max, $done): string
|
||||||
$query_dayoff = "SELECT iddayoff, dayoffdate FROM dayoff WHERE idteacher = '1'";
|
{
|
||||||
$result_dayoff = $conn->query($query_dayoff);
|
$done = (int) $done;
|
||||||
$documents = [];
|
if ($max === null || $max === '') {
|
||||||
if ($result_dayoff->num_rows > 0) {
|
return '<span class="badge bg-light text-dark">' . $done . '</span>';
|
||||||
while ($row = $result_dayoff->fetch_assoc()) {
|
|
||||||
$documents[] = $row;
|
|
||||||
}
|
}
|
||||||
|
$max = (int) $max;
|
||||||
|
$cls = $done >= $max ? 'bg-danger' : ($done > 0 ? 'bg-warning text-dark' : 'bg-success');
|
||||||
|
return '<span class="badge ' . $cls . '">' . $done . ' / ' . $max . '</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$message = $_GET['message'] ?? 'n';
|
||||||
|
|
||||||
|
$filter = $_GET['filter'] ?? 'all';
|
||||||
|
if (!in_array($filter, ['all', 'active', 'expired'], true)) {
|
||||||
|
$filter = 'all';
|
||||||
|
}
|
||||||
|
$q = trim((string) ($_GET['q'] ?? ''));
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
|
||||||
|
$where = '';
|
||||||
|
$params = [];
|
||||||
|
if ($filter === 'active') {
|
||||||
|
$where = 'WHERE o.expireon > :today';
|
||||||
|
$params[':today'] = $today;
|
||||||
|
} elseif ($filter === 'expired') {
|
||||||
|
$where = 'WHERE o.expireon <= :today';
|
||||||
|
$params[':today'] = $today;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT o.idorderbook, o.order_id, o.first_name, o.last_name, o.order_billing_email,
|
||||||
|
s.servicename, o.expireon, o.status, o.nticket, o.maxreschedule, o.reprogrammed
|
||||||
|
FROM orderbook o
|
||||||
|
LEFT JOIN service s ON o.idservice = s.idservice
|
||||||
|
$where
|
||||||
|
ORDER BY o.idorderbook DESC";
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute($params);
|
||||||
|
$orders = $stmt->fetchAll();
|
||||||
|
|
||||||
|
$qParam = $q !== '' ? '&q=' . urlencode($q) : '';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="it">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>YogiBook - Prenotazioni YogaSoul</title>
|
<title>YogiBook - Riepilogo Ordini</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta content="YogiBook - Prenotazione facile YogaSOul" name="description" />
|
<meta content="YogiBook - Prenotazione facile YogaSoul" name="description" />
|
||||||
<meta content="Advanced Creative Solutions" name="author" />
|
<meta content="Advanced Creative Solutions" name="author" />
|
||||||
<link rel="shortcut icon" href="assets/images/favicon.ico">
|
<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/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/icons.min.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="assets/css/app.min.css" id="app-style" 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">
|
<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@10"></script>
|
|
||||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@10"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||||
<script>
|
|
||||||
$(function() {
|
|
||||||
// Inizializzazione del Datepicker per i campi DayOff
|
|
||||||
$("#dayoff").datepicker({
|
|
||||||
dateFormat: "yy-mm-dd",
|
|
||||||
minDate: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inizializzazione del Datepicker per i campi di scadenza editabili
|
|
||||||
$(document).on('focus', '.expiryDateInput', function() {
|
|
||||||
$(this).datepicker({
|
|
||||||
dateFormat: "yy-mm-dd",
|
|
||||||
minDate: 0,
|
|
||||||
onSelect: function(dateText) {
|
|
||||||
let orderId = $(this).data('order-id');
|
|
||||||
console.log("Datepicker selezionato - idorderbook: " + orderId + ", newExpiry: " + dateText);
|
|
||||||
confirmUpdate(orderId, dateText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rendere il campo scadenza editabile al click
|
|
||||||
$(document).on('click', '.expiry-date', function() {
|
|
||||||
let orderId = $(this).data('order-id');
|
|
||||||
let currentDate = $(this).text().trim();
|
|
||||||
console.log("Clic su expiry-date - idorderbook: " + orderId + ", currentDate: " + currentDate);
|
|
||||||
if (!orderId) {
|
|
||||||
Swal.fire('Errore!', 'ID ordine non valido.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$(this).html(`<input type="text" class="form-control expiryDateInput" data-order-id="${orderId}" value="${currentDate}" />`);
|
|
||||||
$(this).find('.expiryDateInput').focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Funzione per confermare l'aggiornamento della scadenza
|
|
||||||
function confirmUpdate(orderId, newExpiry) {
|
|
||||||
let originalCell = $(`td.expiry-date[data-order-id="${orderId}"]`);
|
|
||||||
let originalDate = originalCell.text().trim();
|
|
||||||
console.log("Invio AJAX - idorderbook: " + orderId + ", new_expiry: " + newExpiry);
|
|
||||||
|
|
||||||
Swal.fire({
|
|
||||||
title: "Sei sicuro?",
|
|
||||||
text: "La data di scadenza verrà modificata!",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
cancelButtonColor: "#d33",
|
|
||||||
confirmButtonText: "Sì, modifica!",
|
|
||||||
cancelButtonText: "Annulla"
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
$.ajax({
|
|
||||||
url: 'update_expiry.php',
|
|
||||||
type: 'POST',
|
|
||||||
data: {
|
|
||||||
order_id: orderId,
|
|
||||||
new_expiry: newExpiry,
|
|
||||||
update_expiry: 1
|
|
||||||
},
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(response) {
|
|
||||||
console.log("Risposta AJAX:", response);
|
|
||||||
if (response.status === 'success') {
|
|
||||||
Swal.fire('Successo!', response.message, 'success');
|
|
||||||
originalCell.text(newExpiry);
|
|
||||||
} else {
|
|
||||||
Swal.fire('Errore!', response.message, 'error');
|
|
||||||
originalCell.text(originalDate);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('Errore AJAX:', {
|
|
||||||
status: status,
|
|
||||||
error: error,
|
|
||||||
responseText: xhr.responseText
|
|
||||||
});
|
|
||||||
Swal.fire('Errore!', 'Errore durante la richiesta al server: ' + error, 'error');
|
|
||||||
originalCell.text(originalDate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
originalCell.text(originalDate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Funzione per confermare la cancellazione di un ordine e delle sue prenotazioni
|
|
||||||
window.confirmDeleteOrder = function(id) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Sei sicuro?",
|
|
||||||
text: "Verranno cancellati l'ordine e TUTTE le prenotazioni collegate a questa persona. L'azione è irreversibile!",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
cancelButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Sì, cancella tutto!",
|
|
||||||
cancelButtonText: "Annulla"
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
window.location.href = `deleteorder.php?id=${id}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Swal.fire({
|
|
||||||
title: "Sei sicuro?",
|
|
||||||
text: "Questa prenotazione verrà cancellata definitivamente! Ricordati poi di riprogrammare la tua lezione!",
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#d33",
|
|
||||||
cancelButtonColor: "#3085d6",
|
|
||||||
confirmButtonText: "Sì, cancella!",
|
|
||||||
cancelButtonText: "Annulla"
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
window.location.href = `deleteclass.php?id=${id}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style>
|
<style>
|
||||||
.custom-card {
|
.orders-toolbar {
|
||||||
margin: 10px auto;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 90%;
|
flex-wrap: wrap;
|
||||||
max-width: 700px;
|
gap: 8px;
|
||||||
background-color: white;
|
|
||||||
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-date-box {
|
|
||||||
flex: 1;
|
|
||||||
background-color: red;
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
margin-bottom: 12px;
|
||||||
font-size: 60px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-top-left-radius: 8px;
|
|
||||||
border-bottom-left-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-day {
|
.orders-search {
|
||||||
line-height: 1;
|
margin-left: auto;
|
||||||
|
position: relative;
|
||||||
|
min-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-month {
|
.orders-search i {
|
||||||
font-size: 28px;
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-event-details {
|
.orders-search input {
|
||||||
flex: 2;
|
padding-left: 34px;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: lightblue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-heading {
|
#searchCount {
|
||||||
margin-top: 0;
|
font-size: 13px;
|
||||||
font-size: 24px;
|
color: #6b7280;
|
||||||
|
min-height: 20px;
|
||||||
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-paragraph {
|
.editable {
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-actions {
|
|
||||||
display: none;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-card.expanded .custom-actions {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-action-button {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-action-button:hover {
|
.editable:hover {
|
||||||
background-color: #e0e0e0;
|
background-color: #eef6f1 !important;
|
||||||
|
box-shadow: inset 0 0 0 1px #cfe8db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expiry-date {
|
.num-input {
|
||||||
cursor: pointer;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expiry-date:hover {
|
.expiryDateInput {
|
||||||
background-color: #f0f0f0;
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9ca3af;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.custom-card {
|
.orders-search {
|
||||||
flex-direction: column;
|
margin-left: 0;
|
||||||
}
|
min-width: 100%;
|
||||||
|
|
||||||
.custom-date-box,
|
|
||||||
.custom-event-details {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-event-time {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -329,98 +158,118 @@ if ($result_dayoff->num_rows > 0) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<?php include('include/languageselection.php'); ?>
|
<?php include('include/languageselection.php'); ?>
|
||||||
<div class="dropdown d-inline-block">
|
<?php include('include/profiletopbar.php'); ?>
|
||||||
<button type="button" class="btn header-item noti-icon" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
||||||
<i class="bx bx-search icon-sm align-middle"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-end p-0">
|
|
||||||
<form class="p-2">
|
|
||||||
<div class="search-box">
|
|
||||||
<div class="position-relative">
|
|
||||||
<input type="text" class="form-control rounded bg-light border-0" placeholder="Search...">
|
|
||||||
<i class="bx bx-search search-icon"></i>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<?php include('include/profiletopbar.php'); ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</header>
|
<?php include('include/sidebar.php'); ?>
|
||||||
<?php include('include/sidebar.php'); ?>
|
<header class="ishorizontal-topbar">
|
||||||
<header class="ishorizontal-topbar">
|
<div class="navbar-header">
|
||||||
<div class="navbar-header">
|
<div class="d-flex"></div>
|
||||||
<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>
|
||||||
</div>
|
<div class="topnav">
|
||||||
</header>
|
<div class="container-fluid">
|
||||||
|
<nav class="navbar navbar-light navbar-expand-lg topnav-menu"></nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-12">
|
<div class="col-xl-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5>Riepilogo Ordini (Amministratore)</h5>
|
<h5>Riepilogo Ordini (Amministratore)</h5>
|
||||||
<p>Tutti gli ordini registrati</p>
|
<p>Tutti gli ordini registrati</p>
|
||||||
<div class="mb-3">
|
|
||||||
<a href="?filter=all" class="btn btn-secondary <?php echo $filter == 'all' ? 'active' : ''; ?>">Tutti</a>
|
<div class="orders-toolbar">
|
||||||
<a href="?filter=active" class="btn btn-success <?php echo $filter == 'active' ? 'active' : ''; ?>">Attivi</a>
|
<a href="?filter=all<?php echo e($qParam); ?>" data-filter="all" class="filter-link btn btn-secondary <?php echo $filter === 'all' ? 'active' : ''; ?>">Tutti</a>
|
||||||
<a href="?filter=expired" class="btn btn-danger <?php echo $filter == 'expired' ? 'active' : ''; ?>">Scaduti</a>
|
<a href="?filter=active<?php echo e($qParam); ?>" data-filter="active" class="filter-link btn btn-success <?php echo $filter === 'active' ? 'active' : ''; ?>">Attivi</a>
|
||||||
</div>
|
<a href="?filter=expired<?php echo e($qParam); ?>" data-filter="expired" class="filter-link btn btn-danger <?php echo $filter === 'expired' ? 'active' : ''; ?>">Scaduti</a>
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped mb-0">
|
<div class="orders-search">
|
||||||
<thead>
|
<i class="fas fa-search"></i>
|
||||||
<tr>
|
<input type="search" id="orderSearch" class="form-control" autocomplete="off"
|
||||||
<th>Stato</th>
|
placeholder="Cerca nome, cognome, email, n. ordine…" value="<?php echo e($q); ?>">
|
||||||
<th>Numero Ordine</th>
|
</div>
|
||||||
<th>Nome</th>
|
</div>
|
||||||
<th>Cognome</th>
|
|
||||||
<th>Nome Classe</th>
|
<div id="searchCount"></div>
|
||||||
<th>Numero Ticket</th>
|
<div class="edit-hint"><i class="fas fa-pen me-1"></i> Clicca su scadenza, max riprogr. o riprogrammate per modificarle.</div>
|
||||||
<th>Scadenza</th>
|
|
||||||
<th>Status</th>
|
<div class="table-responsive">
|
||||||
<th>Azioni</th>
|
<table class="table table-striped mb-0" id="ordersTable">
|
||||||
</tr>
|
<thead>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($orders as $order) {
|
|
||||||
$isActive = ($order["expireon"] > $today);
|
|
||||||
$badgeClass = $isActive ? 'badge bg-success' : 'badge bg-danger';
|
|
||||||
$badgeText = $isActive ? 'Attivo' : 'Scaduto';
|
|
||||||
?>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="<?php echo $badgeClass; ?>"><?php echo $badgeText; ?></span></td>
|
<th>Stato</th>
|
||||||
<td><?php echo $order["idorderbook"]; ?></td>
|
<th>N. Ordine</th>
|
||||||
<td><?php echo $order["first_name"]; ?></td>
|
<th>Nome</th>
|
||||||
<td><?php echo $order["last_name"]; ?></td>
|
<th>Cognome</th>
|
||||||
<td><?php echo $order["servicename"]; ?></td>
|
<th>Classe</th>
|
||||||
<td><?php echo $order["nticket"]; ?></td>
|
<th>Ticket</th>
|
||||||
<td class="expiry-date" data-order-id="<?php echo $order['idorderbook']; ?>"><?php echo $order["expireon"]; ?></td>
|
<th>Scadenza</th>
|
||||||
<td><?php echo $order["status"]; ?></td>
|
<th>Max riprogr.</th>
|
||||||
<td>
|
<th>Riprogrammate</th>
|
||||||
<button type="button" class="btn btn-sm btn-danger"
|
<th>Status</th>
|
||||||
onclick="confirmDeleteOrder(<?php echo $order['idorderbook']; ?>)">
|
<th>Azioni</th>
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php } ?>
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
<?php foreach ($orders as $order) :
|
||||||
|
$id = (int) $order['idorderbook'];
|
||||||
|
$isActive = ($order['expireon'] !== null && $order['expireon'] > $today);
|
||||||
|
$max = $order['maxreschedule'];
|
||||||
|
$done = (int) $order['reprogrammed'];
|
||||||
|
$search = implode(' ', [
|
||||||
|
$order['first_name'],
|
||||||
|
$order['last_name'],
|
||||||
|
$order['order_billing_email'],
|
||||||
|
$id,
|
||||||
|
$order['order_id'],
|
||||||
|
$order['servicename'],
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
<tr class="order-row" data-search="<?php echo e($search); ?>">
|
||||||
|
<td class="state-cell">
|
||||||
|
<span class="badge <?php echo $isActive ? 'bg-success' : 'bg-danger'; ?>"><?php echo $isActive ? 'Attivo' : 'Scaduto'; ?></span>
|
||||||
|
</td>
|
||||||
|
<td><?php echo $id; ?></td>
|
||||||
|
<td><?php echo e($order['first_name']); ?></td>
|
||||||
|
<td><?php echo e($order['last_name']); ?></td>
|
||||||
|
<td><?php echo e($order['servicename']); ?></td>
|
||||||
|
<td><?php echo e($order['nticket']); ?></td>
|
||||||
|
<td class="expiry-date editable" data-order-id="<?php echo $id; ?>" title="Clicca per modificare"><?php echo e($order['expireon']); ?></td>
|
||||||
|
<td class="num-edit editable" data-order-id="<?php echo $id; ?>" data-field="maxreschedule"
|
||||||
|
data-value="<?php echo $max === null ? '' : (int) $max; ?>" title="Clicca per modificare">
|
||||||
|
<?php echo $max === null ? '<span class="text-muted">–</span>' : (int) $max; ?>
|
||||||
|
</td>
|
||||||
|
<td class="num-edit editable" data-order-id="<?php echo $id; ?>" data-field="reprogrammed"
|
||||||
|
data-value="<?php echo $done; ?>" title="Clicca per modificare">
|
||||||
|
<?php echo reschedBadge($max, $done); ?>
|
||||||
|
</td>
|
||||||
|
<td><?php echo e($order['status']); ?></td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-danger" onclick="confirmDeleteOrder(<?php echo $id; ?>)">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<tr id="noResults" style="display:none;">
|
||||||
|
<td colspan="11" class="text-center text-muted py-4">Nessun ordine trovato.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
</div>
|
</div>
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -429,11 +278,261 @@ if ($result_dayoff->num_rows > 0) {
|
|||||||
<script src="assets/libs/simplebar/simplebar.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/libs/eva-icons/eva.min.js"></script>
|
||||||
<script src="assets/js/app.js"></script>
|
<script src="assets/js/app.js"></script>
|
||||||
<?php if ($message == 'success_delete') { ?>
|
|
||||||
|
<script>
|
||||||
|
function confirmDeleteOrder(id) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Sei sicuro?",
|
||||||
|
text: "Verranno cancellati l'ordine e TUTTE le prenotazioni collegate a questa persona. L'azione è irreversibile!",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#d33",
|
||||||
|
cancelButtonColor: "#3085d6",
|
||||||
|
confirmButtonText: "Sì, cancella tutto!",
|
||||||
|
cancelButtonText: "Annulla"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.href = 'deleteorder.php?id=' + id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var today = '<?php echo $today; ?>';
|
||||||
|
|
||||||
|
/* ===================== RICERCA ===================== */
|
||||||
|
function norm(s) {
|
||||||
|
return (s || '').toString().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#ordersTable tr.order-row').each(function() {
|
||||||
|
$(this).data('hay', norm($(this).attr('data-search')));
|
||||||
|
});
|
||||||
|
|
||||||
|
function applySearch() {
|
||||||
|
var raw = $('#orderSearch').val().trim();
|
||||||
|
var terms = norm(raw).split(/\s+/).filter(Boolean);
|
||||||
|
var visible = 0;
|
||||||
|
|
||||||
|
$('#ordersTable tr.order-row').each(function() {
|
||||||
|
var hay = $(this).data('hay');
|
||||||
|
var ok = terms.every(function(t) {
|
||||||
|
return hay.indexOf(t) !== -1;
|
||||||
|
});
|
||||||
|
$(this).toggle(ok);
|
||||||
|
if (ok) visible++;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#noResults').toggle(visible === 0);
|
||||||
|
$('#searchCount').text(terms.length ? visible + (visible === 1 ? ' ordine trovato' : ' ordini trovati') : '');
|
||||||
|
|
||||||
|
// i pulsanti Tutti/Attivi/Scaduti mantengono la ricerca
|
||||||
|
$('.filter-link').each(function() {
|
||||||
|
$(this).attr('href', '?filter=' + $(this).data('filter') + (raw ? '&q=' + encodeURIComponent(raw) : ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
var url = new URL(window.location.href);
|
||||||
|
if (raw) url.searchParams.set('q', raw);
|
||||||
|
else url.searchParams.delete('q');
|
||||||
|
url.searchParams.delete('message');
|
||||||
|
history.replaceState(null, '', url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#orderSearch').on('input', applySearch);
|
||||||
|
applySearch();
|
||||||
|
|
||||||
|
/* ===================== SCADENZA ===================== */
|
||||||
|
$(document).on('click', 'td.expiry-date', function() {
|
||||||
|
var cell = $(this);
|
||||||
|
if (cell.find('input').length) return;
|
||||||
|
|
||||||
|
var orderId = cell.data('order-id');
|
||||||
|
var current = cell.text().trim();
|
||||||
|
cell.data('original', current).data('selected', false);
|
||||||
|
|
||||||
|
var input = $('<input type="text" class="form-control form-control-sm expiryDateInput" readonly>').val(current);
|
||||||
|
cell.empty().append(input);
|
||||||
|
|
||||||
|
input.datepicker({
|
||||||
|
dateFormat: 'yy-mm-dd',
|
||||||
|
minDate: 0,
|
||||||
|
onSelect: function(dateText) {
|
||||||
|
cell.data('selected', true);
|
||||||
|
confirmExpiry(cell, orderId, dateText);
|
||||||
|
},
|
||||||
|
onClose: function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
if (!cell.data('selected')) cell.text(cell.data('original'));
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.datepicker('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmExpiry(cell, orderId, newExpiry) {
|
||||||
|
var original = cell.data('original');
|
||||||
|
if (newExpiry === original) {
|
||||||
|
cell.text(original);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Swal.fire({
|
||||||
|
title: "Sei sicuro?",
|
||||||
|
html: "La scadenza passerà a <strong>" + newExpiry + "</strong>.",
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Sì, modifica!",
|
||||||
|
cancelButtonText: "Annulla"
|
||||||
|
}).then(function(result) {
|
||||||
|
if (!result.isConfirmed) {
|
||||||
|
cell.text(original);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: 'update_expiry.php',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
order_id: orderId,
|
||||||
|
new_expiry: newExpiry,
|
||||||
|
update_expiry: 1
|
||||||
|
}
|
||||||
|
}).done(function(res) {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
cell.text(newExpiry);
|
||||||
|
var active = newExpiry > today;
|
||||||
|
cell.closest('tr').find('.state-cell').html(
|
||||||
|
'<span class="badge ' + (active ? 'bg-success' : 'bg-danger') + '">' + (active ? 'Attivo' : 'Scaduto') + '</span>'
|
||||||
|
);
|
||||||
|
Swal.fire('Successo!', res.message, 'success');
|
||||||
|
} else {
|
||||||
|
cell.text(original);
|
||||||
|
Swal.fire('Errore!', res.message, 'error');
|
||||||
|
}
|
||||||
|
}).fail(function(xhr, status, error) {
|
||||||
|
console.error('Errore AJAX:', xhr.responseText);
|
||||||
|
cell.text(original);
|
||||||
|
Swal.fire('Errore!', 'Errore durante la richiesta al server: ' + error, 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============ MAX RIPROGRAMMAZIONI / RIPROGRAMMATE ============ */
|
||||||
|
function badgeHtml(max, done) {
|
||||||
|
done = parseInt(done, 10) || 0;
|
||||||
|
if (max === null || max === '' || typeof max === 'undefined') {
|
||||||
|
return '<span class="badge bg-light text-dark">' + done + '</span>';
|
||||||
|
}
|
||||||
|
max = parseInt(max, 10);
|
||||||
|
var cls = done >= max ? 'bg-danger' : (done > 0 ? 'bg-warning text-dark' : 'bg-success');
|
||||||
|
return '<span class="badge ' + cls + '">' + done + ' / ' + max + '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResched(row, max, done) {
|
||||||
|
var maxCell = row.find('td[data-field="maxreschedule"]');
|
||||||
|
var doneCell = row.find('td[data-field="reprogrammed"]');
|
||||||
|
var hasMax = !(max === null || max === '' || typeof max === 'undefined');
|
||||||
|
maxCell.attr('data-value', hasMax ? max : '').html(hasMax ? max : '<span class="text-muted">–</span>');
|
||||||
|
doneCell.attr('data-value', done).html(badgeHtml(max, done));
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).on('click', 'td.num-edit', function() {
|
||||||
|
var cell = $(this);
|
||||||
|
if (cell.find('input').length) return;
|
||||||
|
|
||||||
|
var field = cell.data('field');
|
||||||
|
var orderId = cell.data('order-id');
|
||||||
|
var current = String(cell.attr('data-value'));
|
||||||
|
var input = $('<input type="number" min="0" max="999" step="1" class="form-control form-control-sm num-input">').val(current);
|
||||||
|
|
||||||
|
cell.data('html', cell.html()).empty().append(input);
|
||||||
|
input.trigger('focus').trigger('select');
|
||||||
|
|
||||||
|
var finished = false;
|
||||||
|
|
||||||
|
function finish(save) {
|
||||||
|
if (finished) return;
|
||||||
|
finished = true;
|
||||||
|
var val = String(input.val()).trim();
|
||||||
|
if (!save || val === '' || val === current) {
|
||||||
|
cell.html(cell.data('html'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^\d{1,3}$/.test(val)) {
|
||||||
|
cell.html(cell.data('html'));
|
||||||
|
Swal.fire('Valore non valido', 'Inserisci un numero intero da 0 a 999.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
confirmNumber(cell, orderId, field, parseInt(val, 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
input.on('keydown', function(ev) {
|
||||||
|
if (ev.key === 'Enter') {
|
||||||
|
ev.preventDefault();
|
||||||
|
finish(true);
|
||||||
|
} else if (ev.key === 'Escape') {
|
||||||
|
finish(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
input.on('blur', function() {
|
||||||
|
finish(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmNumber(cell, orderId, field, value) {
|
||||||
|
var label = field === 'maxreschedule' ? 'Max riprogrammazioni' : 'Riprogrammate';
|
||||||
|
Swal.fire({
|
||||||
|
title: 'Confermi la modifica?',
|
||||||
|
html: label + ': <strong>' + value + '</strong>',
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#3085d6',
|
||||||
|
cancelButtonColor: '#d33',
|
||||||
|
confirmButtonText: 'Sì, modifica!',
|
||||||
|
cancelButtonText: 'Annulla'
|
||||||
|
}).then(function(result) {
|
||||||
|
if (!result.isConfirmed) {
|
||||||
|
cell.html(cell.data('html'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: 'update_reschedule.php',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
order_id: orderId,
|
||||||
|
field: field,
|
||||||
|
value: value
|
||||||
|
}
|
||||||
|
}).done(function(res) {
|
||||||
|
if (res.status === 'success') {
|
||||||
|
renderResched(cell.closest('tr'), res.maxreschedule, res.reprogrammed);
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Aggiornato',
|
||||||
|
timer: 1200,
|
||||||
|
showConfirmButton: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cell.html(cell.data('html'));
|
||||||
|
Swal.fire('Errore!', res.message || 'Aggiornamento non riuscito.', 'error');
|
||||||
|
}
|
||||||
|
}).fail(function(xhr) {
|
||||||
|
console.error('Errore AJAX:', xhr.responseText);
|
||||||
|
cell.html(cell.data('html'));
|
||||||
|
Swal.fire('Errore!', 'Errore di comunicazione con il server.', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php if ($message === 'success_delete') { ?>
|
||||||
<script>
|
<script>
|
||||||
Swal.fire('Fatto!', 'Ordine e prenotazioni cancellati.', 'success');
|
Swal.fire('Fatto!', 'Ordine e prenotazioni cancellati.', 'success');
|
||||||
</script>
|
</script>
|
||||||
<?php } elseif ($message == 'error_delete') { ?>
|
<?php } elseif ($message === 'error_delete') { ?>
|
||||||
<script>
|
<script>
|
||||||
Swal.fire('Errore!', 'Errore durante la cancellazione.', 'error');
|
Swal.fire('Errore!', 'Errore durante la cancellazione.', 'error');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
[15-Oct-2025 15:13:11 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-16
|
[15-Oct-2025 15:13:11 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-16
|
||||||
[15-Oct-2025 15:13:19 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-15
|
[15-Oct-2025 15:13:19 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-15
|
||||||
[15-Oct-2025 15:13:58 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-30
|
[15-Oct-2025 15:13:58 UTC] Tentativo di aggiornamento AJAX - idorderbook: 1, new_expiry: 2025-10-30
|
||||||
|
[24-Sep-2026 09:19:47 UTC] Tentativo di aggiornamento AJAX - idorderbook: 7, new_expiry: 2026-11-02
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggiorna maxreschedule o reprogrammed di un ordine (chiamato via AJAX dal riepilogo ordini).
|
||||||
|
* Risponde sempre in JSON: {status, message, maxreschedule, reprogrammed}
|
||||||
|
*/
|
||||||
|
ob_start();
|
||||||
|
require_once('include/headscript.php');
|
||||||
|
ob_end_clean(); // scarta eventuale output di headscript, così il JSON resta pulito
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
function jsonOut(array $data, int $code = 200): void
|
||||||
|
{
|
||||||
|
http_response_code($code);
|
||||||
|
echo json_encode($data);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'Metodo non consentito.'], 405);
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedFields = ['maxreschedule', 'reprogrammed'];
|
||||||
|
$field = $_POST['field'] ?? '';
|
||||||
|
$orderId = (int) ($_POST['order_id'] ?? 0);
|
||||||
|
$value = (string) ($_POST['value'] ?? '');
|
||||||
|
|
||||||
|
if (!in_array($field, $allowedFields, true)) {
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'Campo non valido.'], 400);
|
||||||
|
}
|
||||||
|
if ($orderId <= 0) {
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'ID ordine non valido.'], 400);
|
||||||
|
}
|
||||||
|
if (!preg_match('/^\d{1,3}$/', $value)) {
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'Inserisci un numero intero da 0 a 999.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
$check = $pdo->prepare("SELECT idorderbook FROM orderbook WHERE idorderbook = :id");
|
||||||
|
$check->execute([':id' => $orderId]);
|
||||||
|
if (!$check->fetch()) {
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'Ordine non trovato.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// $field è da whitelist, quindi sicuro da inserire nella query
|
||||||
|
$upd = $pdo->prepare("UPDATE orderbook SET `$field` = :v WHERE idorderbook = :id");
|
||||||
|
$upd->execute([':v' => (int) $value, ':id' => $orderId]);
|
||||||
|
|
||||||
|
$row = $pdo->prepare("SELECT maxreschedule, reprogrammed FROM orderbook WHERE idorderbook = :id");
|
||||||
|
$row->execute([':id' => $orderId]);
|
||||||
|
$r = $row->fetch();
|
||||||
|
|
||||||
|
jsonOut([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Valore aggiornato.',
|
||||||
|
'maxreschedule' => $r['maxreschedule'] === null ? null : (int) $r['maxreschedule'],
|
||||||
|
'reprogrammed' => (int) $r['reprogrammed'],
|
||||||
|
]);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('update_reschedule: ' . $e->getMessage());
|
||||||
|
jsonOut(['status' => 'error', 'message' => 'Errore durante il salvataggio.'], 500);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user