Files
maria_uslugi/public/cabinet/reserver_rdv.php
T
2026-03-05 10:25:39 +01:00

297 lines
12 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
// /public/cabinet/reserver_rdv.php
require_once __DIR__ . '/../includes/init_secretaire.php';
// Valeurs par défaut pour les modes
$c_online = $c_online ?? 'online';
$c_offline = $c_offline ?? 'offline';
// ---- Entrées ----
if (! isset($_GET['praticien_id'])) {
exit($translations['praticien_non_precise'] ?? 'Praticien non précisé.');
}
$praticien_id = (int) $_GET['praticien_id'];
$client_id = isset($_GET['client_id']) ? (int) $_GET['client_id'] : null;
$secretaire_user_id = (int) ($_SESSION['compte_id'] ?? 0);
if ($secretaire_user_id <= 0 || $praticien_id <= 0) {http_response_code(403);exit('Forbidden');}
// ---- Sécurité pivot secrétaire → praticien ----
$chk = $pdo->prepare("
SELECT can_manage_rdv
FROM secretaire_praticien
WHERE secretaire_user_id = :u
AND praticien_id = :p
AND is_active = 1
LIMIT 1
");
$chk->execute([':u' => $secretaire_user_id, ':p' => $praticien_id]);
$pivot = $chk->fetch(PDO::FETCH_ASSOC);
if (! $pivot) {http_response_code(403);exit('Forbidden');}
$canManage = (int) ($pivot['can_manage_rdv'] ?? 0) === 1;
// ---- Infos praticien ----
$stmt = $pdo->prepare("SELECT nom, prenom, duree_rdv, adresse, ville, code_postal FROM praticien WHERE id = ?");
$stmt->execute([$praticien_id]);
$praticien = $stmt->fetch(PDO::FETCH_ASSOC);
$duree_minutes = (int) ($praticien['duree_rdv'] ?? 0);
if ($duree_minutes <= 0) {
die($translations['duree_rdv_non_definie'] ?? "Durée de RDV non définie.");
}
// Adresse complète ?
$has_address =
! empty(trim((string) ($praticien['adresse'] ?? ''))) &&
! empty(trim((string) ($praticien['ville'] ?? ''))) &&
! empty(trim((string) ($praticien['code_postal'] ?? '')));
// ---- Dispos praticien ----
$stmt = $pdo->prepare("SELECT jour_semaine, heure_debut, heure_fin FROM disponibilite WHERE praticien_id = ?");
$stmt->execute([$praticien_id]);
$dispos = $stmt->fetchAll(PDO::FETCH_ASSOC);
// ---- RDV réservés (prochains 21 jours) ----
$tz = new DateTimeZone('Europe/Paris');
$now = new DateTimeImmutable('now', $tz);
$limit = $now->modify('+60 days');
$stmt = $pdo->prepare("SELECT date_rdv FROM rendez_vous WHERE praticien_id = ? AND date_rdv BETWEEN ? AND ?");
$stmt->execute([$praticien_id, $now->format('Y-m-d H:i:s'), $limit->format('Y-m-d H:i:s')]);
$reservations = array_map(
fn($r) => (new DateTimeImmutable($r['date_rdv'], $tz))->format('Y-m-d H:i'),
$stmt->fetchAll(PDO::FETCH_ASSOC)
);
// ---- i18n jours ----
$DAY_I18N = [
'monday' => 'jour_lundi',
'tuesday' => 'jour_mardi',
'wednesday' => 'jour_mercredi',
'thursday' => 'jour_jeudi',
'friday' => 'jour_vendredi',
'saturday' => 'jour_samedi',
'sunday' => 'jour_dimanche',
];
function normalize_day_key(string $label): ?string
{
$l = mb_strtolower(trim($label));
static $map = [
// EN
'monday' => 'monday', 'tuesday' => 'tuesday', 'wednesday' => 'wednesday',
'thursday' => 'thursday', 'friday' => 'friday', 'saturday' => 'saturday', 'sunday' => 'sunday',
// FR
'lundi' => 'monday', 'mardi' => 'tuesday', 'mercredi' => 'wednesday',
'jeudi' => 'thursday', 'vendredi' => 'friday', 'samedi' => 'saturday', 'dimanche' => 'sunday',
// RU
'понедельник' => 'monday', 'вторник' => 'tuesday', 'среда' => 'wednesday',
'четверг' => 'thursday', 'пятница' => 'friday', 'суббота' => 'saturday', 'воскресенье' => 'sunday',
];
return $map[$l] ?? null;
}
// ---- Génération créneaux par jour ----
$creneaux_par_jour = [];
$periode = new DatePeriod($now, new DateInterval('P1D'), $limit); // $limit exclusif
foreach ($periode as $jour) {
$dayKey = strtolower($jour->format('l'));
$i18nKey = $DAY_I18N[$dayKey] ?? null;
$jour_nom = $i18nKey ? ($translations[$i18nKey] ?? ucfirst($dayKey)) : ucfirst($dayKey);
foreach ($dispos as $dispo) {
$dispoKey = normalize_day_key($dispo['jour_semaine'] ?? '');
if ($dispoKey !== $dayKey) {
continue;
}
[$h1, $m1, $s1] = array_map('intval', explode(':', (string) $dispo['heure_debut']));
[$h2, $m2, $s2] = array_map('intval', explode(':', (string) $dispo['heure_fin']));
$debut_creneau = $jour->setTime($h1, $m1, $s1);
$fin_creneau = $jour->setTime($h2, $m2, $s2);
if ($fin_creneau <= $debut_creneau) {
continue;
}
$step = new DateInterval("PT{$duree_minutes}M");
for ($t = $debut_creneau; $t->add($step) <= $fin_creneau; $t = $t->add($step)) {
if ($t <= $now) {
continue;
}
$debut_str = $t->format('Y-m-d H:i');
if (! in_array($debut_str, $reservations, true)) {
$key = $jour_nom . '<br> ' . $jour->format('d/m');
$creneaux_par_jour[$key][] = $t;
}
}
}
}
// Tri horaire dans chaque colonne
foreach ($creneaux_par_jour as &$slots) {
usort(
$slots,
fn(DateTimeImmutable $a, DateTimeImmutable $b) => $a->getTimestamp() <=> $b->getTimestamp()
);
}
unset($slots);
// Pagination colonnes
$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
$limit_columns = 5;
$all_keys = array_keys($creneaux_par_jour);
$total_pages = max(1, (int) ceil(count($all_keys) / $limit_columns));
$offset = ($page - 1) * $limit_columns;
$visible_keys = array_slice($all_keys, $offset, $limit_columns);
// petit helper
if (! function_exists('h')) {function h($s)
{return htmlspecialchars((string) $s, ENT_QUOTES, 'UTF-8');}}
$PAGE_TITRE = $translations['creneaux_disponibles_title'] ?? 'Créneaux disponibles';
$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 rdv-card">
<h2 class="mb-4 text-center">
<?php echo h(($praticien['prenom'] ?? '') . ' ' . ($praticien['nom'] ?? '')) ?> - <?php echo h($translations['zapis'] ?? 'Prendre rendez-vous') ?>
<?php if (! $canManage): ?>
<small class="text-danger d-block mt-1"><?php echo h($translations['droits_insuffisants'] ?? 'Droits insuffisants pour réserver') ?></small>
<?php endif; ?>
</h3>
<?php if ($total_pages > 1): ?>
<div class="pagination mb-3">
<?php if ($page > 1): ?>
<a class="btn btn-outline-secondary btn-sm"
href="?praticien_id=<?php echo (int) $praticien_id ?>&<?php echo $client_id ? 'client_id=' . (int) $client_id . '&' : '' ?>page=<?php echo (int) ($page - 1) ?>">
&laquo; <?php echo h($translations['pagination_precedent'] ?? 'Précédent') ?>
</a>
<?php endif; ?>
<?php if ($page < $total_pages): ?>
<a class="btn btn-outline-secondary btn-sm"
href="?praticien_id=<?php echo (int) $praticien_id ?>&<?php echo $client_id ? 'client_id=' . (int) $client_id . '&' : '' ?>page=<?php echo (int) ($page + 1) ?>">
<?php echo h($translations['pagination_suivant'] ?? 'Suivant') ?> &raquo;
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (empty($creneaux_par_jour)): ?>
<p class="rdv-empty">
<?php echo h($translations['aucun_creneau_disponible_3_semaines'] ?? 'Aucun créneau disponible sur 3 semaines.') ?><br><br>
<a class="btn btn-outline-secondary btn-sm btn-table" href="/messages/start.php?praticien_id=<?php echo (int) $praticien_id ?>">
<?php echo h($translations['chat_praticien'] ?? 'Ouvrir le chat') ?>
</a>
</p>
<?php else: ?>
<div class="grid-container">
<?php foreach ($visible_keys as $jour): ?>
<div class="day-column">
<h4><?php echo $jour ?></h4>
<?php foreach ($creneaux_par_jour[$jour] as $c): ?>
<?php
$startIso = $c->format(DateTime::ATOM);
$slotName = 'mode_rdv_' . $c->format('YmdHi');
$onlineLabel = $translations['option_online'] ?? 'Онлайн';
$cabinetLabel = $translations['option_cabinet'] ?? 'В кабинете';
// URL base vers l’étape de validation (version cabinet)
$validerBase = '/public/cabinet/valider_rdv.php';
$validerHref = $validerBase
. '?praticien_id=' . (int) $praticien_id
. '&start=' . rawurlencode($startIso)
. ($client_id ? '&client_id=' . (int) $client_id : '');
?>
<div class="slot">
<div class="slot-time"><?php echo h($c->format('H:i')) ?></div>
<?php if ($has_address): ?>
<div class="mode-group my-2">
<label class="me-3">
<input type="radio" name="<?php echo h($slotName) ?>" value="<?php echo h($c_online) ?>" checked>
<?php echo h($onlineLabel) ?>
</label>
<label>
<input type="radio" name="<?php echo h($slotName) ?>" value="<?php echo h($c_offline) ?>">
<?php echo h($cabinetLabel) ?>
</label>
</div>
<?php else: ?>
<div class="mode-group my-2"><span><?php echo h($onlineLabel) ?></span></div>
<?php endif; ?>
<a href="<?php echo h($validerHref) ?>"
class="reserver-btn"
data-mode-name="<?php echo h($has_address ? $slotName : '') ?>"
data-default-mode="<?php echo h($c_online) ?>"
data-nom="<?php echo h($praticien['nom'] ?? '') ?>"
data-prenom="<?php echo h($praticien['prenom'] ?? '') ?>"
data-start="<?php echo h($startIso) ?>"
<?php echo $canManage ? '' : 'aria-disabled="true" tabindex="-1" style="pointer-events:none;opacity:.6;"' ?>
>
<?php echo h($translations['btn_reserver'] ?? 'Réserver') ?>
</a>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
document.querySelectorAll('.reserver-btn').forEach(btn => {
btn.addEventListener('click', function (e) {
e.preventDefault();
const start = this.dataset.start;
const nom = this.dataset.nom;
const prenom = this.dataset.prenom;
// Récupérer le mode choisi
const groupName = this.dataset.modeName;
let modeVal = this.dataset.defaultMode || 'online';
if (groupName) {
const checked = this.closest('.slot').querySelector(`input[name="${groupName}"]:checked`);
if (checked) modeVal = checked.value;
}
const date = new Date(start);
const jour = date.toLocaleDateString('ru-RU', { weekday: 'long', day: '2-digit', month: '2-digit' });
const heure = date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
Swal.fire({
title: <?php echo json_encode($translations['confirmation'] ?? 'Confirmation') ?>,
html: `<b>${prenom} ${nom}<br>${jour} <?php echo $translations['a'] ?? 'à' ?> ${heure}</b>`,
icon: 'question',
showCancelButton: true,
confirmButtonText: <?php echo json_encode($translations['btn_oui_reserver'] ?? 'Oui, réserver') ?>,
cancelButtonText: <?php echo json_encode($translations['btn_annuler'] ?? 'Annuler') ?>
}).then((result) => {
if (result.isConfirmed) {
const url = new URL(this.href, location.origin);
url.searchParams.set('mode_rdv', modeVal);
window.location.href = url.toString();
}
});
});
});
</script>
<footer class="site-footer">
<div class="container">
<p><?php echo $translations['copyr'] ?? '&copy; ' . date('Y') ?></p>
</div>
</footer>
</body>
</html>