289 lines
9.9 KiB
PHP
289 lines
9.9 KiB
PHP
<?php
|
||
/**
|
||
* /visio/join.php?rdv=ID[&start=1]
|
||
* - Accès: client OU praticien liés au RDV
|
||
* - Fenêtre horaire respectée (config)
|
||
* - Création de salle SEULEMENT si praticien + start=1
|
||
* - Jitsi via external_api.js
|
||
*/
|
||
|
||
$config = require __DIR__ . '/visio_config.php';
|
||
|
||
// Sécurité & DB
|
||
require_once $config['paths']['securite'];
|
||
require_login_or_redirect(['client','praticien']);
|
||
require_once $config['paths']['db'];
|
||
|
||
// TZ cohérente
|
||
date_default_timezone_set('Europe/Paris');
|
||
$tz = new DateTimeZone('Europe/Paris');
|
||
|
||
// Paramètres
|
||
$rdv_id = (int)($_GET['rdv'] ?? 0);
|
||
if ($rdv_id <= 0) { http_response_code(400); exit('Paramètre manquant.'); }
|
||
|
||
// RDV
|
||
$stmt = $pdo->prepare("
|
||
SELECT rv.id, rv.praticien_id, rv.client_id, rv.date_rdv
|
||
FROM rendez_vous rv
|
||
WHERE rv.id = ?
|
||
LIMIT 1
|
||
");
|
||
$stmt->execute([$rdv_id]);
|
||
$rdv = $stmt->fetch(PDO::FETCH_ASSOC);
|
||
if (!$rdv) { http_response_code(404); exit('Rendez-vous introuvable.'); }
|
||
|
||
// Rôle & appartenance
|
||
$role = $_SESSION['role'] ?? '';
|
||
$refId = (int)($_SESSION['reference_id'] ?? 0);
|
||
$ok = ($role === 'praticien' && $refId === (int)$rdv['praticien_id'])
|
||
|| ($role === 'client' && $refId === (int)$rdv['client_id']);
|
||
if (!$ok) { http_response_code(403); exit('Accès refusé.'); }
|
||
|
||
// Fenêtre d’accès temporelle (si ta BDD est en UTC, décommente la conversion)
|
||
// $start = (new DateTime($rdv['date_rdv'], new DateTimeZone('UTC')))->setTimezone($tz);
|
||
$start = new DateTime($rdv['date_rdv'], $tz);
|
||
$open = (clone $start)->modify('-' . (int)$config['open_before_minutes'] . ' minutes');
|
||
$close = (clone $start)->modify('+' . (int)$config['close_after_minutes'] . ' minutes');
|
||
$now = new DateTime('now', $tz);
|
||
|
||
if ($now < $open) { http_response_code(403); exit('La salle ouvre à '.$open->format('H:i').'.'); }
|
||
if ($now > $close){ http_response_code(403); exit('La salle a fermé à '.$close->format('H:i').'.'); }
|
||
|
||
// Création/obtention de salle
|
||
require_once __DIR__ . '/visio_create_or_get.php';
|
||
$allowCreate = ($role === 'praticien' && isset($_GET['start']) && $_GET['start'] == '1');
|
||
|
||
$roomSlug = visio_room_for_rdv($pdo, $rdv_id, $config['slug_secret'], $allowCreate);
|
||
|
||
if (!$roomSlug) {
|
||
// Pas de salle -> on affiche un message UX propre + code HTTP 403
|
||
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||
|
||
echo <<<HTML
|
||
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Ваша консультация скоро начнётся</title>
|
||
<style>
|
||
:root{
|
||
--brand-blue:#38378B;
|
||
--brand-red:#E03C3F;
|
||
--brand-mint:#06FFA8;
|
||
--text:#1d1d1f;
|
||
--muted:#6b7280;
|
||
--bg:#8CA9FF;
|
||
--card:#ffffff;
|
||
--radius:20px;
|
||
--shadow:0 10px 30px rgba(0,0,0,.08);
|
||
}
|
||
*{box-sizing:border-box}
|
||
html,body{height:100%}
|
||
body{
|
||
margin:0;
|
||
background:var(--bg);
|
||
color:var(--text);
|
||
font-family: "Montserrat",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||
display:flex; align-items:center; justify-content:center; padding:24px;
|
||
}
|
||
.msg{
|
||
width:min(720px, 100%);
|
||
background:var(--card);
|
||
border-radius:var(--radius);
|
||
box-shadow:var(--shadow);
|
||
padding:28px 24px;
|
||
text-align:center;
|
||
border:1px solid rgba(56,55,139,.08);
|
||
}
|
||
.msg__icon{
|
||
width:64px; height:64px; margin:4px auto 14px;
|
||
display:grid; place-items:center;
|
||
border-radius:50%;
|
||
background:rgba(56,55,139,.07);
|
||
}
|
||
.msg__icon svg{width:34px; height:34px; display:block; color:var(--brand-blue)}
|
||
.msg__title{
|
||
margin:0 0 10px;
|
||
font-weight:700;
|
||
font-size:clamp(18px,2.6vw,24px);
|
||
color:var(--brand-blue);
|
||
letter-spacing:.2px;
|
||
}
|
||
.msg__text{
|
||
margin:0 auto 16px;
|
||
max-width:46ch;
|
||
line-height:1.55;
|
||
color:#2b2b2b;
|
||
font-size:clamp(14px,2vw,16px);
|
||
}
|
||
.msg__hint{
|
||
color:var(--muted);
|
||
font-size:13px;
|
||
margin-top:10px;
|
||
}
|
||
.msg__actions{
|
||
margin-top:18px; display:flex; gap:10px; justify-content:center; flex-wrap:wrap;
|
||
}
|
||
.btn{
|
||
appearance:none; border:0; cursor:pointer;
|
||
padding:10px 18px; border-radius:999px; font-weight:600;
|
||
transition:.2s transform ease, .2s box-shadow ease, .2s background-color ease;
|
||
}
|
||
.btn:active{ transform:translateY(1px); }
|
||
.btn-primary{
|
||
background:var(--brand-red); color:#fff;
|
||
box-shadow:0 6px 16px rgba(56,55,139,.25);
|
||
}
|
||
.btn-primary:hover{ background:#E03C3F; }
|
||
.btn-ghost{
|
||
background:transparent; color:var(--brand-blue);
|
||
border:1px solid rgba(56,55,139,.25);
|
||
|
||
}
|
||
.badge{
|
||
display:inline-block; margin:auto; margin-bottom:8px;
|
||
padding:4px 10px; border-radius:999px;
|
||
background:rgba(6,255,168,.14); color:#0a7a5f; font-weight:600; font-size:12px;
|
||
border:1px solid rgba(6,255,168,.25);
|
||
}
|
||
a { text-decoration: none;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="msg" role="alert" aria-live="polite">
|
||
<div class="msg__icon" aria-hidden="true">
|
||
<!-- clock icon -->
|
||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2"/>
|
||
</svg>
|
||
</div>
|
||
<div class="badge">Ожидание подключения</div>
|
||
<h1 class="msg__title">Ваша консультация скоро начнётся</h1>
|
||
<p class="msg__text">
|
||
Пожалуйста, оставайтесь на этой странице — специалист подключится в ближайшее время.
|
||
Если окно не открылось автоматически, нажмите «Обновить».
|
||
</p>
|
||
<div class="msg__actions">
|
||
<button class="btn btn-primary" onclick="location.reload()">Обновить</button>
|
||
<a class="btn btn-ghost" href="/public/client/mes_rendez_vous.php">Мои консультации</a>
|
||
</div>
|
||
<p class="msg__hint">Подсказка: проверьте доступ к камере и микрофону заранее.</p>
|
||
</main>
|
||
</body>
|
||
</html>
|
||
HTML;
|
||
exit;
|
||
}
|
||
|
||
|
||
// Nom affiché
|
||
//$displayName = htmlspecialchars($_SESSION['display_name'] ?? ($role === 'praticien' ? 'Praticien' : 'Client'));
|
||
//$jitsiDomain = $config['jitsi_domain'];
|
||
require_once __DIR__ . '/nom_afficher.php';
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>Visio — RDV #<?= (int)$rdv_id ?></title>
|
||
<style>
|
||
html, body { height: 100%; margin: 0; }
|
||
#jitsi { height: 100%; }
|
||
</style>
|
||
<script src="https://<?= htmlspecialchars($jitsiDomain) ?>/external_api.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="jitsi"></div>
|
||
<script>
|
||
(function () {
|
||
const domain = "<?= htmlspecialchars($jitsiDomain) ?>";
|
||
const REDIRECT_URL = "/visio/fin.php?rdv=<?= (int)$rdv_id ?>";
|
||
const isPraticien = <?= json_encode(($role ?? '') === 'praticien') ?>;
|
||
|
||
const options = {
|
||
roomName: "<?= htmlspecialchars($roomSlug) ?>",
|
||
parentNode: document.querySelector('#jitsi'),
|
||
userInfo: { displayName: <?= json_encode($displayName) ?> },
|
||
configOverwrite: {
|
||
prejoinConfig: { enabled: true },
|
||
startWithAudioMuted: false,
|
||
startWithVideoMuted: false,
|
||
disableDeepLinking: true,
|
||
enableClosePage: false
|
||
}
|
||
};
|
||
|
||
const api = new JitsiMeetExternalAPI(domain, options);
|
||
|
||
// Redirection quand l’utilisateur quitte
|
||
api.addListener('videoConferenceLeft', () => { window.location.assign(REDIRECT_URL); });
|
||
api.addListener('readyToClose', () => { window.location.assign(REDIRECT_URL); });
|
||
|
||
// ---- Bandeau “durée dépassée” (praticien uniquement) ----
|
||
if (isPraticien) {
|
||
// Charger Montserrat si absent
|
||
(function ensureMontserrat(){
|
||
const id = 'montserrat-font-link';
|
||
if (!document.getElementById(id)) {
|
||
const pre = document.createElement('link');
|
||
pre.rel = 'preconnect'; pre.href = 'https://fonts.gstatic.com'; pre.crossOrigin = 'anonymous';
|
||
const link = document.createElement('link');
|
||
link.id = id;
|
||
link.rel = 'stylesheet';
|
||
link.href = 'https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700&display=swap';
|
||
document.head.appendChild(pre);
|
||
document.head.appendChild(link);
|
||
}
|
||
})();
|
||
|
||
const dureeMinutes = <?= (int)($rdv['duree_minutes'] ?? 60) ?>; // durée prévue (BDD)
|
||
const startTime = new Date("<?= (new DateTime($rdv['date_rdv'], new DateTimeZone('Europe/Paris')))->format('Y-m-d H:i:s') ?>");
|
||
const plannedEnd = new Date(startTime.getTime() + dureeMinutes * 60000);
|
||
|
||
function showOvertimeBanner() {
|
||
if (document.getElementById('overtime-banner')) return;
|
||
|
||
const banner = document.createElement('div');
|
||
banner.id = 'overtime-banner';
|
||
banner.textContent = "⚠️ Время консультации истекло. Пожалуйста, завершите встречу.";
|
||
Object.assign(banner.style, {
|
||
position: 'fixed',
|
||
bottom: '10px',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
background: '#E03C3F',
|
||
color: '#fff',
|
||
padding: '10px 18px',
|
||
borderRadius: '8px',
|
||
fontSize: '14px',
|
||
fontWeight: '600',
|
||
zIndex: '9999',
|
||
boxShadow: '0 6px 16px rgba(0,0,0,.2)',
|
||
fontFamily: "Montserrat,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif"
|
||
});
|
||
document.body.appendChild(banner);
|
||
}
|
||
|
||
function checkOvertime() {
|
||
const now = new Date();
|
||
if (now > plannedEnd) showOvertimeBanner();
|
||
}
|
||
|
||
// Check à T0 (au cas où on lance en retard) puis toutes les 60s
|
||
checkOvertime();
|
||
setInterval(checkOvertime, 60000);
|
||
}
|
||
|
||
// Nettoyage
|
||
window.addEventListener('beforeunload', () => api.dispose());
|
||
})();
|
||
</script>
|
||
|
||
|
||
</body>
|
||
</html>
|