dashboard
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
<?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,
|
||||
a.price_paid,
|
||||
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>
|
||||
<a href="new_appointment.php" class="btn btn-dark px-4 py-3"><i class="bx bx-plus-medical me-2"></i> Nuovo Appuntamento</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>
|
||||
Reference in New Issue
Block a user