Files
2026-03-05 10:25:39 +01:00

217 lines
9.3 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
// /public/cabinet/agenda.php
require_once __DIR__ . '/../includes/init_secretaire.php';
if (!function_exists('h')) { function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } }
$compteId = (int)($_SESSION['compte_id'] ?? 0);
$lang = $_SESSION['lang'] ?? 'fr'; // 'fr' ou 'ru'
$fcLocale = ($lang === 'ru') ? 'ru' : 'fr';
// --- on attend ?praticien_id=... (ex. produit par le dashboard secrétaire)
$praticien_id = isset($_GET['praticien_id']) ? (int)$_GET['praticien_id'] : 0;
if ($compteId <= 0 || $praticien_id <= 0) { http_response_code(403); exit('Forbidden'); }
// --- vérification pivot secrétaire -> praticien
$st = $pdo->prepare("
SELECT can_manage_rdv
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) { http_response_code(403); exit('Forbidden'); }
// petit helper perm (même si on n’édite rien ici)
$canManage = (int)($pivot['can_manage_rdv'] ?? 0) === 1;
$PAGE_TITLE = $translations['titre_agenda_page'] ?? 'Agenda';
$HEAD_EXTRA = <<<HTML
<!-- FullCalendar -->
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.css" rel="stylesheet" />
<script src="/js/index.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/locales-all.global.min.js"></script>
<!-- SweetAlert2 -->
<script src="/js/sweetalert2@11.js"></script>
<link href="/js/sweetalert2.min.css" rel="stylesheet">
<style>
:root{
--card-bg:#fff; --muted:#6c757d; --brand:#38378B; --accent:#e9ecef; --text:#222; --border:#e9ecef;
}
body{ background:#8CA9FF; color:var(--text); }
.agenda-wrap{ max-width:1100px; margin:clamp(8px, 2vw, 24px) auto; padding:clamp(8px, 2vw, 24px); }
.agenda-card{ background:var(--card-bg); border:1px solid var(--border); border-radius:16px; box-shadow:0 6px 18px rgba(0,0,0,.06); padding:clamp(12px,2.4vw,28px); }
.agenda-title{ margin:0 0 12px; text-align:center; font-weight:700; letter-spacing:.2px; }
#calendar{ min-height:420px; }
.fc .fc-toolbar.fc-header-toolbar{ gap:8px; flex-wrap:wrap; }
.fc .fc-toolbar-title{ font-size:clamp(18px, 2.4vw, 22px); color:var(--brand); }
.fc .fc-button{ border-radius:999px; border:1px solid var(--border); background:#fff; color:#111; box-shadow:0 2px 6px rgba(0,0,0,.05); padding:6px 10px; }
.fc .fc-button-primary:not(:disabled).fc-button-active, .fc .fc-button-primary:not(:disabled):active, .fc .fc-button-primary:hover{
background:var(--accent); border-color:transparent; color:#000;
}
.fc .fc-col-header-cell-cushion{ color: var(--brand); font-weight:600; }
.fc .fc-timegrid-slot-label, .fc .fc-timegrid-axis-cushion{ color:var(--muted); }
.fc .fc-event{ border-radius:12px; border:0; padding:2px 4px; }
.fc .fc-daygrid-event{ padding:2px 6px; }
.empty-state{ text-align:center; color:var(--muted); padding:28px 12px 8px; font-size:15px; }
.fc .fc-timegrid-now-indicator-line{ border-color:#ff5a5f; }
@media (max-width: 768px){
.agenda-card{ max-width: 720px; border-radius:14px; }
.fc .fc-toolbar.fc-header-toolbar{ justify-content:center; }
}
</style>
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"><?= htmlspecialchars($currentPraticienName, ENT_QUOTES, 'UTF-8') ?> - <?= h($translations['raspisanie'] ?? 'Agenda') ?></h2>
<div id="calendar" role="application" aria-label="calendar"></div>
<div id="emptyMsg" class="empty-state" style="display:none;">
<?= h($translations['agenda_aucun_evenement'] ?? 'Aucun rendez-vous à afficher.') ?>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<p><?= $translations['copyr'] ?? '&copy; '.date('Y') ?></p>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
const emptyMsgEl = document.getElementById('emptyMsg');
const t = <?= json_encode([
'rdv_avec' => $translations['sw_rdv_avec'] ?? 'RDV avec',
'date' => $translations['sw_date'] ?? 'Date',
'heure' => $translations['sw_heure'] ?? 'Heure',
'statut' => $translations['sw_statut'] ?? 'Statut',
'gerer_rdv' => $translations['sw_gerer_rdv'] ?? 'Gérer le RDV',
'btn_fermer' => $translations['sw_btn_fermer'] ?? 'Fermer',
'erreur' => $translations['erreur_chargement_rdv'] ?? 'Erreur de chargement',
'statut_confirme' => $translations['statut_confirme'] ?? 'Confirmé',
'statut_prevu' => $translations['statut_prevu'] ?? 'Prévu',
'statut_annule' => $translations['statut_annule'] ?? 'Annulé',
], JSON_UNESCAPED_UNICODE) ?>;
const fcLocale = <?= json_encode($fcLocale) ?>;
const praticienId = <?= (int)$praticien_id ?>;
const isMobile = window.matchMedia('(max-width: 768px)').matches;
const initialView = isMobile ? 'listWeek' : 'timeGridWeek';
const calendar = new window.FullCalendar.Calendar(calendarEl, {
locale: fcLocale,
timeZone: 'Europe/Paris',
initialView,
nowIndicator: true,
stickyHeaderDates: true,
expandRows: true,
slotMinTime: "07:00:00",
slotMaxTime: "21:00:00",
firstDay: 1,
height: isMobile ? 'auto' : 680,
customButtons: {
btnJour: { text: '<?= addslashes($translations['btn_jour'] ?? 'Jour') ?>', click(){ calendar.changeView('listDay'); } },
btnSemaine: { text: '<?= addslashes($translations['btn_semaine'] ?? 'Semaine') ?>', click(){ calendar.changeView('listWeek'); } }
},
headerToolbar: {
left: 'prev,next,today',
center: 'title',
right: isMobile ? 'btnJour,btnSemaine,dayGridMonth' : 'timeGridWeek,dayGridMonth'
},
buttonText: {
today: '<?= addslashes($translations['btn_auj'] ?? "Aujourd'hui") ?>',
month: '<?= addslashes($translations['btn_mois'] ?? 'Mois') ?>',
week: '<?= addslashes($translations['btn_semaine'] ?? 'Semaine') ?>',
day: '<?= addslashes($translations['btn_jour'] ?? 'Jour') ?>',
list: '<?= addslashes($translations['btn_liste'] ?? 'Liste') ?>'
},
views: { listDay:{buttonText:'<?= addslashes($translations['btn_jour'] ?? 'Jour') ?>'},
listWeek:{buttonText:'<?= addslashes($translations['btn_semaine'] ?? 'Semaine') ?>'} },
events: {
url: '/public/cabinet/events.php',
method: 'POST',
extraParams: { praticien_id: praticienId },
failure: function () {
Swal.fire({ icon: 'error', title: 'Oops…', text: t.erreur });
}
},
loading: function(isLoading) {
if (isLoading) return;
emptyMsgEl.style.display = calendar.getEvents().length > 0 ? 'none' : 'block';
},
eventContent: function(arg){
if (arg.view.type.includes('timeGrid') || arg.view.type.includes('dayGrid')) {
const title = document.createElement('div');
title.style.fontWeight = '600';
title.style.fontSize = '12px';
title.textContent = arg.event.title;
const time = document.createElement('div');
time.style.fontSize = '11px';
time.style.opacity = '.9';
time.textContent = arg.timeText;
return { domNodes: [title, time] };
}
return true;
},
eventClick: function(info) {
const start = new Date(info.event.start);
const end = new Date(info.event.end);
const dateStr = start.toLocaleDateString(<?= json_encode($lang === 'ru' ? 'ru-RU' : 'fr-FR') ?>, { weekday:'long', year:'numeric', month:'long', day:'numeric' });
const heureDebut = start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const heureFin = end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const raw = (info.event.extendedProps.statut || '').trim().toLowerCase();
const fr2key = { 'prévu': 'planned', 'confirmé': 'confirmed', 'annulé': 'cancelled' };
const key = fr2key[raw] || raw;
const statutMap = { planned: t.statut_prevu, confirmed: t.statut_confirme, cancelled: t.statut_annule };
const statutLabel = statutMap[key] ?? key;
Swal.fire({
title: `${t.rdv_avec} ${info.event.title}`,
html: `
<p><strong>${t.date} :</strong> ${dateStr}</p>
<p><strong>${t.heure} :</strong> ${heureDebut} – ${heureFin}</p>
<p><strong>${t.statut} :</strong> ${statutLabel}</p>
<a class="swal2-confirm swal2-styled" style="display:inline-block;margin-top:8px;border-radius:999px"
href="/public/cabinet/rendez_vous.php?id=${info.event.id}&praticien_id=${praticienId}" target="_blank">
${t.gerer_rdv}
</a>
`,
showConfirmButton: true,
confirmButtonText: t.btn_fermer
});
},
eventDidMount: function(info){ info.el.setAttribute('aria-label', info.event.title); },
windowResize: function(){
const mobile = window.matchMedia('(max-width: 768px)').matches;
calendar.setOption('height', mobile ? 'auto' : 680);
if (mobile && !calendar.view.type.startsWith('list')) calendar.changeView('listWeek');
}
});
calendar.render();
});
</script>
</body>
</html>