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

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