comelifacciamo/public/userarea/salon_dashboard.php
2026-02-01 20:37:49 +01:00

334 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// Forza la visualizzazione degli errori (solo in dev!)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include('include/headscript.php');
// Connessione DB
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
// Verifica utente loggato
if (!isset($iduserlogin)) {
die("Errore: ID utente non definito.");
}
// =========================================================================
// Controllo se l'utente ha almeno un salone
// =========================================================================
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
$stmt->execute([$iduserlogin]);
$hasShop = $stmt->fetchColumn() > 0;
if (!$hasShop) {
// Nessun salone → vai alla creazione
header("Location: onboarding_salon.php");
exit;
}
// =========================================================================
// Carichiamo il salone (prendiamo il primo creato o quello più recente)
// =========================================================================
$stmt = $pdo->prepare("
SELECT
id, name, slug, address, city, province, zip_code,
phone, email, instagram, logo, description, active
FROM shops
WHERE owner_id = ?
ORDER BY created_at ASC
LIMIT 1
");
$stmt->execute([$iduserlogin]);
$shop = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$shop) {
// Dovrebbe essere impossibile, ma per sicurezza
die("Errore: salone non trovato nonostante il conteggio dicesse di sì.");
}
$shop_id = $shop['id'];
$shop_name = $shop['name'];
// Servizi attivi
$stmt = $pdo->prepare("
SELECT id, name, duration_minutes, price, category, color_hex
FROM services
WHERE shop_id = ? AND is_active = 1
ORDER BY category, `order`, name
");
$stmt->execute([$shop_id]);
$services = $stmt->fetchAll();
// Staff attivo e visibile online
$stmt = $pdo->prepare("
SELECT s.id, s.first_name, s.last_name, s.nickname, s.color_hex,
u.avatar
FROM staff s
LEFT JOIN auth_users u ON s.user_id = u.id
WHERE s.shop_id = ? AND s.is_active = 1 AND s.can_book_online = 1
ORDER BY s.first_name, s.last_name
");
$stmt->execute([$shop_id]);
$staff_members = $stmt->fetchAll();
// Intervallo appuntamenti (default: oggi)
$start_date = $_GET['start_date'] ?? date('Y-m-d');
$end_date = $_GET['end_date'] ?? $start_date;
// Appuntamenti
$stmt = $pdo->prepare("
SELECT
a.id,
a.start_at,
a.end_at,
a.status,
a.notes,
c.first_name AS customer_first,
c.last_name AS customer_last,
c.phone AS customer_phone,
s.name AS service_name,
s.color_hex AS service_color,
st.first_name AS staff_first,
st.last_name AS staff_last,
st.color_hex AS staff_color
FROM appointments a
LEFT JOIN customers c ON a.customer_id = c.id
LEFT JOIN services s ON a.service_id = s.id
LEFT JOIN staff st ON a.staff_id = st.id
WHERE a.shop_id = ?
AND DATE(a.start_at) BETWEEN ? AND ?
ORDER BY a.start_at
");
$stmt->execute([$shop_id, $start_date, $end_date]);
$appointments = $stmt->fetchAll();
?>
<!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 href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/main.min.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?>
<div class="page-wrapper">
<div class="page-content">
<!-- Info Salone -->
<div class="card radius-10 mb-4">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap gap-4">
<div>
<img src="<?= htmlspecialchars($shop['logo'] ?: 'assets/images/default-salon.png') ?>"
alt="Logo" class="rounded-circle" style="width:100px;height:100px;object-fit:cover;">
</div>
<div class="flex-grow-1">
<h5 class="mb-1"><?= htmlspecialchars($shop_name) ?></h5>
<p class="mb-1">
<strong>Indirizzo:</strong>
<?= htmlspecialchars(implode(', ', array_filter([
$shop['address'],
$shop['city'],
$shop['zip_code'],
$shop['province']
]))) ?: '—' ?>
</p>
<p class="mb-1"><strong>Telefono:</strong> <?= htmlspecialchars($shop['phone'] ?: '—') ?></p>
<?php if ($shop['instagram']): ?>
<p><strong>IG:</strong> @<?= htmlspecialchars($shop['instagram']) ?></p>
<?php endif; ?>
</div>
<div class="d-flex gap-2 flex-wrap">
<a href="salon_profile.php" class="btn btn-warning">Modifica Salone</a>
<a href="day_off.php" class="btn btn-danger">Giorni Chiusi</a>
<a href="services.php" class="btn btn-primary">Servizi</a>
</div>
</div>
</div>
</div>
<!-- Pulsanti rapidi -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex flex-wrap justify-content-center gap-3">
<a href="customers.php" class="btn btn-primary px-4 py-3"><i class="bx bx-user me-2"></i> Clienti</a>
<a href="appointments.php" class="btn btn-success px-4 py-3"><i class="bx bx-calendar-check me-2"></i> Appuntamenti</a>
<a href="staff.php" class="btn btn-info px-4 py-3"><i class="bx bx-group me-2"></i> Staff</a>
<a href="finances.php" class="btn btn-warning px-4 py-3"><i class="bx bx-euro me-2"></i> Incassi</a>
</div>
</div>
</div>
<!-- Tabella Appuntamenti -->
<div class="card radius-10">
<div class="card-header bg-light d-flex justify-content-between align-items-center flex-wrap gap-3">
<h6 class="mb-0">
Appuntamenti <?= ($start_date == $end_date) ? date('d/m/Y', strtotime($start_date)) : "dal " . date('d/m', strtotime($start_date)) . " al " . date('d/m/Y', strtotime($end_date)) ?>
</h6>
<div class="d-flex gap-2 align-items-center flex-wrap">
<form action="" method="GET" class="d-flex gap-2 align-items-center">
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= $start_date ?>">
<span></span>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= $end_date ?>">
<button type="submit" class="btn btn-primary btn-sm">Filtra</button>
</form>
<a href="?start_date=<?= date('Y-m-d') ?>&end_date=<?= date('Y-m-d') ?>" class="btn btn-outline-secondary btn-sm">Oggi</a>
<button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#calendarModal">
<i class="bx bx-calendar"></i> Calendario
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="appTable" class="table table-striped table-hover">
<thead>
<tr>
<th>Orario</th>
<th>Cliente</th>
<th>Servizio</th>
<th>Stylist</th>
<th>Stato</th>
<th>Note</th>
<th></th>
</tr>
</thead>
<tbody>
<?php if (empty($appointments)): ?>
<tr>
<td colspan="7" class="text-center py-4 text-muted">Nessun appuntamento trovato</td>
</tr>
<?php else: ?>
<?php foreach ($appointments as $a): ?>
<tr>
<td><?= date('H:i', strtotime($a['start_at'])) ?> <?= date('H:i', strtotime($a['end_at'])) ?></td>
<td>
<strong><?= htmlspecialchars($a['customer_first'] . ' ' . $a['customer_last']) ?></strong><br>
<small><?= htmlspecialchars($a['customer_phone'] ?: '—') ?></small>
</td>
<td>
<span class="badge" style="background:<?= htmlspecialchars($a['service_color'] ?: '#6c757d') ?>">
<?= htmlspecialchars($a['service_name']) ?>
</span>
</td>
<td><?= htmlspecialchars($a['staff_first'] . ' ' . $a['staff_last']) ?></td>
<td>
<?php
$status_map = [
'pending' => ['bg-warning', 'In attesa'],
'confirmed' => ['bg-info', 'Confermato'],
'completed' => ['bg-success', 'Fatto'],
'cancelled' => ['bg-danger', 'Annullato'],
'no_show' => ['bg-secondary', 'No-show']
];
$st = $a['status'] ?? 'pending';
echo '<span class="badge ' . ($status_map[$st][0] ?? 'bg-secondary') . '">' . ($status_map[$st][1] ?? ucfirst($st)) . '</span>';
?>
</td>
<td><?= htmlspecialchars(substr($a['notes'] ?? '', 0, 50)) . (strlen($a['notes'] ?? '') > 50 ? '...' : '') ?></td>
<td>
<a href="appointment_edit.php?id=<?= $a['id'] ?>" class="btn btn-sm btn-warning"><i class="bx bx-edit"></i></a>
<!-- Aggiungi form delete se vuoi -->
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal Calendario -->
<div class="modal fade" id="calendarModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable" style="max-width:95vw;">
<div class="modal-content" style="height:90vh;">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title">Calendario Appuntamenti</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0">
<div id="calendar" style="height:100%;"></div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/main.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/locales/it.min.js"></script>
<script>
$(document).ready(function() {
$('#appTable').DataTable({
language: {
url: '//cdn.datatables.net/plug-ins/1.13.6/i18n/it-IT.json'
},
order: [
[0, 'asc']
]
});
});
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var modal = document.getElementById('calendarModal');
var cal;
function initCal() {
if (cal) cal.destroy();
cal = new FullCalendar.Calendar(calendarEl, {
locale: 'it',
initialView: 'timeGridWeek',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek'
},
height: '100%',
slotMinTime: '08:00:00',
slotMaxTime: '21:00:00',
events: <?= json_encode(array_map(function ($a) {
$title = $a['service_name'] . ' • ' . $a['customer_first'] . ' ' . substr($a['customer_last'], 0, 1) . '.';
$color = $a['service_color'] ?: '#3788d8';
return [
'title' => $title,
'start' => $a['start_at'],
'end' => $a['end_at'],
'color' => $color,
'id' => $a['id']
];
}, $appointments)) ?>,
eventClick: function(info) {
window.location = 'appointment_edit.php?id=' + info.event.id;
}
});
cal.render();
}
modal.addEventListener('shown.bs.modal', function() {
initCal();
setTimeout(() => cal.updateSize(), 200);
});
});
</script>
</body>
</html>