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

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