547 lines
27 KiB
PHP
547 lines
27 KiB
PHP
<?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>
|