Files
comelifacciamo/public/userarea/appointments.php
T

866 lines
38 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
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');
$dbHandler = DBHandlerSelect::getInstance();
$pdo = $dbHandler->getConnection();
if (!isset($iduserlogin)) {
header("Location: login.php");
exit;
}
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 e(?string $v): string
{
return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
}
// Converte un colore #RRGGBB in rgba() con opacità, per usarlo come sfondo tenue.
// Se l'hex non è valido, torna a un lilla neutro di default.
function hexToRgba(?string $hex, float $alpha): string
{
$hex = ltrim((string)$hex, '#');
if (strlen($hex) !== 6 || !ctype_xdigit($hex)) $hex = '6366f1';
$r = hexdec(substr($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
return "rgba($r, $g, $b, $alpha)";
}
function statusMeta(string $status): array
{
switch ($status) {
case 'confirmed':
return ['badge' => 'bg-success', 'label' => 'Confermato'];
case 'pending':
return ['badge' => 'bg-warning text-dark', 'label' => 'In attesa'];
case 'cancelled':
return ['badge' => 'bg-danger', 'label' => 'Annullato'];
case 'no_show':
return ['badge' => 'bg-secondary', 'label' => 'No-show'];
case 'completed':
return ['badge' => 'bg-primary', 'label' => 'Completato'];
default:
return ['badge' => 'bg-dark', 'label' => ucfirst($status)];
}
}
// --- Salone dell'owner ---
$stmt = $pdo->prepare("SELECT COUNT(*) FROM shops WHERE owner_id = ?");
$stmt->execute([$iduserlogin]);
if ((int)$stmt->fetchColumn() === 0) {
header("Location: onboarding_salon.php");
exit;
}
$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'];
$selected_date = $_GET['date'] ?? date('Y-m-d');
$selected_staff = isset($_GET['staff_id']) ? (int)$_GET['staff_id'] : 0;
// --- Staff prenotabili ---
$stmt = $pdo->prepare("
SELECT id, first_name, last_name, color_hex
FROM staff
WHERE shop_id = ? AND is_active = 1 AND can_book_online = 1
ORDER BY first_name, last_name
");
$stmt->execute([$shop_id]);
$staff_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
// --- Servizi ---
$stmt = $pdo->prepare("
SELECT id, name, duration_minutes, price, category, color_hex
FROM services
WHERE shop_id = ? AND is_active = 1
ORDER BY category ASC, `order` ASC, name ASC
");
$stmt->execute([$shop_id]);
$services_list = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Staff selezionato (per il fallback vista singola)
$selected_staff_obj = null;
if ($selected_staff > 0) {
foreach ($staff_list as $st) {
if ((int)$st['id'] === $selected_staff) {
$selected_staff_obj = $st;
break;
}
}
}
// --- Appuntamenti del giorno ---
$params = [$shop_id, $selected_date];
$where_staff = '';
if ($selected_staff > 0) {
$where_staff = "AND a.staff_id = ?";
$params[] = $selected_staff;
}
$stmt = $pdo->prepare("
SELECT a.id, a.staff_id, a.service_id, a.start_at, a.end_at, a.status, a.notes,
CONCAT(c.first_name, ' ', c.last_name) AS customer_name,
c.phone AS customer_phone,
s.name AS service_name, s.duration_minutes,
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) = ?
$where_staff
ORDER BY a.start_at ASC
");
$stmt->execute($params);
$appointments = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Raggruppa gli appuntamenti per staff_id (per la vista a colonne)
$apptByStaff = [];
foreach ($appointments as $a) {
$apptByStaff[(int)$a['staff_id']][] = $a;
}
$flash = getFlash();
// --- Parametri griglia oraria ---
$start_hour = 8;
$end_hour = 21;
$px_per_min = 1.4; // altezza in px per minuto → blocco alto in proporzione alla durata
$hour_px = 60 * $px_per_min;
// Colonne da mostrare: se filtro un solo staff, solo quello; altrimenti tutti
$columns = $selected_staff > 0 && $selected_staff_obj ? [$selected_staff_obj] : $staff_list;
$prev_date = date('Y-m-d', strtotime($selected_date . ' -1 day'));
$next_date = date('Y-m-d', strtotime($selected_date . ' +1 day'));
// Posizione verticale (px) di un orario dentro la griglia
function topPx(string $datetime, int $start_hour, float $px_per_min): float
{
$t = strtotime($datetime);
$mins = ((int)date('H', $t) - $start_hour) * 60 + (int)date('i', $t);
return max(0, $mins * $px_per_min);
}
?>
<!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>Appuntamenti - <?= e($shop_name) ?></title>
<style>
:root {
--card-radius: 14px;
--soft-border: #e9edf3;
--muted: #6b7280;
--bg: #f6f7fb;
}
body {
background: var(--bg);
}
.card-shell {
border-radius: var(--card-radius);
border: 1px solid var(--soft-border);
background: #fff;
overflow: hidden;
}
.card-shell .card-header {
background: linear-gradient(180deg, #fff, #fbfcff);
border-bottom: 1px solid var(--soft-border);
}
.page-title {
font-weight: 900;
letter-spacing: -0.02em;
}
.pill {
display: inline-flex;
align-items: center;
gap: .5rem;
padding: .40rem .70rem;
border-radius: 999px;
border: 1px solid var(--soft-border);
background: #fff;
font-weight: 700;
font-size: .88rem;
}
.pill .dot {
width: 10px;
height: 10px;
border-radius: 999px;
display: inline-block;
}
.datebar {
display: flex;
align-items: center;
gap: .5rem;
flex-wrap: nowrap;
}
.icon-btn {
width: 36px;
height: 36px;
border-radius: 10px;
border: 1px solid var(--soft-border);
background: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.date-input {
width: 155px;
height: 36px;
border-radius: 10px;
}
/* ===== AGENDA A COLONNE ===== */
.agenda-scroll {
overflow-x: auto;
overflow-y: hidden;
}
.agenda-grid {
display: grid;
min-width: 640px;
}
.agenda-head {
display: contents;
}
.agenda-col-head {
padding: 10px 8px;
border-bottom: 1px solid var(--soft-border);
border-right: 1px solid var(--soft-border);
background: #fbfcff;
display: flex;
align-items: center;
gap: 8px;
font-weight: 800;
font-size: .9rem;
position: sticky;
top: 0;
z-index: 3;
}
.agenda-col-head .dot {
width: 10px;
height: 10px;
border-radius: 999px;
flex: 0 0 auto;
}
.agenda-time-head {
background: #fbfcff;
border-bottom: 1px solid var(--soft-border);
border-right: 1px solid var(--soft-border);
position: sticky;
top: 0;
left: 0;
z-index: 4;
}
.agenda-body {
display: contents;
}
.time-gutter {
border-right: 1px solid var(--soft-border);
background: #fbfcff;
position: sticky;
left: 0;
z-index: 2;
}
.time-label {
font-size: 11px;
color: var(--muted);
font-weight: 800;
text-align: right;
padding: 2px 6px;
box-sizing: border-box;
}
.staff-lane {
position: relative;
border-right: 1px solid var(--soft-border);
}
.lane-lines {
position: absolute;
inset: 0;
pointer-events: none;
}
.hour-line {
position: absolute;
left: 0;
right: 0;
border-top: 1px solid var(--soft-border);
}
.half-line {
position: absolute;
left: 0;
right: 0;
border-top: 1px dashed #eef1f6;
}
/* Slot cliccabili (celle vuote) per creare appuntamento */
.new-slot {
position: absolute;
left: 2px;
right: 2px;
cursor: pointer;
border-radius: 8px;
}
.new-slot:hover {
background: rgba(37, 99, 235, .06);
outline: 1px dashed #bcd0f5;
}
.appt-block {
position: absolute;
left: 3px;
right: 3px;
border-radius: 10px;
padding: 6px 8px;
box-sizing: border-box;
border-left: 6px solid #6366f1;
border: 1px solid var(--soft-border);
box-shadow: 0 6px 18px rgba(17, 24, 39, .06);
cursor: pointer;
overflow: hidden;
transition: transform .12s ease, box-shadow .12s ease;
}
.appt-block:hover {
transform: translateY(-1px);
box-shadow: 0 10px 24px rgba(17, 24, 39, .12);
z-index: 5;
}
.appt-block .ttl {
font-weight: 800;
font-size: .82rem;
line-height: 1.1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.appt-block .sub {
color: var(--muted);
font-size: .75rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.appt-block.status-cancelled {
opacity: .5;
}
.appt-block.status-cancelled .ttl {
text-decoration: line-through;
}
.appt-block.status-no_show {
opacity: .75;
}
.appt-block.status-completed {
filter: grayscale(.25);
}
/* Nastrino diagonale d'angolo per lo stato, sempre visibile */
.appt-block {
position: absolute;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 999px;
margin-right: 4px;
vertical-align: middle;
}
.st-pending {
background: #f59e0b;
}
.st-confirmed {
background: #16a34a;
}
.st-completed {
background: #2563eb;
}
.st-no_show {
background: #6b7280;
}
.st-cancelled {
background: #dc2626;
}
.badge-mini {
display: inline-block;
font-size: .66rem;
padding: .05rem .4rem;
border-radius: 999px;
margin-top: 2px;
}
@media (max-width:576px) {
.date-input {
width: 135px;
}
}
.slot-action-legacy button {
background: #2563eb;
color: #fff;
}
</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-shell">
<div class="card-header p-3 p-md-4">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2">
<div class="d-flex align-items-center gap-2 flex-wrap">
<div class="page-title h5 mb-0">Appuntamenti • <?= date('d/m/Y', strtotime($selected_date)) ?></div>
<?php if ($selected_staff_obj): ?>
<span class="pill">
<span class="dot" style="background: <?= e($selected_staff_obj['color_hex'] ?: '#6366f1') ?>;"></span>
<?= e($selected_staff_obj['first_name'] . ' ' . $selected_staff_obj['last_name']) ?>
</span>
<?php else: ?>
<span class="pill"><span class="dot" style="background:#94a3b8;"></span> Tutti i parrucchieri</span>
<?php endif; ?>
</div>
<div class="d-flex align-items-center gap-2 flex-wrap">
<div class="datebar">
<a class="icon-btn" href="?date=<?= e($prev_date) ?>&staff_id=<?= (int)$selected_staff ?>" title="Precedente"><i class="bx bx-chevron-left"></i></a>
<div class="position-relative">
<i class="bx bx-calendar" style="position:absolute; left:10px; top:9px; color:#6b7280;"></i>
<input type="date" class="form-control form-control-sm date-input ps-5" value="<?= e($selected_date) ?>"
onchange="location.href='?date=' + this.value + '&staff_id=<?= (int)$selected_staff ?>'">
</div>
<a class="icon-btn" href="?date=<?= e($next_date) ?>&staff_id=<?= (int)$selected_staff ?>" title="Successivo"><i class="bx bx-chevron-right"></i></a>
</div>
<a href="?date=<?= e(date('Y-m-d')) ?>&staff_id=<?= (int)$selected_staff ?>" class="btn btn-outline-primary btn-sm">Oggi</a>
<select class="form-select form-select-sm" onchange="location.href='?date=<?= e($selected_date) ?>&staff_id=' + this.value">
<option value="0" <?= $selected_staff === 0 ? 'selected' : '' ?>>Tutti i parrucchieri</option>
<?php foreach ($staff_list as $st): ?>
<option value="<?= (int)$st['id'] ?>" <?= $selected_staff === (int)$st['id'] ? 'selected' : '' ?>>
<?= e($st['first_name'] . ' ' . $st['last_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="card-body p-0">
<?php if ($flash): ?>
<div class="alert alert-<?= e($flash['type']) ?> alert-dismissible fade show m-3" role="alert">
<?= e($flash['text']) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="p-3 p-md-4">
<div class="d-flex flex-wrap gap-2 align-items-center mb-3">
<span class="text-muted small me-2"><i class="bx bx-info-circle me-1"></i>Clicca uno spazio libero per creare, un appuntamento per gestirlo</span>
<span class="badge bg-success">Confermato</span>
<span class="badge bg-warning text-dark">In attesa</span>
<span class="badge bg-danger">Annullato</span>
<span class="badge bg-secondary">No-show</span>
<span class="badge bg-primary">Completato</span>
</div>
<?php if (empty($columns)): ?>
<div class="text-center py-5 text-muted">Nessun parrucchiere disponibile. Aggiungine uno per gestire l'agenda.</div>
<?php else: ?>
<?php
$nCols = count($columns);
$totalMins = ($end_hour - $start_hour) * 60;
$laneHeight = $totalMins * $px_per_min;
?>
<div class="agenda-scroll">
<div class="agenda-grid" style="grid-template-columns:56px repeat(<?= $nCols ?>, minmax(180px, 1fr));">
<!-- Intestazioni -->
<div class="agenda-time-head"></div>
<?php foreach ($columns as $col): ?>
<div class="agenda-col-head">
<span class="dot" style="background: <?= e($col['color_hex'] ?: '#6366f1') ?>;"></span>
<?= e($col['first_name'] . ' ' . $col['last_name']) ?>
</div>
<?php endforeach; ?>
<!-- Colonna orari -->
<div class="time-gutter" style="height: <?= $laneHeight ?>px; position:relative;">
<?php for ($h = $start_hour; $h <= $end_hour; $h++): ?>
<div class="time-label" style="position:absolute; top: <?= ($h - $start_hour) * $hour_px ?>px;">
<?= sprintf('%02d:00', $h) ?>
</div>
<?php endfor; ?>
</div>
<!-- Una lane per parrucchiere -->
<?php foreach ($columns as $col): ?>
<?php $cid = (int)$col['id'];
$laneAppts = $apptByStaff[$cid] ?? []; ?>
<div class="staff-lane" style="height: <?= $laneHeight ?>px;">
<!-- Linee guida orarie -->
<div class="lane-lines">
<?php for ($h = $start_hour; $h <= $end_hour; $h++): ?>
<div class="hour-line" style="top: <?= ($h - $start_hour) * $hour_px ?>px;"></div>
<?php if ($h < $end_hour): ?>
<div class="half-line" style="top: <?= ($h - $start_hour) * $hour_px + $hour_px / 2 ?>px;"></div>
<?php endif; ?>
<?php endfor; ?>
</div>
<!-- Slot vuoti cliccabili (ogni 30 min) -->
<?php
for ($h = $start_hour; $h < $end_hour; $h++):
foreach ([0, 30] as $mm):
$slotTop = (($h - $start_hour) * 60 + $mm) * $px_per_min;
$slotTime = sprintf('%02d:%02d', $h, $mm);
?>
<div class="new-slot open-new-appt"
style="top: <?= $slotTop ?>px; height: <?= 30 * $px_per_min ?>px;"
data-date="<?= e($selected_date) ?>"
data-time="<?= e($slotTime) ?>"
data-staff-id="<?= $cid ?>"></div>
<?php endforeach;
endfor; ?>
<!-- Blocchi appuntamento -->
<?php foreach ($laneAppts as $a): ?>
<?php
$top = topPx($a['start_at'], $start_hour, $px_per_min);
$durMin = max(15, (strtotime($a['end_at']) - strtotime($a['start_at'])) / 60);
$blockH = $durMin * $px_per_min;
$meta = statusMeta((string)$a['status']);
$color = $a['staff_color'] ?: '#6366f1';
$startHi = date('H:i', strtotime($a['start_at']));
$endHi = date('H:i', strtotime($a['end_at']));
// Payload per la modale dettaglio (data-* JSON-safe)
$payload = e(json_encode([
'id' => (int)$a['id'],
'service_id' => (int)$a['service_id'],
'staff_id' => (int)$a['staff_id'],
'service' => $a['service_name'] ?: 'Servizio',
'customer' => $a['customer_name'] ?: 'Cliente',
'phone' => $a['customer_phone'] ?: '',
'status' => $a['status'],
'date' => date('Y-m-d', strtotime($a['start_at'])),
'time' => $startHi,
'range' => $startHi . '–' . $endHi,
'notes' => $a['notes'] ?: '',
], JSON_UNESCAPED_UNICODE));
?>
<div class="appt-block status-<?= e($a['status']) ?> open-appt-detail"
style="top: <?= $top ?>px; height: <?= $blockH ?>px; border-left-color: <?= e($color) ?>; background: <?= hexToRgba($color, 0.14) ?>; border-color: <?= hexToRgba($color, 0.35) ?>;"
data-appt='<?= $payload ?>'>
<div class="ttl">
<span class="status-dot st-<?= e($a['status']) ?>"></span><?= e($a['service_name'] ?: 'Servizio') ?>
</div>
<div class="sub"><?= e($startHi) ?>–<?= e($endHi) ?> · <?= e($a['customer_name'] ?: 'Cliente') ?></div>
<span class="badge <?= e($meta['badge']) ?> badge-mini"><?= e($meta['label']) ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<?php include('include/footer.php'); ?>
</div>
<?php include('jsinclude.php'); ?>
<!-- ===== MODALE NUOVO APPUNTAMENTO (invariata, POST a create_appointments.php) ===== -->
<div class="modal fade" id="newApptModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Nuovo Appuntamento</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST" action="create_appointments.php">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Data *</label>
<input type="date" class="form-control" name="date" required>
</div>
<div class="mb-3">
<label class="form-label">Ora inizio *</label>
<input type="time" class="form-control" id="start_time" name="start_time" required>
</div>
<div class="mb-3">
<label class="form-label">Parrucchiere *</label>
<select class="form-select" name="staff_id" required>
<option value="">Seleziona...</option>
<?php foreach ($staff_list as $st): ?>
<option value="<?= (int)$st['id'] ?>"><?= e($st['first_name'] . ' ' . $st['last_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Servizio *</label>
<select class="form-select" id="service_id" name="service_id" required>
<option value="">Seleziona servizio...</option>
<?php
$current_category = null;
foreach ($services_list as $srv):
if ($srv['category'] && $srv['category'] !== $current_category):
if ($current_category !== null) echo '</optgroup>';
echo '<optgroup label="' . e($srv['category']) . '">';
$current_category = $srv['category'];
endif;
?>
<option value="<?= (int)$srv['id'] ?>" data-duration="<?= (int)$srv['duration_minutes'] ?>">
<?= e($srv['name']) ?> (<?= (int)$srv['duration_minutes'] ?> min - €<?= number_format($srv['price'], 2) ?>)
</option>
<?php endforeach;
if ($current_category !== null) echo '</optgroup>'; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Nome cliente *</label>
<input type="text" class="form-control" name="customer_name" placeholder="Mario Rossi" required>
</div>
<div class="mb-3">
<label class="form-label">Telefono</label>
<input type="tel" class="form-control" name="customer_phone" placeholder="+39 333 1234567">
</div>
<div class="mb-3">
<label class="form-label">Note</label>
<textarea class="form-control" name="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-primary">Crea Appuntamento</button>
</div>
</form>
</div>
</div>
</div>
<!-- ===== MODALE DETTAGLIO / GESTIONE APPUNTAMENTO ===== -->
<div class="modal fade" id="apptDetailModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="adTitle">Appuntamento</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- Riepilogo -->
<div class="mb-3">
<div class="d-flex justify-content-between"><span class="text-muted">Cliente</span><span id="adCustomer" class="fw-bold"></span></div>
<div class="d-flex justify-content-between"><span class="text-muted">Telefono</span><span id="adPhone"></span></div>
<div class="d-flex justify-content-between"><span class="text-muted">Orario</span><span id="adRange"></span></div>
<div class="d-flex justify-content-between align-items-center"><span class="text-muted">Stato</span><span id="adStatus"></span></div>
<div id="adNotesWrap" class="mt-2 d-none"><span class="text-muted">Note:</span> <span id="adNotes"></span></div>
</div>
<!-- Cambio stato -->
<label class="form-label fw-bold small text-uppercase text-muted">Cambia stato</label>
<form method="POST" action="appointment_action.php" class="d-flex flex-wrap gap-2 mb-3">
<input type="hidden" name="action" value="set_status">
<input type="hidden" name="appointment_id" id="adId_status">
<input type="hidden" name="date" id="adDate_status">
<input type="hidden" name="redirect_staff" value="<?= (int)$selected_staff ?>">
<button name="status" value="confirmed" class="btn btn-sm btn-success">Conferma</button>
<button name="status" value="completed" class="btn btn-sm btn-primary">Completato</button>
<button name="status" value="no_show" class="btn btn-sm btn-secondary">No-show</button>
<button name="status" value="cancelled" class="btn btn-sm btn-danger">Annulla</button>
</form>
<!-- Modifica -->
<details class="mb-2">
<summary class="form-label fw-bold small text-uppercase text-muted" style="cursor:pointer;">Modifica orario / servizio / staff</summary>
<form method="POST" action="appointment_action.php" class="mt-2">
<input type="hidden" name="action" value="edit">
<input type="hidden" name="appointment_id" id="adId_edit">
<input type="hidden" name="date" id="adDate_edit">
<input type="hidden" name="redirect_staff" value="<?= (int)$selected_staff ?>">
<div class="row g-2">
<div class="col-6"><input type="date" class="form-control form-control-sm" name="edit_date" id="adEditDate" required></div>
<div class="col-6"><input type="time" class="form-control form-control-sm" name="edit_time" id="adEditTime" required></div>
</div>
<div class="mt-2">
<select class="form-select form-select-sm" name="edit_staff_id" id="adEditStaff" required>
<?php foreach ($staff_list as $st): ?>
<option value="<?= (int)$st['id'] ?>"><?= e($st['first_name'] . ' ' . $st['last_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mt-2">
<select class="form-select form-select-sm" name="edit_service_id" id="adEditService" required>
<?php foreach ($services_list as $srv): ?>
<option value="<?= (int)$srv['id'] ?>"><?= e($srv['name']) ?> (<?= (int)$srv['duration_minutes'] ?> min)</option>
<?php endforeach; ?>
</select>
</div>
<button class="btn btn-sm btn-outline-primary mt-2 w-100">Salva modifiche</button>
</form>
</details>
</div>
<div class="modal-footer justify-content-between">
<form method="POST" action="appointment_action.php" onsubmit="return confirm('Eliminare definitivamente questo appuntamento?');">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="appointment_id" id="adId_delete">
<input type="hidden" name="date" id="adDate_delete">
<input type="hidden" name="redirect_staff" value="<?= (int)$selected_staff ?>">
<button class="btn btn-sm btn-outline-danger"><i class="bx bx-trash"></i> Elimina</button>
</form>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Chiudi</button>
</div>
</div>
</div>
</div>
<script>
// --- Nuovo appuntamento: click su slot vuoto ---
document.addEventListener('click', function(ev) {
var btn = ev.target.closest('.open-new-appt');
if (!btn) return;
var modalEl = document.getElementById('newApptModal');
if (!modalEl) return;
modalEl.querySelector('[name="date"]').value = btn.dataset.date || '';
modalEl.querySelector('[name="start_time"]').value = btn.dataset.time || '';
var staffSel = modalEl.querySelector('[name="staff_id"]');
if (staffSel && btn.dataset.staffId && parseInt(btn.dataset.staffId, 10) > 0) {
staffSel.value = btn.dataset.staffId;
}
if (window.bootstrap && bootstrap.Modal) bootstrap.Modal.getOrCreateInstance(modalEl).show();
});
// --- Dettaglio appuntamento: click su blocco ---
document.addEventListener('click', function(ev) {
var block = ev.target.closest('.open-appt-detail');
if (!block) return;
var d;
try {
d = JSON.parse(block.dataset.appt);
} catch (e) {
return;
}
document.getElementById('adCustomer').textContent = d.customer;
document.getElementById('adPhone').textContent = d.phone || '—';
document.getElementById('adRange').textContent = d.range;
document.getElementById('adTitle').textContent = d.service;
var statusLabels = {
pending: 'In attesa',
confirmed: 'Confermato',
cancelled: 'Annullato',
no_show: 'No-show',
completed: 'Completato'
};
document.getElementById('adStatus').textContent = statusLabels[d.status] || d.status;
var nw = document.getElementById('adNotesWrap');
if (d.notes) {
document.getElementById('adNotes').textContent = d.notes;
nw.classList.remove('d-none');
} else {
nw.classList.add('d-none');
}
// Popola gli hidden dei form
['adId_status', 'adId_edit', 'adId_delete'].forEach(function(id) {
document.getElementById(id).value = d.id;
});
['adDate_status', 'adDate_edit', 'adDate_delete'].forEach(function(id) {
document.getElementById(id).value = d.date;
});
// Precompila la sezione modifica
document.getElementById('adEditDate').value = d.date;
document.getElementById('adEditTime').value = d.time;
document.getElementById('adEditStaff').value = d.staff_id;
document.getElementById('adEditService').value = d.service_id;
var modalEl = document.getElementById('apptDetailModal');
if (window.bootstrap && bootstrap.Modal) bootstrap.Modal.getOrCreateInstance(modalEl).show();
});
// --- Auto-calcolo (invariato) ---
document.addEventListener('DOMContentLoaded', function() {
var serviceSelect = document.getElementById('service_id');
var startTimeInput = document.getElementById('start_time');
if (serviceSelect && startTimeInput) {
serviceSelect.addEventListener('change', function() {
// hook lasciato per eventuale campo end_time
});
}
});
</script>
</body>
</html>