236 lines
9.7 KiB
PHP
236 lines
9.7 KiB
PHP
<?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>
|