Init Git Prod Mars 2026
This commit is contained in:
commit
d81efe93b1
362 files changed
+86627
No files matched your search
@@ -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'] ?? '© '.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>
|
||||
Reference in new issue
Block a user