settings
This commit is contained in:
parent
bd9d3811f6
commit
75d63261bb
@ -4,6 +4,10 @@ ini_set('display_errors', 1);
|
|||||||
ini_set('display_startup_errors', 1);
|
ini_set('display_startup_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
include('include/headscript.php');
|
include('include/headscript.php');
|
||||||
|
|
||||||
// Connessione DB
|
// Connessione DB
|
||||||
@ -19,17 +23,17 @@ if (!isset($iduserlogin)) {
|
|||||||
// Controlla se esiste almeno un salone
|
// Controlla se esiste almeno un salone
|
||||||
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
if ($stmt->fetchColumn() === 0) {
|
if ((int)$stmt->fetchColumn() === 0) {
|
||||||
header("Location: onboarding_salon.php");
|
header("Location: onboarding_salon.php");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prendi il primo salone (o quello attivo – puoi aggiungere switcher dopo)
|
// Prendi il primo salone (o quello attivo – puoi aggiungere switcher dopo)
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, name
|
SELECT id, name
|
||||||
FROM shops
|
FROM shops
|
||||||
WHERE owner_id = ?
|
WHERE owner_id = ?
|
||||||
ORDER BY created_at ASC
|
ORDER BY created_at ASC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
");
|
");
|
||||||
$stmt->execute([$iduserlogin]);
|
$stmt->execute([$iduserlogin]);
|
||||||
@ -39,66 +43,150 @@ if (!$shop) {
|
|||||||
die("Errore: salone non trovato.");
|
die("Errore: salone non trovato.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$shop_id = $shop['id'];
|
$shop_id = (int)$shop['id'];
|
||||||
$shop_name = $shop['name'];
|
$shop_name = $shop['name'];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Helpers
|
||||||
|
// =========================================================================
|
||||||
|
function isValidDateYmd(string $date): bool {
|
||||||
|
$d = DateTime::createFromFormat('Y-m-d', $date);
|
||||||
|
return $d && $d->format('Y-m-d') === $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFlash(string $type, string $text): void {
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlash(): ?array {
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Gestione POST (add / edit / delete)
|
// Gestione POST (add / edit / delete)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
$success_message = '';
|
|
||||||
$error_message = '';
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
$action = $_POST['action'];
|
$action = $_POST['action'];
|
||||||
|
|
||||||
if ($action === 'add' || $action === 'edit') {
|
try {
|
||||||
$start_date = trim($_POST['start_date'] ?? '');
|
if ($action === 'add') {
|
||||||
$end_date = trim($_POST['end_date'] ?? '');
|
$start_date = trim($_POST['start_date'] ?? '');
|
||||||
$description = trim($_POST['description'] ?? '');
|
$end_date = trim($_POST['end_date'] ?? '');
|
||||||
$id = ($action === 'edit') ? (int)($_POST['id'] ?? 0) : 0;
|
$title = trim($_POST['title'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
|
||||||
// Validazioni
|
if ($start_date === '' || $end_date === '') {
|
||||||
if (empty($start_date) || empty($end_date)) {
|
setFlash('danger', "Le date di inizio e fine sono obbligatorie.");
|
||||||
$error_message = "Le date di inizio e fine sono obbligatorie.";
|
} elseif (!isValidDateYmd($start_date) || !isValidDateYmd($end_date)) {
|
||||||
} elseif (strtotime($end_date) < strtotime($start_date)) {
|
setFlash('danger', "Formato data non valido.");
|
||||||
$error_message = "La data di fine non può essere precedente alla data di inizio.";
|
} elseif (strtotime($end_date) < strtotime($start_date)) {
|
||||||
} else {
|
setFlash('danger', "La data di fine non può essere precedente alla data di inizio.");
|
||||||
if ($action === 'add') {
|
} else {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Inserisco una riga per ogni giorno del range (B2)
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO shop_day_off (shop_id, date, title, description, is_recurring, created_at)
|
INSERT INTO shop_day_off (shop_id, date, title, description, is_recurring)
|
||||||
VALUES (?, ?, ?, ?, 0, NOW())
|
VALUES (?, ?, ?, ?, 0)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
title = VALUES(title),
|
||||||
|
description = VALUES(description),
|
||||||
|
updated_at = CURRENT_TIMESTAMP
|
||||||
");
|
");
|
||||||
$ok = $stmt->execute([$shop_id, $start_date, $description ?: 'Chiusura', $description]);
|
|
||||||
$success_message = $ok ? "Giorno di chiusura aggiunto!" : "Errore durante l'aggiunta.";
|
$start = new DateTime($start_date);
|
||||||
} else { // edit
|
$end = new DateTime($end_date);
|
||||||
|
$end->setTime(0,0,0);
|
||||||
|
|
||||||
|
// +1 giorno per includere la fine
|
||||||
|
$period = new DatePeriod($start, new DateInterval('P1D'), (clone $end)->modify('+1 day'));
|
||||||
|
|
||||||
|
$inserted = 0;
|
||||||
|
foreach ($period as $dt) {
|
||||||
|
$day = $dt->format('Y-m-d');
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$shop_id,
|
||||||
|
$day,
|
||||||
|
$title !== '' ? $title : 'Chiusura',
|
||||||
|
$description
|
||||||
|
]);
|
||||||
|
if ($ok) $inserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
setFlash('success', "Chiusura aggiunta: salvati/aggiornati {$inserted} giorni.");
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: day_off.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'edit') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
$date = trim($_POST['start_date'] ?? ''); // nel form edit usiamo start_date come "data singola"
|
||||||
|
$title = trim($_POST['title'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
} elseif ($date === '' || !isValidDateYmd($date)) {
|
||||||
|
setFlash('danger', "La data è obbligatoria e deve essere valida.");
|
||||||
|
} else {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
UPDATE shop_day_off
|
UPDATE shop_day_off
|
||||||
SET date = ?, title = ?, description = ?, updated_at = NOW()
|
SET date = ?, title = ?, description = ?, updated_at = NOW()
|
||||||
WHERE id = ? AND shop_id = ?
|
WHERE id = ? AND shop_id = ?
|
||||||
");
|
");
|
||||||
$ok = $stmt->execute([$start_date, $description ?: 'Chiusura', $description, $id, $shop_id]);
|
$ok = $stmt->execute([
|
||||||
$success_message = $ok ? "Giorno di chiusura aggiornato!" : "Errore durante l'aggiornamento.";
|
$date,
|
||||||
|
$title !== '' ? $title : 'Chiusura',
|
||||||
|
$description,
|
||||||
|
$id,
|
||||||
|
$shop_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Giorno di chiusura aggiornato!" : "Errore durante l'aggiornamento.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action === 'delete') {
|
header("Location: day_off.php");
|
||||||
$id = (int)($_POST['id'] ?? 0);
|
exit;
|
||||||
if ($id > 0) {
|
|
||||||
$stmt = $pdo->prepare("DELETE FROM shop_day_off WHERE id = ? AND shop_id = ?");
|
|
||||||
$ok = $stmt->execute([$id, $shop_id]);
|
|
||||||
$success_message = $ok ? "Giorno di chiusura eliminato!" : "Errore durante l'eliminazione.";
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Evita doppio submit
|
if ($action === 'delete') {
|
||||||
if ($success_message || $error_message) {
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
header("Location: day_off.php" . ($success_message ? "?msg=success" : "?msg=error"));
|
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM shop_day_off WHERE id = ? AND shop_id = ?");
|
||||||
|
$ok = $stmt->execute([$id, $shop_id]);
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Giorno di chiusura eliminato!" : "Errore durante l'eliminazione.");
|
||||||
|
}
|
||||||
|
|
||||||
|
header("Location: day_off.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// action sconosciuta
|
||||||
|
setFlash('danger', "Azione non valida.");
|
||||||
|
header("Location: day_off.php");
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
||||||
|
setFlash('danger', "Errore: " . $e->getMessage());
|
||||||
|
header("Location: day_off.php");
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
// Recupera tutti i giorni di chiusura
|
// Recupera tutti i giorni di chiusura
|
||||||
|
// =========================================================================
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT id, date, title, description, is_recurring
|
SELECT id, date, title, description, is_recurring
|
||||||
FROM shop_day_off
|
FROM shop_day_off
|
||||||
@ -106,12 +194,13 @@ $stmt = $pdo->prepare("
|
|||||||
ORDER BY date ASC
|
ORDER BY date ASC
|
||||||
");
|
");
|
||||||
$stmt->execute([$shop_id]);
|
$stmt->execute([$shop_id]);
|
||||||
$days_off = $stmt->fetchAll();
|
$days_off = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$flash = getFlash();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="it">
|
<html lang="it">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@ -122,184 +211,197 @@ $days_off = $stmt->fetchAll();
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<?php include('include/navbar.php'); ?>
|
<?php include('include/navbar.php'); ?>
|
||||||
<?php include('include/topbar.php'); ?>
|
<?php include('include/topbar.php'); ?>
|
||||||
|
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<div class="card radius-10">
|
<div class="card radius-10">
|
||||||
<div class="card-header bg-light d-flex align-items-center justify-content-between">
|
<div class="card-header bg-light d-flex align-items-center justify-content-between">
|
||||||
<h6 class="mb-0">Giorni di Chiusura - <?= htmlspecialchars($shop_name) ?></h6>
|
<h6 class="mb-0">Giorni di Chiusura - <?= htmlspecialchars($shop_name) ?></h6>
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addDayOffModal">
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addDayOffModal">
|
||||||
<i class="bx bx-plus me-1"></i> Aggiungi Chiusura
|
<i class="bx bx-plus me-1"></i> Aggiungi Chiusura
|
||||||
</button>
|
</button>
|
||||||
<a href="salon_dashboard.php" class="btn btn-outline-secondary ms-2">
|
<a href="salon_dashboard.php" class="btn btn-outline-secondary ms-2">
|
||||||
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show" role="alert">
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="card-body">
|
<?php if (empty($days_off)): ?>
|
||||||
<?php if (isset($_GET['msg'])): ?>
|
<div class="alert alert-info text-center py-4">
|
||||||
<?php if ($_GET['msg'] === 'success'): ?>
|
Non hai ancora impostato giorni di chiusura.<br>
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
Aggiungine uno per bloccare le prenotazioni in quei giorni.
|
||||||
<?= htmlspecialchars($success_message) ?>
|
</div>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<?php else: ?>
|
||||||
</div>
|
<div class="table-responsive">
|
||||||
<?php elseif ($_GET['msg'] === 'error'): ?>
|
<table id="daysOffTable" class="table table-striped table-hover table-bordered">
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
<thead>
|
||||||
<?= htmlspecialchars($error_message) ?>
|
<tr>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<th>Data</th>
|
||||||
</div>
|
<th>Titolo / Descrizione</th>
|
||||||
<?php endif; ?>
|
<th>Ricorrente</th>
|
||||||
<?php endif; ?>
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($days_off as $day): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($day['date']) ?></td>
|
||||||
|
<td><?= htmlspecialchars(($day['title'] ?: '') . (($day['description'] ?? '') ? ' — ' . $day['description'] : '')) ?: 'Chiusura' ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($day['is_recurring'])): ?>
|
||||||
|
<span class="badge bg-success">Sì (ogni anno)</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-warning me-1"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editDayOffModal"
|
||||||
|
onclick='fillEditModal(<?= json_encode([
|
||||||
|
"id" => (int)$day["id"],
|
||||||
|
"date" => $day["date"],
|
||||||
|
"title" => $day["title"] ?? "",
|
||||||
|
"description" => $day["description"] ?? ""
|
||||||
|
], JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
|
||||||
|
<i class="bx bx-edit"></i> Modifica
|
||||||
|
</button>
|
||||||
|
|
||||||
<?php if (empty($days_off)): ?>
|
<form action="" method="POST" style="display:inline;"
|
||||||
<div class="alert alert-info text-center py-4">
|
onsubmit="return confirm('Confermi l\'eliminazione?');">
|
||||||
Non hai ancora impostato giorni di chiusura.<br>
|
<input type="hidden" name="action" value="delete">
|
||||||
Aggiungine uno per bloccare le prenotazioni in quei giorni.
|
<input type="hidden" name="id" value="<?= (int)$day['id'] ?>">
|
||||||
</div>
|
<button type="submit" class="btn btn-sm btn-danger">
|
||||||
<?php else: ?>
|
<i class="bx bx-trash"></i> Elimina
|
||||||
<div class="table-responsive">
|
</button>
|
||||||
<table id="daysOffTable" class="table table-striped table-hover table-bordered">
|
</form>
|
||||||
<thead>
|
</td>
|
||||||
<tr>
|
</tr>
|
||||||
<th>Data</th>
|
<?php endforeach; ?>
|
||||||
<th>Titolo / Descrizione</th>
|
</tbody>
|
||||||
<th>Ricorrente</th>
|
</table>
|
||||||
<th>Azioni</th>
|
</div>
|
||||||
</tr>
|
<?php endif; ?>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<?php foreach ($days_off as $day): ?>
|
|
||||||
<tr>
|
|
||||||
<td><?= htmlspecialchars($day['date']) ?></td>
|
|
||||||
<td><?= htmlspecialchars($day['title'] ?: $day['description'] ?: 'Chiusura') ?></td>
|
|
||||||
<td>
|
|
||||||
<?php if ($day['is_recurring']): ?>
|
|
||||||
<span class="badge bg-success">Sì (ogni anno)</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary">No</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button type="button" class="btn btn-sm btn-warning me-1"
|
|
||||||
data-bs-toggle="modal" data-bs-target="#editDayOffModal"
|
|
||||||
onclick='fillEditModal(<?= json_encode([
|
|
||||||
"id" => $day['id'],
|
|
||||||
"date" => $day['date'],
|
|
||||||
"title" => htmlspecialchars($day['title'] ?? '', ENT_QUOTES),
|
|
||||||
"description" => htmlspecialchars($day['description'] ?? '', ENT_QUOTES)
|
|
||||||
]) ?>)'>
|
|
||||||
<i class="bx bx-edit"></i> Modifica
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<form action="" method="POST" style="display:inline;"
|
|
||||||
onsubmit="return confirm('Confermi l\'eliminazione?');">
|
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="id" value="<?= $day['id'] ?>">
|
|
||||||
<button type="submit" class="btn btn-sm btn-danger">
|
|
||||||
<i class="bx bx-trash"></i> Elimina
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php include('include/footer.php'); ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal Aggiungi -->
|
<?php include('include/footer.php'); ?>
|
||||||
<div class="modal fade" id="addDayOffModal" tabindex="-1" aria-labelledby="addDayOffModalLabel">
|
</div>
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
<!-- Modal Aggiungi -->
|
||||||
<div class="modal-header bg-primary text-white">
|
<div class="modal fade" id="addDayOffModal" tabindex="-1" aria-labelledby="addDayOffModalLabel">
|
||||||
<h5 class="modal-title" id="addDayOffModalLabel">Aggiungi Giorno di Chiusura</h5>
|
<div class="modal-dialog">
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
<div class="modal-content">
|
||||||
</div>
|
<div class="modal-header bg-primary text-white">
|
||||||
<form action="" method="POST">
|
<h5 class="modal-title" id="addDayOffModalLabel">Aggiungi Giorni di Chiusura</h5>
|
||||||
<div class="modal-body">
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
<input type="hidden" name="action" value="add">
|
</div>
|
||||||
<div class="mb-3">
|
<form action="" method="POST">
|
||||||
<label class="form-label fw-bold">Data <span class="text-danger">*</span></label>
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Dal <span class="text-danger">*</span></label>
|
||||||
<input type="date" class="form-control" name="start_date" required>
|
<input type="date" class="form-control" name="start_date" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label fw-bold">Descrizione / Motivo</label>
|
<label class="form-label fw-bold">Al <span class="text-danger">*</span></label>
|
||||||
<input type="text" class="form-control" name="description"
|
<input type="date" class="form-control" name="end_date" required>
|
||||||
placeholder="Es: Ferragosto, Ferie estive, Corso formazione">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
|
||||||
<button type="submit" class="btn btn-primary">Aggiungi</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal Modifica -->
|
<div class="mb-3">
|
||||||
<div class="modal fade" id="editDayOffModal" tabindex="-1" aria-labelledby="editDayOffModalLabel">
|
<label class="form-label fw-bold">Titolo (es. Ferie estive)</label>
|
||||||
<div class="modal-dialog">
|
<input type="text" class="form-control" name="title" placeholder="Titolo breve">
|
||||||
<div class="modal-content">
|
</div>
|
||||||
<div class="modal-header bg-warning text-dark">
|
|
||||||
<h5 class="modal-title" id="editDayOffModalLabel">Modifica Giorno di Chiusura</h5>
|
<div class="mb-3">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<label class="form-label fw-bold">Descrizione / Note</label>
|
||||||
|
<textarea class="form-control" name="description" rows="3"
|
||||||
|
placeholder="Dettagli (es. chiuso tutto il giorno per ferie)"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-secondary mb-0">
|
||||||
|
Verrà salvata una riga per ogni giorno del range (così blocchi facilmente le prenotazioni).
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form action="" method="POST">
|
<div class="modal-footer">
|
||||||
<div class="modal-body">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
<input type="hidden" name="action" value="edit">
|
<button type="submit" class="btn btn-primary">Aggiungi</button>
|
||||||
<input type="hidden" name="id" id="edit_id">
|
</div>
|
||||||
<div class="mb-3">
|
</form>
|
||||||
<label class="form-label fw-bold">Data <span class="text-danger">*</span></label>
|
|
||||||
<input type="date" class="form-control" name="start_date" id="edit_date" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-bold">Descrizione / Motivo</label>
|
|
||||||
<input type="text" class="form-control" name="description" id="edit_description"
|
|
||||||
placeholder="Es: Ferie natalizie">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
|
||||||
<button type="submit" class="btn btn-warning">Salva Modifiche</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php include('jsinclude.php'); ?>
|
<!-- Modal Modifica (singolo giorno) -->
|
||||||
|
<div class="modal fade" id="editDayOffModal" tabindex="-1" aria-labelledby="editDayOffModalLabel">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning text-dark">
|
||||||
|
<h5 class="modal-title" id="editDayOffModalLabel">Modifica Giorno di Chiusura</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
|
||||||
<script>
|
<div class="mb-3">
|
||||||
$(document).ready(function() {
|
<label class="form-label fw-bold">Data <span class="text-danger">*</span></label>
|
||||||
$('#daysOffTable').DataTable({
|
<input type="date" class="form-control" name="start_date" id="edit_date" required>
|
||||||
language: {
|
</div>
|
||||||
url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
|
|
||||||
},
|
<div class="mb-3">
|
||||||
order: [
|
<label class="form-label fw-bold">Titolo</label>
|
||||||
[0, 'asc']
|
<input type="text" class="form-control" name="title" id="edit_title" placeholder="Es: Chiusura straordinaria">
|
||||||
]
|
</div>
|
||||||
});
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Descrizione / Motivo</label>
|
||||||
|
<input type="text" class="form-control" name="description" id="edit_description"
|
||||||
|
placeholder="Es: Ferie natalizie">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-warning">Salva Modifiche</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('#daysOffTable').DataTable({
|
||||||
|
language: { url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json' },
|
||||||
|
order: [[0, 'asc']]
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function fillEditModal(data) {
|
function fillEditModal(data) {
|
||||||
document.getElementById('edit_id').value = data.id;
|
document.getElementById('edit_id').value = data.id;
|
||||||
document.getElementById('edit_date').value = data.date;
|
document.getElementById('edit_date').value = data.date;
|
||||||
document.getElementById('edit_description').value = data.description || '';
|
document.getElementById('edit_title').value = data.title || '';
|
||||||
}
|
document.getElementById('edit_description').value = data.description || '';
|
||||||
</script>
|
}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
</html>
|
||||||
</html>
|
|
||||||
|
|||||||
@ -85,6 +85,12 @@
|
|||||||
<div class="menu-title">Il mio profilo</div>
|
<div class="menu-title">Il mio profilo</div>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="user_settings.php">
|
||||||
|
<div class="parent-icon"><i class="bx bx-user"></i></div>
|
||||||
|
<div class="menu-title">Impostazioni utente</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Titolare Salone -->
|
<!-- Titolare Salone -->
|
||||||
|
|||||||
552
public/userarea/salon_profile.php
Normal file
552
public/userarea/salon_profile.php
Normal file
@ -0,0 +1,552 @@
|
|||||||
|
<?php
|
||||||
|
// Forza la visualizzazione degli errori (solo dev)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Connessione DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Verifica utente loggato
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se esiste almeno un salone
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
if ((int)$stmt->fetchColumn() === 0) {
|
||||||
|
header("Location: onboarding_salon.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prendi il salone
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT *
|
||||||
|
FROM shops
|
||||||
|
WHERE owner_id = ?
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$shop) {
|
||||||
|
die("Errore: salone non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$shop_id = (int)$shop['id'];
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helper: Flash messages
|
||||||
|
// ===========================
|
||||||
|
function setFlash(string $type, string $text): void
|
||||||
|
{
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlash(): ?array
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helper: Ridimensiona immagine (come nel tuo template)
|
||||||
|
// ===========================
|
||||||
|
function resizeImage($source_path, $dest_path, $max_width = 800)
|
||||||
|
{
|
||||||
|
list($width, $height, $type) = getimagesize($source_path);
|
||||||
|
if ($width <= $max_width) {
|
||||||
|
copy($source_path, $dest_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$new_width = $max_width;
|
||||||
|
$new_height = (int)(($height * $new_width) / $width);
|
||||||
|
switch ($type) {
|
||||||
|
case IMAGETYPE_JPEG:
|
||||||
|
$source = imagecreatefromjpeg($source_path);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_PNG:
|
||||||
|
$source = imagecreatefrompng($source_path);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_GIF:
|
||||||
|
$source = imagecreatefromgif($source_path);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Formato non supportato.");
|
||||||
|
}
|
||||||
|
$dest = imagecreatetruecolor($new_width, $new_height);
|
||||||
|
if ($type == IMAGETYPE_PNG) {
|
||||||
|
imagealphablending($dest, false);
|
||||||
|
imagesavealpha($dest, true);
|
||||||
|
}
|
||||||
|
imagecopyresampled($dest, $source, 0, 0, 0, 0, $new_width, $new_height, $width, $height);
|
||||||
|
switch ($type) {
|
||||||
|
case IMAGETYPE_JPEG:
|
||||||
|
imagejpeg($dest, $dest_path, 90);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_PNG:
|
||||||
|
imagepng($dest, $dest_path);
|
||||||
|
break;
|
||||||
|
case IMAGETYPE_GIF:
|
||||||
|
imagegif($dest, $dest_path);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
imagedestroy($source);
|
||||||
|
imagedestroy($dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helper: Geolocalizzazione con Nominatim
|
||||||
|
// ===========================
|
||||||
|
function geocodeAddress(string $address): array
|
||||||
|
{
|
||||||
|
$url = 'https://nominatim.openstreetmap.org/search';
|
||||||
|
$params = http_build_query([
|
||||||
|
'q' => $address,
|
||||||
|
'format' => 'json',
|
||||||
|
'limit' => 1,
|
||||||
|
'addressdetails' => 1,
|
||||||
|
'countrycodes' => 'it',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init($url . '?' . $params);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, 'HairBook Salon Manager/1.0 (contact: info@hairbook.it)');
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200 || !$response) {
|
||||||
|
return ['success' => false, 'message' => 'Errore connessione Nominatim'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
|
||||||
|
if (empty($data) || !isset($data[0])) {
|
||||||
|
return ['success' => false, 'message' => 'Indirizzo non trovato'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $data[0];
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'lat' => (float)$result['lat'],
|
||||||
|
'lon' => (float)$result['lon'],
|
||||||
|
'display' => $result['display_name'] ?? ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// POST - Salva profilo + geocode se necessario
|
||||||
|
// ===========================
|
||||||
|
$flash = null;
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !isset($_POST['action'])) { // normale submit form
|
||||||
|
try {
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$address = trim($_POST['address'] ?? '');
|
||||||
|
$address_extra = trim($_POST['address_extra'] ?? '');
|
||||||
|
$city = trim($_POST['city'] ?? '');
|
||||||
|
$province = strtoupper(trim($_POST['province'] ?? ''));
|
||||||
|
$zip_code = trim($_POST['zip_code'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$mobile = trim($_POST['mobile'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$website = trim($_POST['website'] ?? '');
|
||||||
|
$instagram = trim(ltrim($_POST['instagram'] ?? '', '@'));
|
||||||
|
$facebook = trim($_POST['facebook'] ?? '');
|
||||||
|
$google_maps_url = trim($_POST['google_maps_url'] ?? '');
|
||||||
|
$latitude = !empty($_POST['latitude']) ? (float)$_POST['latitude'] : null;
|
||||||
|
$longitude = !empty($_POST['longitude']) ? (float)$_POST['longitude'] : null;
|
||||||
|
$active = isset($_POST['active']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Validazioni
|
||||||
|
if ($name === '') {
|
||||||
|
throw new Exception("Nome salone obbligatorio.");
|
||||||
|
}
|
||||||
|
if ($city === '') {
|
||||||
|
throw new Exception("Città obbligatoria.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tenta geocode automatico se lat/lon vuoti
|
||||||
|
if ($latitude === null || $longitude === null) {
|
||||||
|
$full_address = implode(', ', array_filter([
|
||||||
|
$address,
|
||||||
|
$address_extra,
|
||||||
|
$zip_code,
|
||||||
|
$city,
|
||||||
|
$province ? $province : '',
|
||||||
|
'Italia'
|
||||||
|
]));
|
||||||
|
|
||||||
|
$geocode = geocodeAddress($full_address);
|
||||||
|
if ($geocode['success']) {
|
||||||
|
$latitude = $geocode['lat'];
|
||||||
|
$longitude = $geocode['lon'];
|
||||||
|
setFlash('info', "Coordinate calcolate automaticamente!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload logo
|
||||||
|
$logo = $shop['logo'];
|
||||||
|
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$file = $_FILES['logo'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$new_filename = "logos/{$shop_id}-" . time() . "." . $ext;
|
||||||
|
resizeImage($file['tmp_name'], $new_filename, 400);
|
||||||
|
if ($logo && file_exists($logo)) @unlink($logo);
|
||||||
|
$logo = $new_filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload cover
|
||||||
|
$cover = $shop['cover_image'];
|
||||||
|
if (isset($_FILES['cover_image']) && $_FILES['cover_image']['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$file = $_FILES['cover_image'];
|
||||||
|
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$new_filename = "covers/{$shop_id}-" . time() . "." . $ext;
|
||||||
|
resizeImage($file['tmp_name'], $new_filename, 1200);
|
||||||
|
if ($cover && file_exists($cover)) @unlink($cover);
|
||||||
|
$cover = $new_filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update DB
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE shops SET
|
||||||
|
name = ?, description = ?, address = ?, address_extra = ?, city = ?, province = ?,
|
||||||
|
zip_code = ?, phone = ?, mobile = ?, email = ?, website = ?, instagram = ?, facebook = ?,
|
||||||
|
google_maps_url = ?, latitude = ?, longitude = ?, logo = ?, cover_image = ?, active = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = ? AND owner_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$name,
|
||||||
|
$description ?: null,
|
||||||
|
$address,
|
||||||
|
$address_extra ?: null,
|
||||||
|
$city,
|
||||||
|
$province,
|
||||||
|
$zip_code,
|
||||||
|
$phone ?: null,
|
||||||
|
$mobile ?: null,
|
||||||
|
$email ?: null,
|
||||||
|
$website ?: null,
|
||||||
|
$instagram ?: null,
|
||||||
|
$facebook ?: null,
|
||||||
|
$google_maps_url ?: null,
|
||||||
|
$latitude,
|
||||||
|
$longitude,
|
||||||
|
$logo,
|
||||||
|
$cover,
|
||||||
|
$active,
|
||||||
|
$shop_id,
|
||||||
|
$iduserlogin
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Profilo aggiornato!" : "Errore salvataggio.");
|
||||||
|
header("Location: salon_profile.php");
|
||||||
|
exit;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
setFlash('danger', $e->getMessage());
|
||||||
|
header("Location: salon_profile.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash
|
||||||
|
$flash = getFlash();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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'); ?>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
<title>Profilo Salone - <?= htmlspecialchars($shop['name']) ?></title>
|
||||||
|
<style>
|
||||||
|
#map {
|
||||||
|
height: 350px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background: #f8f9fa;
|
||||||
|
}
|
||||||
|
</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 bg-light d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="mb-0">Modifica Profilo Salone</h6>
|
||||||
|
<a href="salon_dashboard.php" class="btn btn-outline-secondary">
|
||||||
|
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show">
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST" enctype="multipart/form-data">
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Colonna sinistra: dati principali -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nome Salone <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="name" value="<?= htmlspecialchars($shop['name']) ?>" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Descrizione</label>
|
||||||
|
<textarea class="form-control" name="description" rows="4"><?= htmlspecialchars($shop['description'] ?? '') ?></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<label class="form-label">Indirizzo <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="address" id="address" value="<?= htmlspecialchars($shop['address']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label">Indirizzo extra</label>
|
||||||
|
<input type="text" class="form-control" name="address_extra" value="<?= htmlspecialchars($shop['address_extra'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Città <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="city" id="city" value="<?= htmlspecialchars($shop['city']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Provincia</label>
|
||||||
|
<input type="text" class="form-control text-uppercase" name="province" maxlength="2" value="<?= htmlspecialchars($shop['province'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">CAP</label>
|
||||||
|
<input type="text" class="form-control" name="zip_code" value="<?= htmlspecialchars($shop['zip_code']) ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Telefono</label>
|
||||||
|
<input type="text" class="form-control" name="phone" value="<?= htmlspecialchars($shop['phone'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Cellulare / WhatsApp</label>
|
||||||
|
<input type="text" class="form-control" name="mobile" value="<?= htmlspecialchars($shop['mobile'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Email</label>
|
||||||
|
<input type="email" class="form-control" name="email" value="<?= htmlspecialchars($shop['email'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Sito Web</label>
|
||||||
|
<input type="url" class="form-control" name="website" value="<?= htmlspecialchars($shop['website'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Instagram (solo username)</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">@</span>
|
||||||
|
<input type="text" class="form-control" name="instagram" value="<?= htmlspecialchars($shop['instagram'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Facebook</label>
|
||||||
|
<input type="text" class="form-control" name="facebook" value="<?= htmlspecialchars($shop['facebook'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Link Google Maps (opzionale)</label>
|
||||||
|
<input type="url" class="form-control" name="google_maps_url" value="<?= htmlspecialchars($shop['google_maps_url'] ?? '') ?>">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colonna destra: immagini + mappa -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Logo Salone (quadrato)</label>
|
||||||
|
<?php if ($shop['logo']): ?>
|
||||||
|
<img src="<?= htmlspecialchars($shop['logo']) ?>" alt="Logo" class="img-fluid rounded mb-2" style="max-height:120px;">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" class="form-control" name="logo" accept="image/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label">Immagine di Copertina (orizzontale)</label>
|
||||||
|
<?php if ($shop['cover_image']): ?>
|
||||||
|
<img src="<?= htmlspecialchars($shop['cover_image']) ?>" alt="Cover" class="img-fluid rounded mb-2" style="max-height:120px; width:100%;">
|
||||||
|
<?php endif; ?>
|
||||||
|
<input type="file" class="form-control" name="cover_image" accept="image/*">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Geolocalizzazione -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Coordinate</label>
|
||||||
|
<div class="input-group mb-2">
|
||||||
|
<input type="number" step="any" class="form-control" name="latitude" id="lat"
|
||||||
|
value="<?= htmlspecialchars($shop['latitude'] ?? '') ?>" placeholder="Latitudine">
|
||||||
|
<input type="number" step="any" class="form-control" name="longitude" id="lng"
|
||||||
|
value="<?= htmlspecialchars($shop['longitude'] ?? '') ?>" placeholder="Longitudine">
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-outline-info w-100" id="geocodeBtn">
|
||||||
|
<i class="bx bx-current-location"></i> Geolocalizza indirizzo
|
||||||
|
</button>
|
||||||
|
<small class="form-text text-muted mt-1 d-block">
|
||||||
|
Clicca per calcolare lat/lon da indirizzo (usa OpenStreetMap)
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mappa -->
|
||||||
|
<div id="map"></div>
|
||||||
|
|
||||||
|
<div class="form-check mt-4">
|
||||||
|
<input class="form-check-input" type="checkbox" name="active" id="active" <?= $shop['active'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label fw-bold" for="active">
|
||||||
|
Salone attivo e visibile online
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mt-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg">Salva Modifiche</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Inizializza mappa
|
||||||
|
var map = L.map('map').setView([45.4642, 9.1900], 13); // default Milano
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
var marker = null;
|
||||||
|
|
||||||
|
<?php if ($shop['latitude'] && $shop['longitude']): ?>
|
||||||
|
var initLat = <?= (float)$shop['latitude'] ?>;
|
||||||
|
var initLng = <?= (float)$shop['longitude'] ?>;
|
||||||
|
map.setView([initLat, initLng], 15);
|
||||||
|
marker = L.marker([initLat, initLng]).addTo(map);
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
// Click su mappa → aggiorna coordinate
|
||||||
|
map.on('click', function(e) {
|
||||||
|
document.getElementById('lat').value = e.latlng.lat.toFixed(8);
|
||||||
|
document.getElementById('lng').value = e.latlng.lng.toFixed(8);
|
||||||
|
if (marker) map.removeLayer(marker);
|
||||||
|
marker = L.marker(e.latlng).addTo(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bottone Geolocalizza
|
||||||
|
document.getElementById('geocodeBtn').addEventListener('click', function() {
|
||||||
|
const addr = document.querySelector('[name="address"]').value.trim();
|
||||||
|
const extra = document.querySelector('[name="address_extra"]').value.trim();
|
||||||
|
const zip = document.querySelector('[name="zip_code"]').value.trim();
|
||||||
|
const city = document.querySelector('[name="city"]').value.trim();
|
||||||
|
const prov = document.querySelector('[name="province"]').value.trim().toUpperCase();
|
||||||
|
|
||||||
|
if (!addr || !city) {
|
||||||
|
alert("Compila almeno Indirizzo e Città.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const full = [addr, extra, zip, city, prov ? prov : '', 'Italia'].filter(Boolean).join(', ');
|
||||||
|
|
||||||
|
fetch('', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: 'action=geocode&address=' + encodeURIComponent(full)
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
document.getElementById('lat').value = data.lat;
|
||||||
|
document.getElementById('lng').value = data.lon;
|
||||||
|
map.setView([data.lat, data.lon], 15);
|
||||||
|
if (marker) map.removeLayer(marker);
|
||||||
|
marker = L.marker([data.lat, data.lon]).addTo(map);
|
||||||
|
alert("Trovato!\nLat: " + data.lat + "\nLon: " + data.lon);
|
||||||
|
} else {
|
||||||
|
alert("Non trovato: " + (data.message || "Indirizzo non riconosciuto"));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => alert("Errore: " + err));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Gestione AJAX geocode (in fondo alla pagina)
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'geocode') {
|
||||||
|
$addr = trim($_POST['address'] ?? '');
|
||||||
|
if ($addr === '') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Indirizzo vuoto']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = geocodeAddress($addr);
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode($result);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
?>
|
||||||
367
public/userarea/salon_settings.php
Normal file
367
public/userarea/salon_settings.php
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
<?php
|
||||||
|
// Forza la visualizzazione degli errori (solo dev)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Connessione DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Verifica utente loggato
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se esiste almeno un salone
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
if ((int)$stmt->fetchColumn() === 0) {
|
||||||
|
header("Location: onboarding_salon.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prendi il primo salone
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name
|
||||||
|
FROM shops
|
||||||
|
WHERE owner_id = ?
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$shop) {
|
||||||
|
die("Errore: salone non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$shop_id = (int)$shop['id'];
|
||||||
|
$shop_name = $shop['name'];
|
||||||
|
|
||||||
|
// Helpers flash
|
||||||
|
function setFlash(string $type, string $text): void
|
||||||
|
{
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlash(): ?array
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST - Salva impostazioni
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
try {
|
||||||
|
$show_prices_online = isset($_POST['show_prices_online']) ? 1 : 0;
|
||||||
|
$restrict_start_minutes = trim($_POST['restrict_start_minutes'] ?? '00,30');
|
||||||
|
$min_booking_notice_hours = (int)($_POST['min_booking_notice_hours'] ?? 2);
|
||||||
|
$max_booking_days_ahead = (int)($_POST['max_booking_days_ahead'] ?? 90);
|
||||||
|
|
||||||
|
// Validazione restrict_start_minutes
|
||||||
|
$valid_options = ['any', '00', '00,30', '00,15,30,45'];
|
||||||
|
if (!in_array($restrict_start_minutes, $valid_options)) {
|
||||||
|
$restrict_start_minutes = '00,30';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla esistenza
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM shop_settings WHERE shop_id = ?");
|
||||||
|
$stmt->execute([$shop_id]);
|
||||||
|
$exists = $stmt->fetchColumn() !== false;
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE shop_settings SET
|
||||||
|
show_prices_online = ?,
|
||||||
|
allowed_start_minutes = ?,
|
||||||
|
min_booking_notice_hours = ?,
|
||||||
|
max_booking_days_ahead = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE shop_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$show_prices_online,
|
||||||
|
$restrict_start_minutes,
|
||||||
|
$min_booking_notice_hours,
|
||||||
|
$max_booking_days_ahead,
|
||||||
|
$shop_id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO shop_settings (
|
||||||
|
shop_id, show_prices_online, allowed_start_minutes,
|
||||||
|
min_booking_notice_hours, max_booking_days_ahead
|
||||||
|
) VALUES (?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$shop_id,
|
||||||
|
$show_prices_online,
|
||||||
|
$restrict_start_minutes,
|
||||||
|
$min_booking_notice_hours,
|
||||||
|
$max_booking_days_ahead
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Impostazioni salvate con successo!" : "Errore durante il salvataggio.");
|
||||||
|
header("Location: salon_settings.php");
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
setFlash('danger', "Errore: " . $e->getMessage());
|
||||||
|
header("Location: salon_settings.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch impostazioni
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM shop_settings WHERE shop_id = ?");
|
||||||
|
$stmt->execute([$shop_id]);
|
||||||
|
$settings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [
|
||||||
|
'show_prices_online' => 1,
|
||||||
|
'allowed_start_minutes' => '00,30',
|
||||||
|
'min_booking_notice_hours' => 2,
|
||||||
|
'max_booking_days_ahead' => 90
|
||||||
|
];
|
||||||
|
|
||||||
|
$flash = getFlash();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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'); ?>
|
||||||
|
<title>Impostazioni Salone - <?= htmlspecialchars($shop_name) ?></title>
|
||||||
|
<style>
|
||||||
|
.settings-card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1.8rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-box {
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-slots {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-slot {
|
||||||
|
background: #e5e7eb;
|
||||||
|
color: #374151;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-slot.allowed {
|
||||||
|
background: #d1fae5;
|
||||||
|
color: #065f46;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
border: none;
|
||||||
|
padding: 1rem 2.5rem;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
|
||||||
|
}
|
||||||
|
</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 settings-card">
|
||||||
|
<div class="settings-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Impostazioni Salone</h5>
|
||||||
|
<a href="salon_dashboard.php" class="btn btn-light btn-sm px-4">
|
||||||
|
<i class="bx bx-arrow-back me-2"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= $flash['type'] ?> alert-dismissible fade show mb-4" role="alert">
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<!-- Visibilità prezzi -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
|
<label class="form-label mb-0" for="showPrices">Mostra i prezzi online ai clienti</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input" type="checkbox" name="show_prices_online" id="showPrices" <?= $settings['show_prices_online'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-text">
|
||||||
|
I clienti vedranno i prezzi durante la prenotazione online.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Orari inizio -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="restrictSelect">Orari di inizio appuntamenti permessi</label>
|
||||||
|
<select class="form-select form-select-lg" name="restrict_start_minutes" id="restrictSelect">
|
||||||
|
<option value="any" <?= $settings['allowed_start_minutes'] === 'any' ? 'selected' : '' ?>>
|
||||||
|
Qualsiasi minuto (libero)
|
||||||
|
</option>
|
||||||
|
<option value="00" <?= $settings['allowed_start_minutes'] === '00' ? 'selected' : '' ?>>
|
||||||
|
Solo ore piene (:00)
|
||||||
|
</option>
|
||||||
|
<option value="00,30" <?= $settings['allowed_start_minutes'] === '00,30' ? 'selected' : '' ?>>
|
||||||
|
Solo :00 e :30 (ogni mezz'ora piena)
|
||||||
|
</option>
|
||||||
|
<option value="00,15,30,45" <?= $settings['allowed_start_minutes'] === '00,15,30,45' ? 'selected' : '' ?>>
|
||||||
|
Ogni 15 minuti (:00, :15, :30, :45)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="preview-box">
|
||||||
|
<strong>Anteprima slot (es. dalle 9:00):</strong>
|
||||||
|
<div class="preview-slots" id="previewSlots"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Limiti temporali -->
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-6 form-group">
|
||||||
|
<label class="form-label" for="minNotice">Preavviso minimo prenotazione (ore)</label>
|
||||||
|
<input type="number" class="form-control form-control-lg" name="min_booking_notice_hours" id="minNotice" min="0" max="72"
|
||||||
|
value="<?= (int)$settings['min_booking_notice_hours'] ?>">
|
||||||
|
<div class="form-text">Es: 2 = non prenotabile per oggi se mancano meno di 2 ore.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 form-group">
|
||||||
|
<label class="form-label" for="maxDays">Giorni massimi in anticipo</label>
|
||||||
|
<input type="number" class="form-control form-control-lg" name="max_booking_days_ahead" id="maxDays" min="7" max="365"
|
||||||
|
value="<?= (int)$settings['max_booking_days_ahead'] ?>">
|
||||||
|
<div class="form-text">Es: 90 = massimo 90 giorni da oggi.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid mt-5">
|
||||||
|
<button type="submit" class="btn btn-save">
|
||||||
|
<i class="bx bx-save me-2"></i> Salva Impostazioni
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
const restrictSelect = document.getElementById('restrictSelect');
|
||||||
|
const previewSlots = document.getElementById('previewSlots');
|
||||||
|
|
||||||
|
function updatePreview() {
|
||||||
|
const value = restrictSelect.value;
|
||||||
|
previewSlots.innerHTML = '';
|
||||||
|
|
||||||
|
const examples = [{
|
||||||
|
val: 'any',
|
||||||
|
slots: ['9:00', '9:05', '9:10', '9:15', '9:20', '...']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: '00',
|
||||||
|
slots: ['9:00', '10:00', '11:00', '12:00']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: '00,30',
|
||||||
|
slots: ['9:00', '9:30', '10:00', '10:30', '11:00']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
val: '00,15,30,45',
|
||||||
|
slots: ['9:00', '9:15', '9:30', '9:45', '10:00']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const selected = examples.find(e => e.val === value) || examples[2];
|
||||||
|
|
||||||
|
selected.slots.forEach(slot => {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.className = 'preview-slot' + (value !== 'any' ? ' allowed' : '');
|
||||||
|
span.textContent = slot;
|
||||||
|
previewSlots.appendChild(span);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restrictSelect.addEventListener('change', updatePreview);
|
||||||
|
updatePreview(); // init
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
577
public/userarea/services.php
Normal file
577
public/userarea/services.php
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
<?php
|
||||||
|
// Forza la visualizzazione degli errori (solo dev)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Connessione DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Verifica utente loggato
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se esiste almeno un salone
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
if ((int)$stmt->fetchColumn() === 0) {
|
||||||
|
header("Location: onboarding_salon.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prendi il primo salone (o quello attivo)
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name
|
||||||
|
FROM shops
|
||||||
|
WHERE owner_id = ?
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$shop) {
|
||||||
|
die("Errore: salone non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$shop_id = (int)$shop['id'];
|
||||||
|
$shop_name = $shop['name'];
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helpers
|
||||||
|
// ===========================
|
||||||
|
function setFlash(string $type, string $text): void
|
||||||
|
{
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
function getFlash(): ?array
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slug semplice e robusto (senza intl)
|
||||||
|
function makeSlug(string $str): string
|
||||||
|
{
|
||||||
|
$str = trim($str);
|
||||||
|
$str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str);
|
||||||
|
$str = strtolower($str);
|
||||||
|
$str = preg_replace('/[^a-z0-9]+/', '-', $str);
|
||||||
|
$str = trim($str, '-');
|
||||||
|
return $str ?: 'servizio';
|
||||||
|
}
|
||||||
|
|
||||||
|
// assicura slug unico per shop
|
||||||
|
function uniqueSlug(PDO $pdo, int $shop_id, string $baseSlug, int $excludeId = 0): string
|
||||||
|
{
|
||||||
|
$slug = $baseSlug;
|
||||||
|
$i = 1;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if ($excludeId > 0) {
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM services WHERE shop_id = ? AND slug = ? AND id != ?");
|
||||||
|
$stmt->execute([$shop_id, $slug, $excludeId]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM services WHERE shop_id = ? AND slug = ?");
|
||||||
|
$stmt->execute([$shop_id, $slug]);
|
||||||
|
}
|
||||||
|
if ((int)$stmt->fetchColumn() === 0) break;
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
$slug = $baseSlug . '-' . $i;
|
||||||
|
}
|
||||||
|
return $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampInt($val, int $min, int $max, int $fallback): int
|
||||||
|
{
|
||||||
|
if ($val === null || $val === '') return $fallback;
|
||||||
|
if (!is_numeric($val)) return $fallback;
|
||||||
|
$n = (int)$val;
|
||||||
|
if ($n < $min) return $min;
|
||||||
|
if ($n > $max) return $max;
|
||||||
|
return $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMoney($val): ?string
|
||||||
|
{
|
||||||
|
if ($val === null) return null;
|
||||||
|
$val = trim((string)$val);
|
||||||
|
if ($val === '') return null;
|
||||||
|
$val = str_replace(',', '.', $val);
|
||||||
|
if (!preg_match('/^\d+(\.\d{1,2})?$/', $val)) return null;
|
||||||
|
return $val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// POST actions (add/edit/delete)
|
||||||
|
// ===========================
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'add' || $action === 'edit') {
|
||||||
|
$id = ($action === 'edit') ? (int)($_POST['id'] ?? 0) : 0;
|
||||||
|
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$description = trim($_POST['description'] ?? '');
|
||||||
|
$duration = clampInt($_POST['duration_minutes'] ?? null, 5, 600, 30);
|
||||||
|
$price = normalizeMoney($_POST['price'] ?? null);
|
||||||
|
$price_max = normalizeMoney($_POST['price_max'] ?? null);
|
||||||
|
$category = trim($_POST['category'] ?? '');
|
||||||
|
$color_hex = trim($_POST['color_hex'] ?? '');
|
||||||
|
$order = clampInt($_POST['order'] ?? null, 0, 9999, 0);
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Validazioni base
|
||||||
|
if ($name === '') {
|
||||||
|
setFlash('danger', "Il nome del servizio è obbligatorio.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($price === null) {
|
||||||
|
setFlash('danger', "Prezzo non valido (usa es. 20 o 20.00).");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($price_max !== null && (float)$price_max < (float)$price) {
|
||||||
|
setFlash('danger', "Il prezzo massimo non può essere inferiore al prezzo.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($color_hex !== '' && !preg_match('/^#[0-9A-Fa-f]{6}$/', $color_hex)) {
|
||||||
|
setFlash('danger', "Colore non valido. Usa formato tipo #FFAA00.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slug
|
||||||
|
$baseSlug = makeSlug($name);
|
||||||
|
$slug = uniqueSlug($pdo, $shop_id, $baseSlug, $id);
|
||||||
|
|
||||||
|
if ($action === 'add') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO services
|
||||||
|
(shop_id, name, slug, description, duration_minutes, price, price_max, category, is_active, color_hex, `order`)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$shop_id,
|
||||||
|
$name,
|
||||||
|
$slug,
|
||||||
|
$description !== '' ? $description : null,
|
||||||
|
$duration,
|
||||||
|
$price,
|
||||||
|
$price_max,
|
||||||
|
$category !== '' ? $category : null,
|
||||||
|
$is_active,
|
||||||
|
$color_hex !== '' ? $color_hex : null,
|
||||||
|
$order
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Servizio aggiunto!" : "Errore durante l'aggiunta.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE services
|
||||||
|
SET name = ?, slug = ?, description = ?, duration_minutes = ?, price = ?, price_max = ?,
|
||||||
|
category = ?, is_active = ?, color_hex = ?, `order` = ?, updated_at = NOW()
|
||||||
|
WHERE id = ? AND shop_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$name,
|
||||||
|
$slug,
|
||||||
|
$description !== '' ? $description : null,
|
||||||
|
$duration,
|
||||||
|
$price,
|
||||||
|
$price_max,
|
||||||
|
$category !== '' ? $category : null,
|
||||||
|
$is_active,
|
||||||
|
$color_hex !== '' ? $color_hex : null,
|
||||||
|
$order,
|
||||||
|
$id,
|
||||||
|
$shop_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Servizio aggiornato!" : "Errore durante l'aggiornamento.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM services WHERE id = ? AND shop_id = ?");
|
||||||
|
$ok = $stmt->execute([$id, $shop_id]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Servizio eliminato!" : "Errore durante l'eliminazione.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash('danger', "Azione non valida.");
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
setFlash('danger', "Errore: " . $e->getMessage());
|
||||||
|
header("Location: services.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Fetch services
|
||||||
|
// ===========================
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name, slug, description, duration_minutes, price, price_max, category, is_active, color_hex, `order`
|
||||||
|
FROM services
|
||||||
|
WHERE shop_id = ?
|
||||||
|
ORDER BY `order` ASC, name ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$shop_id]);
|
||||||
|
$services = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$flash = getFlash();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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'); ?>
|
||||||
|
<title>Servizi - <?= htmlspecialchars($shop_name) ?></title>
|
||||||
|
</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 bg-light d-flex align-items-center justify-content-between">
|
||||||
|
<h6 class="mb-0">Servizi - <?= htmlspecialchars($shop_name) ?></h6>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addServiceModal">
|
||||||
|
<i class="bx bx-plus me-1"></i> Aggiungi Servizio
|
||||||
|
</button>
|
||||||
|
<a href="salon_dashboard.php" class="btn btn-outline-secondary ms-2">
|
||||||
|
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show" role="alert">
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($services)): ?>
|
||||||
|
<div class="alert alert-info text-center py-4">
|
||||||
|
Non hai ancora creato servizi.<br>
|
||||||
|
Aggiungine uno per renderlo prenotabile.
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="servicesTable" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Ordine</th>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Durata</th>
|
||||||
|
<th>Prezzo</th>
|
||||||
|
<th>Categoria</th>
|
||||||
|
<th>Attivo</th>
|
||||||
|
<th>Colore</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($services as $s): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= (int)$s['order'] ?></td>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold"><?= htmlspecialchars($s['name']) ?></div>
|
||||||
|
<?php if (!empty($s['description'])): ?>
|
||||||
|
<div class="text-muted small"><?= htmlspecialchars($s['description']) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= (int)$s['duration_minutes'] ?> min</td>
|
||||||
|
<td>
|
||||||
|
€ <?= htmlspecialchars(number_format((float)$s['price'], 2, ',', '.')) ?>
|
||||||
|
<?php if ($s['price_max'] !== null && (float)$s['price_max'] > (float)$s['price']): ?>
|
||||||
|
<span class="text-muted">– € <?= htmlspecialchars(number_format((float)$s['price_max'], 2, ',', '.')) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($s['category'] ?? '-') ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ((int)$s['is_active'] === 1): ?>
|
||||||
|
<span class="badge bg-success">Sì</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($s['color_hex'])): ?>
|
||||||
|
<span class="badge" style="background: <?= htmlspecialchars($s['color_hex']) ?>;">
|
||||||
|
<?= htmlspecialchars($s['color_hex']) ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-warning me-1"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editServiceModal"
|
||||||
|
onclick='fillEditServiceModal(<?= json_encode([
|
||||||
|
"id" => (int)$s["id"],
|
||||||
|
"name" => $s["name"] ?? "",
|
||||||
|
"description" => $s["description"] ?? "",
|
||||||
|
"duration_minutes" => (int)$s["duration_minutes"],
|
||||||
|
"price" => (string)$s["price"],
|
||||||
|
"price_max" => $s["price_max"] !== null ? (string)$s["price_max"] : "",
|
||||||
|
"category" => $s["category"] ?? "",
|
||||||
|
"is_active" => (int)$s["is_active"],
|
||||||
|
"color_hex" => $s["color_hex"] ?? "",
|
||||||
|
"order" => (int)$s["order"],
|
||||||
|
], JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
|
||||||
|
<i class="bx bx-edit"></i> Modifica
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form action="" method="POST" style="display:inline;"
|
||||||
|
onsubmit="return confirm('Confermi l\'eliminazione del servizio?');">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="id" value="<?= (int)$s['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger">
|
||||||
|
<i class="bx bx-trash"></i> Elimina
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Aggiungi -->
|
||||||
|
<div class="modal fade" id="addServiceModal" tabindex="-1" aria-labelledby="addServiceModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title" id="addServiceModalLabel">Aggiungi Servizio</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="name" required placeholder="Es: Taglio Donna">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Durata (min) <span class="text-danger">*</span></label>
|
||||||
|
<input type="number" class="form-control" name="duration_minutes" min="5" max="600" value="30" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Descrizione</label>
|
||||||
|
<textarea class="form-control" name="description" rows="3" placeholder="Dettagli del servizio..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Prezzo (€) <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="price" required placeholder="Es: 25.00">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Prezzo max (€)</label>
|
||||||
|
<input type="text" class="form-control" name="price_max" placeholder="Es: 35.00">
|
||||||
|
<div class="form-text">Usalo solo se il prezzo può variare.</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Categoria</label>
|
||||||
|
<input type="text" class="form-control" name="category" placeholder="Es: Donna, Uomo, Colore">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Colore (hex)</label>
|
||||||
|
<input type="text" class="form-control" name="color_hex" placeholder="#FFAA00">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Ordine</label>
|
||||||
|
<input type="number" class="form-control" name="order" min="0" max="9999" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3 d-flex align-items-center">
|
||||||
|
<div class="form-check mt-3">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="add_is_active" checked>
|
||||||
|
<label class="form-check-label fw-bold" for="add_is_active">
|
||||||
|
Attivo (prenotabile)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Aggiungi</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Modifica -->
|
||||||
|
<div class="modal fade" id="editServiceModal" tabindex="-1" aria-labelledby="editServiceModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning text-dark">
|
||||||
|
<h5 class="modal-title" id="editServiceModalLabel">Modifica Servizio</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="name" id="edit_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Durata (min) <span class="text-danger">*</span></label>
|
||||||
|
<input type="number" class="form-control" name="duration_minutes" id="edit_duration" min="5" max="600" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Descrizione</label>
|
||||||
|
<textarea class="form-control" name="description" id="edit_description" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Prezzo (€) <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="price" id="edit_price" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Prezzo max (€)</label>
|
||||||
|
<input type="text" class="form-control" name="price_max" id="edit_price_max">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Categoria</label>
|
||||||
|
<input type="text" class="form-control" name="category" id="edit_category">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Colore (hex)</label>
|
||||||
|
<input type="text" class="form-control" name="color_hex" id="edit_color_hex">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Ordine</label>
|
||||||
|
<input type="number" class="form-control" name="order" id="edit_order" min="0" max="9999">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3 d-flex align-items-center">
|
||||||
|
<div class="form-check mt-3">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active">
|
||||||
|
<label class="form-check-label fw-bold" for="edit_is_active">
|
||||||
|
Attivo (prenotabile)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-secondary mb-0">
|
||||||
|
Lo slug viene generato automaticamente dal nome e reso univoco per il salone.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-warning">Salva Modifiche</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#servicesTable').DataTable({
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
|
||||||
|
},
|
||||||
|
order: [
|
||||||
|
[0, 'asc'],
|
||||||
|
[1, 'asc']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function fillEditServiceModal(data) {
|
||||||
|
document.getElementById('edit_id').value = data.id;
|
||||||
|
document.getElementById('edit_name').value = data.name || '';
|
||||||
|
document.getElementById('edit_description').value = data.description || '';
|
||||||
|
document.getElementById('edit_duration').value = data.duration_minutes || 30;
|
||||||
|
document.getElementById('edit_price').value = data.price || '';
|
||||||
|
document.getElementById('edit_price_max').value = data.price_max || '';
|
||||||
|
document.getElementById('edit_category').value = data.category || '';
|
||||||
|
document.getElementById('edit_color_hex').value = data.color_hex || '';
|
||||||
|
document.getElementById('edit_order').value = data.order || 0;
|
||||||
|
|
||||||
|
document.getElementById('edit_is_active').checked = (parseInt(data.is_active, 10) === 1);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
547
public/userarea/staff.php
Normal file
547
public/userarea/staff.php
Normal file
@ -0,0 +1,547 @@
|
|||||||
|
<?php
|
||||||
|
// Forza la visualizzazione degli errori (solo dev)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Connessione DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Verifica utente loggato
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se esiste almeno un salone
|
||||||
|
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
if ((int)$stmt->fetchColumn() === 0) {
|
||||||
|
header("Location: onboarding_salon.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prendi il primo salone
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, name
|
||||||
|
FROM shops
|
||||||
|
WHERE owner_id = ?
|
||||||
|
ORDER BY created_at ASC
|
||||||
|
LIMIT 1
|
||||||
|
");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if (!$shop) {
|
||||||
|
die("Errore: salone non trovato.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$shop_id = (int)$shop['id'];
|
||||||
|
$shop_name = $shop['name'];
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helpers (flash, validazioni)
|
||||||
|
// ===========================
|
||||||
|
function setFlash(string $type, string $text): void
|
||||||
|
{
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlash(): ?array
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampInt($val, int $min, int $max, int $fallback): int
|
||||||
|
{
|
||||||
|
if ($val === null || $val === '') return $fallback;
|
||||||
|
if (!is_numeric($val)) return $fallback;
|
||||||
|
$n = (int)$val;
|
||||||
|
if ($n < $min) return $min;
|
||||||
|
if ($n > $max) return $max;
|
||||||
|
return $n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// POST actions (add/edit/delete)
|
||||||
|
// ===========================
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||||
|
$action = $_POST['action'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($action === 'add' || $action === 'edit') {
|
||||||
|
$id = ($action === 'edit') ? (int)($_POST['id'] ?? 0) : 0;
|
||||||
|
|
||||||
|
$first_name = trim($_POST['first_name'] ?? '');
|
||||||
|
$last_name = trim($_POST['last_name'] ?? '');
|
||||||
|
$nickname = trim($_POST['nickname'] ?? '');
|
||||||
|
$role = trim($_POST['role'] ?? '');
|
||||||
|
$phone = trim($_POST['phone'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$color_hex = trim($_POST['color_hex'] ?? '');
|
||||||
|
$max_app_day = clampInt($_POST['max_appointments_per_day'] ?? null, 1, 50, 10);
|
||||||
|
$can_book_online = isset($_POST['can_book_online']) ? 1 : 0;
|
||||||
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
$notes = trim($_POST['notes'] ?? '');
|
||||||
|
|
||||||
|
// Validazioni base
|
||||||
|
if ($first_name === '' || $last_name === '') {
|
||||||
|
setFlash('danger', "Nome e Cognome sono obbligatori.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
if ($color_hex !== '' && !preg_match('/^#[0-9A-Fa-f]{6}$/', $color_hex)) {
|
||||||
|
setFlash('danger', "Colore non valido. Usa formato #FFAA00.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'add') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO staff
|
||||||
|
(shop_id, first_name, last_name, nickname, role, phone, email, color_hex,
|
||||||
|
max_appointments_per_day, can_book_online, is_active, notes)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$shop_id,
|
||||||
|
$first_name,
|
||||||
|
$last_name,
|
||||||
|
$nickname ?: null,
|
||||||
|
$role ?: null,
|
||||||
|
$phone ?: null,
|
||||||
|
$email ?: null,
|
||||||
|
$color_hex ?: null,
|
||||||
|
$max_app_day,
|
||||||
|
$can_book_online,
|
||||||
|
$is_active,
|
||||||
|
$notes ?: null
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Collaboratore aggiunto!" : "Errore durante l'aggiunta.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE staff
|
||||||
|
SET first_name = ?, last_name = ?, nickname = ?, role = ?, phone = ?, email = ?,
|
||||||
|
color_hex = ?, max_appointments_per_day = ?, can_book_online = ?, is_active = ?,
|
||||||
|
notes = ?, updated_at = NOW()
|
||||||
|
WHERE id = ? AND shop_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$first_name,
|
||||||
|
$last_name,
|
||||||
|
$nickname ?: null,
|
||||||
|
$role ?: null,
|
||||||
|
$phone ?: null,
|
||||||
|
$email ?: null,
|
||||||
|
$color_hex ?: null,
|
||||||
|
$max_app_day,
|
||||||
|
$can_book_online,
|
||||||
|
$is_active,
|
||||||
|
$notes ?: null,
|
||||||
|
$id,
|
||||||
|
$shop_id
|
||||||
|
]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Collaboratore aggiornato!" : "Errore durante l'aggiornamento.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'delete') {
|
||||||
|
$id = (int)($_POST['id'] ?? 0);
|
||||||
|
if ($id <= 0) {
|
||||||
|
setFlash('danger', "ID non valido.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("DELETE FROM staff WHERE id = ? AND shop_id = ?");
|
||||||
|
$ok = $stmt->execute([$id, $shop_id]);
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Collaboratore eliminato!" : "Errore durante l'eliminazione.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash('danger', "Azione non valida.");
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
setFlash('danger', "Errore: " . $e->getMessage());
|
||||||
|
header("Location: staff.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Fetch staff
|
||||||
|
// ===========================
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT id, first_name, last_name, nickname, role, phone, email, color_hex,
|
||||||
|
max_appointments_per_day, can_book_online, is_active, notes
|
||||||
|
FROM staff
|
||||||
|
WHERE shop_id = ?
|
||||||
|
ORDER BY last_name ASC, first_name ASC
|
||||||
|
");
|
||||||
|
$stmt->execute([$shop_id]);
|
||||||
|
$staff_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$flash = getFlash();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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'); ?>
|
||||||
|
<title>Staff / Parrucchieri - <?= htmlspecialchars($shop_name) ?></title>
|
||||||
|
</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 bg-light d-flex align-items-center justify-content-between">
|
||||||
|
<h6 class="mb-0">Staff / Parrucchieri - <?= htmlspecialchars($shop_name) ?></h6>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addStaffModal">
|
||||||
|
<i class="bx bx-plus me-1"></i> Aggiungi Collaboratore
|
||||||
|
</button>
|
||||||
|
<a href="salon_dashboard.php" class="btn btn-outline-secondary ms-2">
|
||||||
|
<i class="bx bx-arrow-back me-1"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= htmlspecialchars($flash['type']) ?> alert-dismissible fade show" role="alert">
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($staff_list)): ?>
|
||||||
|
<div class="alert alert-info text-center py-4">
|
||||||
|
Non hai ancora aggiunto collaboratori.<br>
|
||||||
|
Aggiungine uno per gestire le prenotazioni.
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="staffTable" class="table table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Ruolo</th>
|
||||||
|
<th>Telefono / Email</th>
|
||||||
|
<th>Max app/giorno</th>
|
||||||
|
<th>Online</th>
|
||||||
|
<th>Attivo</th>
|
||||||
|
<th>Colore</th>
|
||||||
|
<th>Azioni</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($staff_list as $s): ?>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="fw-bold">
|
||||||
|
<?= htmlspecialchars($s['first_name'] . ' ' . $s['last_name']) ?>
|
||||||
|
<?php if ($s['nickname']): ?>
|
||||||
|
<small class="text-muted">(@<?= htmlspecialchars($s['nickname']) ?>)</small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td><?= htmlspecialchars($s['role'] ?: '-') ?></td>
|
||||||
|
<td>
|
||||||
|
<?= htmlspecialchars($s['phone'] ?: '-') ?><br>
|
||||||
|
<small><?= htmlspecialchars($s['email'] ?: '-') ?></small>
|
||||||
|
</td>
|
||||||
|
<td><?= (int)$s['max_appointments_per_day'] ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ((int)$s['can_book_online'] === 1): ?>
|
||||||
|
<span class="badge bg-success">Sì</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if ((int)$s['is_active'] === 1): ?>
|
||||||
|
<span class="badge bg-success">Sì</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-danger">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php if (!empty($s['color_hex'])): ?>
|
||||||
|
<span class="badge" style="background: <?= htmlspecialchars($s['color_hex']) ?>;">
|
||||||
|
<?= htmlspecialchars($s['color_hex']) ?>
|
||||||
|
</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-muted">-</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-sm btn-warning me-1"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#editStaffModal"
|
||||||
|
onclick='fillEditStaffModal(<?= json_encode([
|
||||||
|
"id" => (int)$s["id"],
|
||||||
|
"first_name" => $s["first_name"],
|
||||||
|
"last_name" => $s["last_name"],
|
||||||
|
"nickname" => $s["nickname"] ?? "",
|
||||||
|
"role" => $s["role"] ?? "",
|
||||||
|
"phone" => $s["phone"] ?? "",
|
||||||
|
"email" => $s["email"] ?? "",
|
||||||
|
"color_hex" => $s["color_hex"] ?? "",
|
||||||
|
"max_appointments_per_day" => (int)$s["max_appointments_per_day"],
|
||||||
|
"can_book_online" => (int)$s["can_book_online"],
|
||||||
|
"is_active" => (int)$s["is_active"],
|
||||||
|
"notes" => $s["notes"] ?? ""
|
||||||
|
], JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'>
|
||||||
|
<i class="bx bx-edit"></i> Modifica
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form action="" method="POST" style="display:inline;"
|
||||||
|
onsubmit="return confirm('Confermi l\'eliminazione di questo collaboratore?');">
|
||||||
|
<input type="hidden" name="action" value="delete">
|
||||||
|
<input type="hidden" name="id" value="<?= (int)$s['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger">
|
||||||
|
<i class="bx bx-trash"></i> Elimina
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Aggiungi -->
|
||||||
|
<div class="modal fade" id="addStaffModal" tabindex="-1" aria-labelledby="addStaffModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title" id="addStaffModalLabel">Aggiungi Collaboratore</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="add">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="first_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Cognome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="last_name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nickname / Nome d'arte</label>
|
||||||
|
<input type="text" class="form-control" name="nickname" placeholder="Es: Sara la Colorista">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Ruolo</label>
|
||||||
|
<input type="text" class="form-control" name="role" placeholder="Es: Parrucchiere Senior">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Telefono</label>
|
||||||
|
<input type="text" class="form-control" name="phone">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Email</label>
|
||||||
|
<input type="email" class="form-control" name="email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Colore calendario (hex)</label>
|
||||||
|
<input type="text" class="form-control" name="color_hex" placeholder="#3788d8">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Max app/giorno</label>
|
||||||
|
<input type="number" class="form-control" name="max_appointments_per_day" min="1" max="50" value="10">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3 d-flex align-items-center gap-3 mt-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="can_book_online" id="add_can_book" checked>
|
||||||
|
<label class="form-check-label" for="add_can_book">Prenotabile online</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="add_is_active" checked>
|
||||||
|
<label class="form-check-label" for="add_is_active">Attivo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Note interne</label>
|
||||||
|
<textarea class="form-control" name="notes" rows="2" placeholder="Es: solo su appuntamento, non lavora lunedì..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Aggiungi</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Modifica -->
|
||||||
|
<div class="modal fade" id="editStaffModal" tabindex="-1" aria-labelledby="editStaffModalLabel">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning text-dark">
|
||||||
|
<h5 class="modal-title" id="editStaffModalLabel">Modifica Collaboratore</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="action" value="edit">
|
||||||
|
<input type="hidden" name="id" id="edit_id">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="first_name" id="edit_first_name" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Cognome <span class="text-danger">*</span></label>
|
||||||
|
<input type="text" class="form-control" name="last_name" id="edit_last_name" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Nickname / Nome d'arte</label>
|
||||||
|
<input type="text" class="form-control" name="nickname" id="edit_nickname">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Ruolo</label>
|
||||||
|
<input type="text" class="form-control" name="role" id="edit_role">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Telefono</label>
|
||||||
|
<input type="text" class="form-control" name="phone" id="edit_phone">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">Email</label>
|
||||||
|
<input type="email" class="form-control" name="email" id="edit_email">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Colore calendario (hex)</label>
|
||||||
|
<input type="text" class="form-control" name="color_hex" id="edit_color_hex">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label class="form-label fw-bold">Max app/giorno</label>
|
||||||
|
<input type="number" class="form-control" name="max_appointments_per_day" id="edit_max_app" min="1" max="50">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-3 d-flex align-items-center gap-3 mt-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="can_book_online" id="edit_can_book">
|
||||||
|
<label class="form-check-label" for="edit_can_book">Prenotabile online</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_active" id="edit_is_active">
|
||||||
|
<label class="form-check-label" for="edit_is_active">Attivo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">Note interne</label>
|
||||||
|
<textarea class="form-control" name="notes" id="edit_notes" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annulla</button>
|
||||||
|
<button type="submit" class="btn btn-warning">Salva Modifiche</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
$('#staffTable').DataTable({
|
||||||
|
language: {
|
||||||
|
url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
|
||||||
|
},
|
||||||
|
order: [
|
||||||
|
[1, 'asc']
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function fillEditStaffModal(data) {
|
||||||
|
document.getElementById('edit_id').value = data.id;
|
||||||
|
document.getElementById('edit_first_name').value = data.first_name || '';
|
||||||
|
document.getElementById('edit_last_name').value = data.last_name || '';
|
||||||
|
document.getElementById('edit_nickname').value = data.nickname || '';
|
||||||
|
document.getElementById('edit_role').value = data.role || '';
|
||||||
|
document.getElementById('edit_phone').value = data.phone || '';
|
||||||
|
document.getElementById('edit_email').value = data.email || '';
|
||||||
|
document.getElementById('edit_color_hex').value = data.color_hex || '';
|
||||||
|
document.getElementById('edit_max_app').value = data.max_appointments_per_day || 10;
|
||||||
|
document.getElementById('edit_can_book').checked = (parseInt(data.can_book_online, 10) === 1);
|
||||||
|
document.getElementById('edit_is_active').checked = (parseInt(data.is_active, 10) === 1);
|
||||||
|
document.getElementById('edit_notes').value = data.notes || '';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
349
public/userarea/user_settings.php
Normal file
349
public/userarea/user_settings.php
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
<?php
|
||||||
|
// Forza la visualizzazione degli errori (solo dev)
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
include('include/headscript.php');
|
||||||
|
|
||||||
|
// Connessione DB
|
||||||
|
$dbHandler = DBHandlerSelect::getInstance();
|
||||||
|
$pdo = $dbHandler->getConnection();
|
||||||
|
|
||||||
|
// Verifica utente loggato
|
||||||
|
if (!isset($iduserlogin)) {
|
||||||
|
header("Location: login.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// Helpers flash
|
||||||
|
// ===========================
|
||||||
|
function setFlash(string $type, string $text): void
|
||||||
|
{
|
||||||
|
$_SESSION['flash'] = ['type' => $type, 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFlash(): ?array
|
||||||
|
{
|
||||||
|
if (!isset($_SESSION['flash'])) return null;
|
||||||
|
$f = $_SESSION['flash'];
|
||||||
|
unset($_SESSION['flash']);
|
||||||
|
return $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// POST - Salva impostazioni utente
|
||||||
|
// ===========================
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
try {
|
||||||
|
$receive_newsletter = isset($_POST['receive_newsletter']) ? 1 : 0;
|
||||||
|
$receive_marketing_offers = isset($_POST['receive_marketing_offers']) ? 1 : 0;
|
||||||
|
$notify_new_appointment = isset($_POST['notify_new_appointment']) ? 1 : 0;
|
||||||
|
$notify_reminder = isset($_POST['notify_reminder']) ? 1 : 0;
|
||||||
|
$notify_cancellation = isset($_POST['notify_cancellation']) ? 1 : 0;
|
||||||
|
$notify_modification = isset($_POST['notify_modification']) ? 1 : 0;
|
||||||
|
$notify_email = isset($_POST['notify_email']) ? 1 : 0;
|
||||||
|
$notify_sms = isset($_POST['notify_sms']) ? 1 : 0;
|
||||||
|
$notify_whatsapp = isset($_POST['notify_whatsapp']) ? 1 : 0;
|
||||||
|
|
||||||
|
// Controlla esistenza riga
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM user_settings WHERE user_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$exists = $stmt->fetchColumn() !== false;
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
UPDATE user_settings SET
|
||||||
|
receive_newsletter = ?,
|
||||||
|
receive_marketing_offers = ?,
|
||||||
|
notify_new_appointment = ?,
|
||||||
|
notify_reminder = ?,
|
||||||
|
notify_cancellation = ?,
|
||||||
|
notify_modification = ?,
|
||||||
|
notify_email = ?,
|
||||||
|
notify_sms = ?,
|
||||||
|
notify_whatsapp = ?,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE user_id = ?
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$receive_newsletter,
|
||||||
|
$receive_marketing_offers,
|
||||||
|
$notify_new_appointment,
|
||||||
|
$notify_reminder,
|
||||||
|
$notify_cancellation,
|
||||||
|
$notify_modification,
|
||||||
|
$notify_email,
|
||||||
|
$notify_sms,
|
||||||
|
$notify_whatsapp,
|
||||||
|
$iduserlogin
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
INSERT INTO user_settings (
|
||||||
|
user_id, receive_newsletter, receive_marketing_offers,
|
||||||
|
notify_new_appointment, notify_reminder, notify_cancellation,
|
||||||
|
notify_modification, notify_email, notify_sms, notify_whatsapp
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
");
|
||||||
|
$ok = $stmt->execute([
|
||||||
|
$iduserlogin,
|
||||||
|
$receive_newsletter,
|
||||||
|
$receive_marketing_offers,
|
||||||
|
$notify_new_appointment,
|
||||||
|
$notify_reminder,
|
||||||
|
$notify_cancellation,
|
||||||
|
$notify_modification,
|
||||||
|
$notify_email,
|
||||||
|
$notify_sms,
|
||||||
|
$notify_whatsapp
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlash($ok ? 'success' : 'danger', $ok ? "Preferenze utente salvate!" : "Errore durante il salvataggio.");
|
||||||
|
header("Location: user_settings.php");
|
||||||
|
exit;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
setFlash('danger', "Errore: " . $e->getMessage());
|
||||||
|
header("Location: user_settings.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch impostazioni utente
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM user_settings WHERE user_id = ?");
|
||||||
|
$stmt->execute([$iduserlogin]);
|
||||||
|
$userSettings = $stmt->fetch(PDO::FETCH_ASSOC) ?: [
|
||||||
|
'receive_newsletter' => 1,
|
||||||
|
'receive_marketing_offers' => 1,
|
||||||
|
'notify_new_appointment' => 1,
|
||||||
|
'notify_reminder' => 1,
|
||||||
|
'notify_cancellation' => 1,
|
||||||
|
'notify_modification' => 1,
|
||||||
|
'notify_email' => 1,
|
||||||
|
'notify_sms' => 0,
|
||||||
|
'notify_whatsapp' => 0
|
||||||
|
];
|
||||||
|
|
||||||
|
$flash = getFlash();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!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'); ?>
|
||||||
|
<title>Le Mie Impostazioni - Notifiche & Newsletter</title>
|
||||||
|
<style>
|
||||||
|
.settings-card {
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-header {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 2.5rem 0 1.5rem;
|
||||||
|
color: #1f2937;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px;
|
||||||
|
left: 0;
|
||||||
|
width: 60px;
|
||||||
|
height: 3px;
|
||||||
|
background: #3b82f6;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1.25rem 0;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-group:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-label {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input-lg {
|
||||||
|
width: 3.2rem;
|
||||||
|
height: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input-lg:checked {
|
||||||
|
background-color: #3b82f6;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save {
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
border: none;
|
||||||
|
padding: 1.2rem 3rem;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 10px 25px rgba(16, 185, 129, 0.4);
|
||||||
|
}
|
||||||
|
</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="settings-card">
|
||||||
|
<div class="settings-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">Le Mie Impostazioni</h5>
|
||||||
|
<a href="user_dashboard.php" class="btn btn-light btn-sm px-4">
|
||||||
|
<i class="bx bx-arrow-back me-2"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<?php if ($flash): ?>
|
||||||
|
<div class="alert alert-<?= $flash['type'] ?> alert-dismissible fade show mb-5 shadow-sm" role="alert">
|
||||||
|
<i class="bx <?= $flash['type'] === 'success' ? 'bx-check-circle' : 'bx-error-circle' ?> me-2"></i>
|
||||||
|
<?= htmlspecialchars($flash['text']) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<!-- Sezione Newsletter & Marketing -->
|
||||||
|
<div class="section-title">Newsletter & Offerte</div>
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="newsletter">Ricevi newsletter e promozioni</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="receive_newsletter" id="newsletter" <?= $userSettings['receive_newsletter'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="marketing">Ricevi offerte speciali e sconti</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="receive_marketing_offers" id="marketing" <?= $userSettings['receive_marketing_offers'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sezione Notifiche Appuntamenti -->
|
||||||
|
<div class="section-title mt-5">Notifiche Appuntamenti</div>
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="newAppt">Nuova prenotazione effettuata</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="notify_new_appointment" id="newAppt" <?= $userSettings['notify_new_appointment'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="reminder">Promemoria appuntamento (24h prima)</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="notify_reminder" id="reminder" <?= $userSettings['notify_reminder'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="cancel">Cancellazione appuntamento</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="notify_cancellation" id="cancel" <?= $userSettings['notify_cancellation'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toggle-group">
|
||||||
|
<label class="toggle-label" for="modify">Modifica appuntamento (orario/servizio)</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input form-check-input-lg" type="checkbox" name="notify_modification" id="modify" <?= $userSettings['notify_modification'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sezione Canali Notifica -->
|
||||||
|
<div class="section-title mt-5">Canali di Notifica</div>
|
||||||
|
<div class="row g-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="toggle-group flex-column align-items-start">
|
||||||
|
<label class="toggle-label mb-2" for="emailNotify">Email</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_email" id="emailNotify" <?= $userSettings['notify_email'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="toggle-group flex-column align-items-start">
|
||||||
|
<label class="toggle-label mb-2" for="smsNotify">SMS</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_sms" id="smsNotify" <?= $userSettings['notify_sms'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="toggle-group flex-column align-items-start">
|
||||||
|
<label class="toggle-label mb-2" for="waNotify">WhatsApp</label>
|
||||||
|
<div class="form-check form-switch form-switch-lg">
|
||||||
|
<input class="form-check-input" type="checkbox" name="notify_whatsapp" id="waNotify" <?= $userSettings['notify_whatsapp'] ? 'checked' : '' ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pulsante salva -->
|
||||||
|
<div class="d-grid mt-5">
|
||||||
|
<button type="submit" class="btn btn-save">
|
||||||
|
<i class="bx bx-save me-2"></i> Salva Le Mie Preferenze
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('include/footer.php'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php include('jsinclude.php'); ?>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user