Files

386 lines
13 KiB
PHP
Raw Permalink 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
declare (strict_types = 1);
// /public/cabinet/disponibilites.php
require_once __DIR__ . '/../includes/init_secretaire.php';
if (! function_exists('h')) {
function h($s): string
{
return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');
}
}
$compteId = (int) ($_SESSION['compte_id'] ?? 0);
$userRole = strtolower((string) ($_SESSION['role'] ?? ''));
$praticien_id = (int) ($_GET['praticien_id'] ?? 0);
if ($praticien_id <= 0 || $userRole !== 'secretaire') {
http_response_code(403);
exit('Forbidden');
}
// Vérifier pivot + droit
$st = $pdo->prepare("
SELECT can_edit_slots
FROM secretaire_praticien
WHERE secretaire_user_id = :u
AND praticien_id = :p
AND is_active = 1
LIMIT 1
");
$st->execute([
'u' => $compteId,
'p' => $praticien_id,
]);
$pivot = $st->fetch(PDO::FETCH_ASSOC);
if (! $pivot || (int) $pivot['can_edit_slots'] !== 1) {
http_response_code(403);
exit('Forbidden');
}
// Récup nom praticien pour le titre
$st = $pdo->prepare("SELECT prenom, nom, duree_rdv FROM praticien WHERE id = ? LIMIT 1");
$st->execute([$praticien_id]);
$praticien = $st->fetch(PDO::FETCH_ASSOC);
if (! $praticien) {
http_response_code(404);
exit('Praticien introuvable');
}
$currentPraticienName = trim(((string) ($praticien['prenom'] ?? '')) . ' ' . ((string) ($praticien['nom'] ?? '')));
$duree_minutes = (int) ($praticien['duree_rdv'] ?? 0);
if ($duree_minutes <= 0) {
$duree_minutes = 30;
}
$erreur = null;
$toMin = function (string $hhmm): ?int {
if (! preg_match('/^\d{2}:\d{2}$/', $hhmm)) {
return null;
}
[$h, $m] = array_map('intval', explode(':', $hhmm));
if ($h < 0 || $h > 23 || $m < 0 || $m > 59) {
return null;
}
return $h * 60 + $m;
};
// ---- Ajouter un créneau (POST + CSRF)
if (
$_SERVER['REQUEST_METHOD'] === 'POST'
&& ($_POST['action'] ?? '') === 'add'
&& isset($_POST['jour'], $_POST['heure_debut'], $_POST['heure_fin'])
) {
require_post_csrf();
$jour = trim((string) $_POST['jour']);
$debut = trim((string) $_POST['heure_debut']);
$fin = trim((string) $_POST['heure_fin']);
$joursValides = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'];
$md = $toMin($debut);
$mf = $toMin($fin);
if (! in_array($jour, $joursValides, true)) {
$erreur = $translations['erreur_jour_invalide'] ?? 'Jour invalide.';
} elseif ($md === null || $mf === null) {
$erreur = $translations['erreur_format_heure'] ?? "Format d'heure invalide.";
} elseif ($md >= $mf) {
$erreur = $translations['erreur_debut_avant_fin'] ?? "Heure de début après l’heure de fin.";
} else {
$window = $mf - $md;
if ($window < $duree_minutes) {
$erreur = sprintf(
$translations['erreur_plage_trop_courte'] ?? "Créneau trop court (%d min). Minimum : %d min.",
$window,
$duree_minutes
);
} elseif (($window % $duree_minutes) !== 0) {
$erreur = sprintf(
$translations['erreur_plage_incompatible_duree'] ?? "Créneau incompatible. La durée doit être un multiple de %d min.",
$duree_minutes
);
} else {
$stmt = $pdo->prepare("
INSERT INTO disponibilite (praticien_id, jour_semaine, heure_debut, heure_fin)
VALUES (:p, :j, :hd, :hf)
");
$stmt->execute([
'p' => $praticien_id,
'j' => $jour,
'hd' => $debut,
'hf' => $fin,
]);
header('Location: /public/cabinet/disponibilites.php?praticien_id=' . $praticien_id . '&ok=added', true, 303);
exit;
}
}
}
// ---- Récupérer les créneaux
$stmt = $pdo->prepare("
SELECT *
FROM disponibilite
WHERE praticien_id = ?
ORDER BY FIELD(jour_semaine,'lundi','mardi','mercredi','jeudi','vendredi','samedi','dimanche'), heure_debut
");
$stmt->execute([$praticien_id]);
$dispos = $stmt->fetchAll(PDO::FETCH_ASSOC);
$jourKeyMap = [
'lundi' => 'jour_lundi',
'mardi' => 'jour_mardi',
'mercredi' => 'jour_mercredi',
'jeudi' => 'jour_jeudi',
'vendredi' => 'jour_vendredi',
'samedi' => 'jour_samedi',
'dimanche' => 'jour_dimanche',
];
$PAGE_TITLE = $translations['titre_disponibilites'] ?? 'Disponibilités';
$HEAD_EXTRA = <<<HTML
<script src="/js/sweetalert2@11.js"></script>
HTML;
require_once __DIR__ . '/../includes/header_cabinet.php';
?>
<div class="main1">
<div class="container py-5">
<div class="shadow p-4 rounded bg-light">
<h2 class="mb-4 text-center">
<?php echo h($currentPraticienName) ?> - <?php echo h($translations['sloti'] ?? 'Mes disponibilités') ?>
</h2>
<div class="text-end">
<a href="/public/cabinet/disponibilites_date.php?praticien_id=<?php echo (int) $praticien_id ?>" class="btn btn-outline-secondary mb-4">
<?php echo h($translations['btn_ajouter_par_date'] ?? 'Ajouter par date') ?>
</a>
</div>
<div class="table-responsive mb-4">
<table class="table table-bordered table-striped align-middle text-center mobile-cards" id="table_rdv">
<thead class="table-light">
<tr>
<th><?php echo h($translations['table_jour'] ?? 'Jour') ?></th>
<th><?php echo h($translations['table_debut'] ?? 'Début') ?></th>
<th><?php echo h($translations['table_fin'] ?? 'Fin') ?></th>
<th><?php echo h($translations['table_action'] ?? 'Action') ?></th>
</tr>
</thead>
<tbody>
<?php if ($dispos): ?>
<?php foreach ($dispos as $d): ?>
<tr>
<td>
<?php
$key = $jourKeyMap[$d['jour_semaine']] ?? null;
echo h($key ? ($translations[$key] ?? ucfirst($d['jour_semaine'])) : ucfirst($d['jour_semaine']));
?>
</td>
<td><?php echo h(substr((string) $d['heure_debut'], 0, 5)) ?></td>
<td><?php echo h(substr((string) $d['heure_fin'], 0, 5)) ?></td>
<td>
<button
type="button"
class="btn btn-sm btn-danger js-del"
data-id="<?php echo (int) $d['id'] ?>">
<?php echo h($translations['btn_supprimer'] ?? 'Supprimer') ?>
</button>
</td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="4"><?php echo h($translations['aucun_creneau'] ?? 'Aucun créneau.') ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="card shadow-sm p-4">
<h4 class="mb-3 text-center"><?php echo h($translations['card_ajouter_creneau'] ?? 'Ajouter un créneau') ?></h4>
<form method="POST">
<input type="hidden" name="action" value="add">
<input type="hidden" name="csrf_token" value="<?php echo h(ensure_csrf_token()) ?>">
<div class="mb-3">
<label class="form-label" for="jour"><?php echo h($translations['label_jour'] ?? 'Jour') ?></label>
<select name="jour" id="jour" class="form-select" required>
<option value=""><?php echo h($translations['option_choisir'] ?? '— Choisir —') ?></option>
<?php
$jours = [
'lundi' => $translations['jour_lundi'] ?? 'Lundi',
'mardi' => $translations['jour_mardi'] ?? 'Mardi',
'mercredi' => $translations['jour_mercredi'] ?? 'Mercredi',
'jeudi' => $translations['jour_jeudi'] ?? 'Jeudi',
'vendredi' => $translations['jour_vendredi'] ?? 'Vendredi',
'samedi' => $translations['jour_samedi'] ?? 'Samedi',
'dimanche' => $translations['jour_dimanche'] ?? 'Dimanche',
];
foreach ($jours as $val => $label):
?>
<option value="<?php echo h($val) ?>"><?php echo h($label) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" for="heure_debut"><?php echo h($translations['label_heure_debut'] ?? 'Heure de début') ?></label>
<input type="time" name="heure_debut" id="heure_debut" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" for="heure_fin"><?php echo h($translations['label_heure_fin'] ?? 'Heure de fin') ?></label>
<input type="time" name="heure_fin" id="heure_fin" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">
<?php echo h($translations['btn_ajouter'] ?? 'Ajouter') ?>
</button>
</form>
</div>
</div>
</div>
</div>
<script>
const csrfToken = <?php echo json_encode(ensure_csrf_token(), JSON_UNESCAPED_UNICODE) ?>;
const praticienId = <?php echo (int) $praticien_id ?>;
document.addEventListener('DOMContentLoaded', function () {
const okParam = <?php echo json_encode(isset($_GET['ok']) ? (string) $_GET['ok'] : '', JSON_UNESCAPED_UNICODE) ?>;
const erreurMsg = <?php echo json_encode($erreur ?: '', JSON_UNESCAPED_UNICODE) ?>;
if (okParam === 'added') {
Swal.fire({
icon: 'success',
title: <?php echo json_encode($translations['msg_creneau_ajoute'] ?? 'Créneau ajouté.', JSON_UNESCAPED_UNICODE) ?>,
timer: 1800,
showConfirmButton: false
});
if (window.history.replaceState) {
window.history.replaceState({}, document.title, window.location.pathname + '?praticien_id=' + encodeURIComponent(praticienId));
}
}
if (erreurMsg) {
Swal.fire({
icon: 'error',
title: <?php echo json_encode($translations['erreur_titre'] ?? 'Erreur', JSON_UNESCAPED_UNICODE) ?>,
text: erreurMsg,
confirmButtonText: 'OK'
});
}
});
document.addEventListener('click', async function (e) {
const btn = e.target.closest('.js-del');
if (!btn) return;
const id = btn.dataset.id;
if (!id) return;
const result = await Swal.fire({
icon: 'warning',
title: <?php echo json_encode($translations['confirm_title'] ?? 'Confirmation', JSON_UNESCAPED_UNICODE) ?>,
text: <?php echo json_encode($translations['confirm_supprimer_creneau'] ?? 'Supprimer ce créneau ?', JSON_UNESCAPED_UNICODE) ?>,
showCancelButton: true,
confirmButtonText: <?php echo json_encode($translations['btn_da_udalit'] ?? 'Oui', JSON_UNESCAPED_UNICODE) ?>,
cancelButtonText: <?php echo json_encode($translations['btn_annuler'] ?? 'Annuler', JSON_UNESCAPED_UNICODE) ?>,
reverseButtons: true
});
if (!result.isConfirmed) return;
const formData = new FormData();
formData.append('id', String(id));
formData.append('praticien_id', String(praticienId));
formData.append('csrf_token', csrfToken);
fetch('/public/cabinet/supprimer_disponibilite.php', {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: formData
})
.then(async r => {
const text = await r.text();
try {
return JSON.parse(text);
} catch (e) {
return { ok: false, message: text };
}
})
.then(data => {
if (data.ok) {
Swal.fire({
icon: 'success',
title: <?php echo json_encode($translations['msg_creneau_supprime'] ?? 'Créneau supprimé.', JSON_UNESCAPED_UNICODE) ?>,
timer: 1400,
showConfirmButton: false
}).then(() => {
window.location.reload();
});
} else {
Swal.fire({
icon: 'error',
title: <?php echo json_encode($translations['erreur_titre'] ?? 'Erreur', JSON_UNESCAPED_UNICODE) ?>,
text: data.message || 'Suppression impossible.'
});
}
})
.catch(() => {
Swal.fire({
icon: 'error',
title: 'Erreur réseau',
text: 'Veuillez réessayer.'
});
});
});
</script>
<script defer>
(function(){
function initMobileCards(tbl){
if (!tbl) return;
const heads = Array.from(tbl.querySelectorAll('thead th')).map(th => th.textContent.trim());
tbl.querySelectorAll('tbody tr').forEach(tr => {
Array.from(tr.children).forEach((td, i) => {
if (!td.hasAttribute('data-label')) td.setAttribute('data-label', heads[i] || '');
});
});
document.documentElement.classList.add('mc-ready');
}
function ready(fn){
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn);
else fn();
}
ready(function(){
initMobileCards(document.getElementById('table_rdv'));
});
})();
</script>
<footer class="site-footer">
<div class="container">
<p><?php echo $translations['copyr'] ?? '&copy; ' . date('Y') ?></p>
</div>
</footer>
</body>
</html>