Init Git Prod Mars 2026

This commit is contained in:
Oleg Barykin committed 2026-03-05 10:25:39 +01:00
commit d81efe93b1
362 files changed
+86627

No files matched your search

+296
View File
@@ -0,0 +1,296 @@
<?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>