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

236 lines
9.7 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
require_once __DIR__ . '/../includes/init_praticien.php';
$praticien_id = (int)($_SESSION['reference_id'] ?? 0);
$lang = $_SESSION['lang'] ?? 'fr';
$fcLocale = ($lang === 'ru') ? 'ru' : 'fr';
// ✅ ne pas htmlspecialchars ici
$PAGE_TITLE = $translations['titre_agenda_page'] ?? 'Agenda';
$HEAD_EXTRA = <<<HTML
<!-- FullCalendar (bundle + toutes les locales) -->
<!--<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="/css/sweetalert2.min.css" rel="stylesheet">
<style>
/* ====== Theme léger & responsive ====== */
:root{
--card-bg:#fff;
--muted:#6c757d;
--brand:#38378B;
--accent:#e9ecef;
--text:#222;
--border:#e9ecef;
}
body{ background:#8CA9FF; color:var(--text); }
.agenda-wrap{ width:1000px; 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; }
.agenda-wrap{ width:750px; max-width:768px; }
}
</style>
HTML;
require_once __DIR__ . '/../includes/header.php';
?>
<div class="main1">
<div class="container py-5">
<div class="shadow p-4 rounded bg-light">
<h2 class="mb-4 text-center"><?= htmlspecialchars($translations['h2_mon_agenda']) ?></h2>
<div id="calendar" role="application" aria-label="calendar"></div>
<div id="emptyMsg" class="empty-state" style="display:none;">
<?= htmlspecialchars($translations['agenda_aucun_evenement'] ?? 'Aucun rendez-vous à afficher.') ?>
</div>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<p><?= $translations['copyr'] ?></p>
</div>
</footer>
<!-- Bootstrap JS -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const calendarEl = document.getElementById('calendar');
const emptyMsgEl = document.getElementById('emptyMsg');
// Expose translations to JS
const t = <?= json_encode([
'rdv_avec' => $translations['sw_rdv_avec'],
'date' => $translations['sw_date'],
'heure' => $translations['sw_heure'],
'statut' => $translations['sw_statut'],
'gerer_rdv' => $translations['sw_gerer_rdv'],
'btn_fermer' => $translations['sw_btn_fermer'],
'err_chargement' => $translations['erreur_chargement_rdv'],
'statut_confirme' => $translations['statut_confirme'],
'statut_prevu' => $translations['statut_prevu'],
'statut_annule' => $translations['statut_annule'],
], JSON_UNESCAPED_UNICODE) ?>;
const fcLocale = <?= json_encode($fcLocale) ?>;
// Vue par défaut selon la largeur : liste sur mobile, semaine sur desktop
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, // Lundi
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' // ✅ affiche: Jour | Semaine | Mois
: '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') ?>'
},
// 👉 clé pour corriger "Liste Liste"
views: {
listDay: { buttonText: '<?= addslashes($translations['btn_jour'] ?? 'Jour') ?>' },
listWeek: { buttonText: '<?= addslashes($translations['btn_semaine'] ?? 'Semaine') ?>' }
},
// Événements
events: {
url: '/public/praticien/events.php',
method: 'POST',
extraParams: { praticien_id: <?= json_encode($praticien_id) ?> },
failure: function () {
Swal.fire({
icon: 'error',
title: 'Oops…',
text: t.err_chargement
});
}
},
// Ajuste l’état vide
loading: function(isLoading) {
if (isLoading) return;
// Quand le chargement est fini, teste s’il y a des events visibles
const hasEvents = calendar.getEvents().length > 0;
emptyMsgEl.style.display = hasEvents ? 'none' : 'block';
},
// Rendu compact des événements
eventContent: function(arg){
// Sur listView, FullCalendar gère déjà bien. En grille, compactons.
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';
// arg.timeText -> "10:00 - 10:30"
time.textContent = arg.timeText;
return { domNodes: [title, time] };
}
return true;
},
// Modale SweetAlert
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/praticien/rendez_vous.php?id=${info.event.id}" target="_blank">${t.gerer_rdv}</a>
`,
showConfirmButton: true,
confirmButtonText: t.btn_fermer
});
},
// Accessibilité basique
eventDidMount: function(info){
info.el.setAttribute('aria-label', info.event.title);
},
// Switch auto si on redimensionne
windowResize: function(view){
const mobile = window.matchMedia('(max-width: 768px)').matches;
calendar.setOption('height', mobile ? 'auto' : 680);
// Garde la vue “liste” sur mobile si l’utilisateur n’a pas changé manuellement
if (mobile && !calendar.view.type.startsWith('list')) {
calendar.changeView('listWeek');
}
}
});
calendar.render();
});
</script>
</body>
</html>