880 lines
41 KiB
PHP
880 lines
41 KiB
PHP
<?php
|
||
// /public/cabinet/valider_rdv.php
|
||
require_once __DIR__ . '/../includes/init_secretaire.php';
|
||
|
||
$ROOT = dirname(__DIR__, 2);
|
||
|
||
require_once APP_HTDOCS . '/push/push_endpoint.php';
|
||
// $MAILCFG
|
||
// (Facultatif) SMS/WhatsApp si tu l'utilises côté client
|
||
$notifyPath = APP_HTDOCS . '/sms/notify_rdv.php';
|
||
if (is_file($notifyPath)) require_once $notifyPath;
|
||
|
||
use PHPMailer\PHPMailer\PHPMailer;
|
||
|
||
/* ================== Const / helpers ================== */
|
||
$c_online = $c_online ?? 'online';
|
||
$c_offline = $c_offline ?? 'offline';
|
||
|
||
if (empty($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
|
||
$CSRF = $_SESSION['csrf_token'];
|
||
|
||
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
|
||
function email_norm(string $e): string { return strtolower(trim($e)); }
|
||
|
||
/* ------- Mail helpers ------- */
|
||
function send_mail_ics(
|
||
string $to, string $toName, string $subject, string $text, string $html,
|
||
?string $icsName=null, ?string $icsContent=null, ?string $icsMethod='REQUEST'
|
||
): bool {
|
||
global $MAILCFG;
|
||
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) return false;
|
||
$m = new PHPMailer(true);
|
||
try {
|
||
$m->isSMTP();
|
||
$m->Host = $MAILCFG['smtp']['host']; $m->Port = (int)$MAILCFG['smtp']['port'];
|
||
$m->SMTPSecure = $MAILCFG['smtp']['secure'] ?? PHPMailer::ENCRYPTION_STARTTLS;
|
||
$m->SMTPAuth = (bool)($MAILCFG['smtp']['auth'] ?? true);
|
||
$m->Username = $MAILCFG['smtp']['username'] ?? '';
|
||
$m->Password = $MAILCFG['smtp']['password'] ?? '';
|
||
$m->CharSet = 'UTF-8'; $m->Encoding = 'base64'; $m->XMailer = '';
|
||
if (!empty($MAILCFG['bounce'])) $m->Sender = $MAILCFG['bounce'];
|
||
$from = $MAILCFG['from'] ?? ['no-reply@uslugi.fr','USLUGI.FR'];
|
||
$rpto = $MAILCFG['reply_to'] ?? ['contact@uslugi.fr','USLUGI.FR'];
|
||
$m->setFrom($from[0], $from[1] ?? '');
|
||
if (!empty($rpto[0])) $m->addReplyTo($rpto[0], $rpto[1] ?? '');
|
||
$m->addAddress($to, $toName);
|
||
if (!empty($MAILCFG['suppress_auto'])) {
|
||
$m->addCustomHeader('Auto-Submitted','auto-generated');
|
||
$m->addCustomHeader('X-Auto-Response-Suppress','All');
|
||
}
|
||
$m->isHTML(true); $m->Subject = $subject; $m->Body = $html; $m->AltBody = $text;
|
||
if ($icsName && $icsContent !== null && $icsMethod) {
|
||
$m->addStringAttachment($icsContent, $icsName, 'base64', 'text/calendar; method='.$icsMethod.'; charset=UTF-8');
|
||
}
|
||
$m->send(); return true;
|
||
} catch(Throwable $e){ error_log('send_mail_ics: '.$e->getMessage()); return false; }
|
||
}
|
||
|
||
/* ====== Email template (même rendu pour client/praticien) ====== */
|
||
function email_tpl_html(array $d, array $t): string {
|
||
$brand = '<img src="https://uslugi.fr/public/uploads/img/logo_mail.png" width="150" alt="USLUGI.FR">';
|
||
$brand_bas ='УСЛУГИ.ФР';
|
||
return '<!DOCTYPE html><html lang="ru"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>USLUGI.FR</title></head>
|
||
<body style="margin:0;padding:0;background:#f4f6f8;font-family:Arial,Helvetica,sans-serif;">
|
||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f4f6f8;padding:24px 0;"><tr><td align="center">
|
||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="background:#fff;border-radius:8px;overflow:hidden;">
|
||
<tr><td style="background:#38378B;color:#fff;padding:20px 24px;font-size:18px;font-weight:600;">'.$brand.'</td></tr>
|
||
<tr><td style="padding:24px 24px 8px 24px;color:#111;font-size:16px;line-height:1.5;">'.nl2br(htmlspecialchars($d['hello'])).'</td></tr>
|
||
<tr><td style="padding:8px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($d['intro'])).'</td></tr>
|
||
<tr><td style="padding:16px 24px;">
|
||
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid #e5e7eb;border-radius:8px;">
|
||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;border-bottom:1px solid #e5e7eb;">'.htmlspecialchars($d['who_label']).'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['who_value']).'</td></tr>
|
||
<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($t['date_label'] ?? 'Date').'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['when']).'</td></tr>'.
|
||
(!empty($d['format_line']) ? '<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($t['format_label'] ?? 'Формат').'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['format_line']).'</td></tr>' : '').
|
||
(!empty($d['address_line']) ? '<tr><td style="padding:14px 16px;font-size:14px;color:#6b7280;">'.htmlspecialchars($t['adresse_label'] ?? 'Adresse').'</td><td style="padding:14px 16px;font-size:14px;color:#111;">'.htmlspecialchars($d['address_line']).'</td></tr>' : '').
|
||
'</table>
|
||
</td></tr>'.
|
||
(!empty($d['cta_url']) ? '<tr><td align="center" style="padding:8px 24px 24px 24px;"><a href="'.htmlspecialchars($d['cta_url']).'" target="_blank" style="display:inline-block;text-decoration:none;padding:12px 20px;border-radius:999px;background:#E03C3F;color:#fff;font-weight:600;">'.htmlspecialchars($d['cta_label']).'</a></td></tr>' : '').
|
||
'<tr><td style="padding:8px 24px 24px 24px;color:#111;font-size:16px;line-height:1.6;">'.nl2br(htmlspecialchars($d['footer'])).'</td></tr>
|
||
</table>
|
||
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="margin-top:12px;"><tr><td style="text-align:center;color:#9ca3af;font-size:12px;padding:8px 0;">© '.date('Y').' '.$brand_bas.' — '.htmlspecialchars($t['merci_simple'] ?? 'Merci').'</td></tr></table>
|
||
</td></tr></table></body></html>';
|
||
}
|
||
function email_tpl_text(array $d, array $t): string {
|
||
$L=[]; $L[]=$d['hello']; $L[]=''; $L[]=$d['intro']; $L[]='';
|
||
$L[]=($d['who_label']).': '.$d['who_value'];
|
||
$L[]=($t['date_label'] ?? 'Date').': '.$d['when'];
|
||
if (!empty($d['format_line'])) $L[]=($t['format_label'] ?? 'Формат').': '.$d['format_line'];
|
||
if (!empty($d['address_line'])) $L[]=($t['adresse_label'] ?? 'Adresse').': '.$d['address_line'];
|
||
if (!empty($d['cta_url'])) { $L[]=''; $L[]=($d['cta_label'] ?? 'Voir les détails').': '.$d['cta_url']; }
|
||
$L[]=''; $L[]=$d['footer']; return implode("\n",$L);
|
||
}
|
||
|
||
|
||
/* ------- ICS helpers ------- */
|
||
function ics_escape(string $s): string {
|
||
$s = str_replace("\\","\\\\",$s);
|
||
$s = str_replace(";","\;",$s);
|
||
$s = str_replace(",","\,",$s);
|
||
return preg_replace("/\r\n|\r|\n/","\\n",$s);
|
||
}
|
||
function ics_build(
|
||
string $uid, DateTime $start, DateTime $end, string $tz, string $summary,
|
||
string $desc, string $loc, array $attendees, string $method='REQUEST', string $status='CONFIRMED'
|
||
): string {
|
||
$L=[];
|
||
$L[]="BEGIN:VCALENDAR";
|
||
$L[]="VERSION:2.0";
|
||
$L[]="PRODID:-//УСЛУГИ.ФР//RDV//FR";
|
||
$L[]="CALSCALE:GREGORIAN";
|
||
$L[]="METHOD:{$method}";
|
||
$L[]="BEGIN:VEVENT";
|
||
$L[]="UID:{$uid}";
|
||
$L[]="DTSTAMP:".gmdate('Ymd\THis\Z');
|
||
$L[]="DTSTART;TZID={$tz}:".$start->format('Ymd\THis');
|
||
$L[]="DTEND;TZID={$tz}:".$end->format('Ymd\THis');
|
||
if ($summary) $L[]="SUMMARY:".ics_escape($summary);
|
||
if ($desc) $L[]="DESCRIPTION:".ics_escape($desc);
|
||
if ($loc) $L[]="LOCATION:".ics_escape($loc);
|
||
$L[]="ORGANIZER;CN=УСЛУГИ.ФР:mailto:rdv@uslugi.fr";
|
||
foreach ($attendees as $a) {
|
||
if (empty($a['email'])) continue;
|
||
$cn = ics_escape($a['cn'] ?? '');
|
||
$L[]="ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;CN={$cn};RSVP=TRUE:mailto:".$a['email'];
|
||
}
|
||
$L[]="STATUS:{$status}";
|
||
$L[]="END:VEVENT";
|
||
$L[]="END:VCALENDAR";
|
||
return implode("\r\n",$L)."\r\n";
|
||
}
|
||
|
||
/* ================== Entrées ================== */
|
||
$secretaire_user_id = (int)($_SESSION['compte_id'] ?? 0);
|
||
$praticien_id = isset($_GET['praticien_id']) ? (int)$_GET['praticien_id'] : (int)($_POST['praticien_id'] ?? 0);
|
||
$startIso = $_GET['start'] ?? ($_POST['start'] ?? null);
|
||
$mode_rdv_in = $_GET['mode_rdv'] ?? ($_POST['mode_rdv'] ?? null);
|
||
$client_id = isset($_GET['client_id']) ? (int)$_GET['client_id'] : (isset($_POST['client_id']) ? (int)$_POST['client_id'] : 0);
|
||
|
||
/* ================== Vérif pivot secrétaire ↔ praticien ================== */
|
||
$chk = $pdo->prepare("
|
||
SELECT can_manage_rdv
|
||
FROM secretaire_praticien
|
||
WHERE secretaire_user_id = :u
|
||
AND praticien_id = :p
|
||
AND is_active = 1
|
||
LIMIT 1
|
||
");
|
||
$chk->execute([':u'=>$secretaire_user_id, ':p'=>$praticien_id]);
|
||
$pivot = $chk->fetch(PDO::FETCH_ASSOC);
|
||
if (!$pivot) { http_response_code(403); exit('<div style="padding:24px">Forbidden</div>'); }
|
||
if (((int)($pivot['can_manage_rdv'] ?? 0)) !== 1) {
|
||
http_response_code(403);
|
||
exit('<div style="padding:24px">'.h($translations['erreur_droits_reservation'] ?? 'Droits insuffisants pour réserver').'</div>');
|
||
}
|
||
|
||
/* ================== Récup praticien ================== */
|
||
$stmt = $pdo->prepare("SELECT nom, prenom, duree_rdv, adresse, ville, code_postal, email FROM praticien WHERE id=?");
|
||
$stmt->execute([$praticien_id]);
|
||
$praticien = $stmt->fetch(PDO::FETCH_ASSOC);
|
||
if (!$praticien) { http_response_code(404); exit('Praticien introuvable'); }
|
||
|
||
$duree = (int)($praticien['duree_rdv'] ?? 60);
|
||
$has_address =
|
||
!empty(trim((string)($praticien['adresse'] ?? ''))) &&
|
||
!empty(trim((string)($praticien['ville'] ?? ''))) &&
|
||
!empty(trim((string)($praticien['code_postal'] ?? '')));
|
||
|
||
/* ================== Mode POST: AJAX (recherche / création / validation) ================== */
|
||
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
|
||
|
||
/* ----- Recherche dynamique de client plateforme non lié ----- */
|
||
// ----- Recherche dynamique de client plateforme non lié -----
|
||
if (($_POST['action'] ?? '') === 'search_client') {
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
|
||
echo json_encode(['ok'=>false, 'items'=>[], 'msg'=>'CSRF']); exit;
|
||
}
|
||
|
||
$q = trim((string)($_POST['q'] ?? ''));
|
||
if ($q === '' || mb_strlen($q) < 2) { // évite spam / vide
|
||
echo json_encode(['ok'=>true, 'items'=>[]]); exit;
|
||
}
|
||
|
||
try {
|
||
$like = '%'.$q.'%';
|
||
|
||
// NOTE: si votre table client n'a PAS adresse/ville/code_postal,
|
||
// remplacez la ligne SELECT par: SELECT c.id, c.prenom, c.nom, c.email, c.telephone
|
||
$sql = "
|
||
SELECT c.id, c.prenom, c.nom, c.email, c.telephone, c.adresse, c.ville, c.code_postal
|
||
FROM client c
|
||
WHERE (
|
||
c.email LIKE :like_email
|
||
OR CONCAT(c.prenom,' ',c.nom) LIKE :like_np1
|
||
OR CONCAT(c.nom,' ',c.prenom) LIKE :like_np2
|
||
OR c.telephone LIKE :like_tel
|
||
)
|
||
AND NOT EXISTS (
|
||
SELECT 1 FROM client_dossiers d
|
||
WHERE d.client_id = c.id
|
||
AND d.praticien_id = :pid
|
||
)
|
||
ORDER BY c.prenom, c.nom
|
||
LIMIT 12
|
||
";
|
||
|
||
$s = $pdo->prepare($sql);
|
||
$s->execute([
|
||
':like_email' => $like,
|
||
':like_np1' => $like,
|
||
':like_np2' => $like,
|
||
':like_tel' => $like,
|
||
':pid' => $praticien_id,
|
||
]);
|
||
|
||
$items = $s->fetchAll(PDO::FETCH_ASSOC) ?: [];
|
||
echo json_encode(['ok'=>true, 'items'=>$items]); exit;
|
||
|
||
} catch (Throwable $e) {
|
||
error_log('search_client: '.$e->getMessage());
|
||
echo json_encode(['ok'=>false, 'items'=>[], 'msg'=>'Erreur serveur']); exit;
|
||
}
|
||
}
|
||
|
||
|
||
// création client rapide ?
|
||
if (($_POST['action'] ?? '') === 'create_client') {
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
|
||
http_response_code(400);
|
||
exit(json_encode(['ok'=>false,'msg'=>'CSRF']));
|
||
}
|
||
$nom = trim((string)($_POST['nom'] ?? ''));
|
||
$prenom = trim((string)($_POST['prenom'] ?? ''));
|
||
$email = email_norm((string)($_POST['email'] ?? ''));
|
||
$tel = trim((string)($_POST['telephone'] ?? ''));
|
||
$dob = trim((string)($_POST['date_naissance'] ?? '')); // YYYY-MM-DD
|
||
|
||
if ($nom === '' || $prenom === '' || $email === '' || $tel === '') {
|
||
exit(json_encode(['ok'=>false,'msg'=>($translations['donnees_manquantes_reservation'] ?? 'Champs manquants')]));
|
||
}
|
||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
exit(json_encode(['ok'=>false,'msg'=>($translations['email_invalide'] ?? 'Email invalide')]));
|
||
}
|
||
|
||
try {
|
||
$pdo->beginTransaction();
|
||
|
||
// Si un compte existe déjà pour cet email
|
||
$acc = $pdo->prepare("SELECT id, role, reference_id FROM compte WHERE email = ? LIMIT 1");
|
||
$acc->execute([$email]);
|
||
$exist = $acc->fetch(PDO::FETCH_ASSOC);
|
||
|
||
if ($exist) {
|
||
if (($exist['role'] ?? '') !== 'client') {
|
||
// Email déjà utilisé par praticien/admin/secretaire => refuser création
|
||
$pdo->rollBack();
|
||
exit(json_encode(['ok'=>false,'msg'=>($translations['email_deja_utilise'] ?? 'Cet email est déjà utilisé.')]));
|
||
}
|
||
// Récupérer le client lié via reference_id
|
||
$cid = (int)($exist['reference_id'] ?? 0);
|
||
if ($cid > 0) {
|
||
// Pas de re-création, on renvoie le client existant pour réserver directement
|
||
$pdo->commit();
|
||
exit(json_encode(['ok'=>true,'client_id'=>$cid,'msg'=>($translations['client_existant_utilise'] ?? 'Client existant sélectionné.')]));
|
||
}
|
||
// Compte client sans fiche client (rare) -> on crée la fiche et on lie le compte
|
||
$insC = $pdo->prepare("
|
||
INSERT INTO client (nom, prenom, email, telephone, date_naissance, accepted_cgu)
|
||
VALUES (?,?,?,?,?,1)
|
||
");
|
||
$insC->execute([$nom, $prenom, $email, $tel, ($dob ?: null)]);
|
||
$newClientId = (int)$pdo->lastInsertId();
|
||
|
||
$upd = $pdo->prepare("UPDATE compte SET reference_id = ? WHERE id = ? LIMIT 1");
|
||
$upd->execute([$newClientId, (int)$exist['id']]);
|
||
|
||
$pdo->commit();
|
||
exit(json_encode(['ok'=>true,'client_id'=>$newClientId,'msg'=>($translations['client_cree_ok'] ?? 'Client créé.')]));
|
||
}
|
||
|
||
// Sinon : créer la fiche client
|
||
$ins = $pdo->prepare("
|
||
INSERT INTO client (nom, prenom, email, telephone, date_naissance, accepted_cgu)
|
||
VALUES (?,?,?,?,?,1)
|
||
");
|
||
$ins->execute([$nom, $prenom, $email, $tel, ($dob ?: null)]);
|
||
$newClientId = (int)$pdo->lastInsertId();
|
||
|
||
// Créer le compte avec MDP provisoire hashé + token de validation email
|
||
$provisional = 'Uslugi_11992288';
|
||
$hash = password_hash($provisional, PASSWORD_DEFAULT);
|
||
$token = bin2hex(random_bytes(32)); // TTL 24h géré côté endpoint (via cree_le ou table dédiée)
|
||
|
||
$accIns = $pdo->prepare("
|
||
INSERT INTO compte (email, mot_de_passe, role, reference_id, token_validation, email_valide, cree_le, modifie_le)
|
||
VALUES (?,?,?,?,?,0,NOW(),NOW())
|
||
");
|
||
$accIns->execute([$email, $hash, 'client', $newClientId, $token]);
|
||
|
||
$pdo->commit();
|
||
|
||
// Envoyer l'email d'activation (sans ICS)
|
||
try {
|
||
$activationUrl = 'https://uslugi.fr/valider_email_cabinet.php?token='.urlencode($token).'&email='.urlencode($email);
|
||
$subj_a = $translations['activation_compte_sujet'] ?? 'Активируйте ваш аккаунт / Activez votre compte';
|
||
$helloName = trim($prenom.' '.$nom);
|
||
|
||
$dataA = [
|
||
'hello' => sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s', $helloName),
|
||
'intro' => $translations['activation_compte_intro'] ??
|
||
"Чтобы завершить создание аккаунта, пожалуйста подтвердите ваш e-mail и задайте новый пароль.\nPour finaliser la création de votre compte, confirmez votre e-mail et définissez votre mot de passe.",
|
||
'who_label' => $translations['email_label'] ?? 'E-mail',
|
||
'who_value' => $email,
|
||
'when' => date('d/m/Y H:i'),
|
||
'format_line' => '',
|
||
'address_line'=> '',
|
||
'cta_url' => $activationUrl,
|
||
'cta_label' => $translations['placeholder_nouveau_mdp'] ?? 'Définir le mot de passe',
|
||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||
];
|
||
$text_a = email_tpl_text($dataA, $translations);
|
||
$html_a = email_tpl_html($dataA, $translations);
|
||
|
||
// Pas de pièce jointe ICS ici
|
||
send_mail_ics($email, $helloName, $subj_a, $text_a, $html_a, null, null, null);
|
||
} catch(Throwable $e) {
|
||
error_log('activation email send failed: '.$e->getMessage());
|
||
}
|
||
|
||
exit(json_encode([
|
||
'ok' => true,
|
||
'client_id' => $newClientId,
|
||
'msg' => ($translations['client_cree_ok'] ?? 'Client créé.')
|
||
]));
|
||
} catch (Throwable $e) {
|
||
if ($pdo->inTransaction()) $pdo->rollBack();
|
||
error_log('create_client: '.$e->getMessage());
|
||
exit(json_encode(['ok'=>false,'msg'=>'Erreur']));
|
||
}
|
||
}
|
||
|
||
// validation RDV
|
||
if (($_POST['action'] ?? '') === 'validate') {
|
||
header('Content-Type: application/json; charset=utf-8');
|
||
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf'] ?? '')) {
|
||
echo json_encode(['ok'=>false,'html'=>'<div class="msg"><h2>CSRF</h2></div>']); exit;
|
||
}
|
||
|
||
$client_id = (int)($_POST['client_id'] ?? 0);
|
||
$startIso = $_POST['start'] ?? null;
|
||
$mode_in = $_POST['mode_rdv'] ?? null;
|
||
if ($client_id <= 0 || !$startIso || $praticien_id <= 0) {
|
||
echo json_encode(['ok'=>false,'html'=>'<div class="msg"><h2>'.h($translations['donnees_manquantes_reservation'] ?? 'Données manquantes').'</h2></div>']); exit;
|
||
}
|
||
|
||
// vérifie que le client existe
|
||
$c = $pdo->prepare("SELECT id, email, nom, prenom, telephone FROM client WHERE id=?");
|
||
$c->execute([$client_id]);
|
||
$client = $c->fetch(PDO::FETCH_ASSOC);
|
||
if (!$client) {
|
||
echo json_encode(['ok'=>false,'html'=>'<div class="msg"><h2>Client introuvable</h2></div>']); exit;
|
||
}
|
||
// Optionnel: lier au praticien ici si tu veux (sinon, tu as dit que ça se fera à la confirmation)
|
||
// $pl = $pdo->prepare("INSERT IGNORE INTO client_dossiers (praticien_id, client_id, cree_le) VALUES (?,?,NOW())");
|
||
// $pl->execute([$praticien_id, $client_id]);
|
||
|
||
// créneau libre ?
|
||
$date_sql = (new DateTime($startIso))->format('Y-m-d H:i:s');
|
||
$dup = $pdo->prepare("SELECT COUNT(*) FROM rendez_vous WHERE praticien_id=? AND date_rdv=?");
|
||
$dup->execute([$praticien_id, $date_sql]);
|
||
if ((int)$dup->fetchColumn() > 0) {
|
||
echo json_encode(['ok'=>false,'html'=>'<div class="msg"><h2>'.h($translations['creneau_deja_reserve'] ?? 'Créneau déjà réservé').'</h2></div>']); exit;
|
||
}
|
||
|
||
// mode / location
|
||
$mode = ($mode_in === $GLOBALS['c_offline']) ? 'offline' : 'online';
|
||
if ($mode === 'offline' && !$GLOBALS['has_address']) $mode = 'online';
|
||
|
||
// insert RDV (statut 'prévu')
|
||
$uuid = bin2hex(random_bytes(16));
|
||
$ins = $pdo->prepare("
|
||
INSERT INTO rendez_vous (praticien_id, client_id, date_rdv, duree_minutes, lieu, statut, commentaire, uuid_ical, created_at)
|
||
VALUES (?, ?, ?, ?, ?, 'prévu', '', ?, NOW())
|
||
");
|
||
$ins->execute([$praticien_id, $client_id, $date_sql, $duree, $mode, $uuid]);
|
||
|
||
// =========================
|
||
// LOG RDV (rdv_actions_log) : create (secrétaire)
|
||
// =========================
|
||
$rdvIdCreated = (int)$pdo->lastInsertId();
|
||
// ✅ compte_id client + praticien (pour badge counters)
|
||
$clientCompteId = 0;
|
||
$praticienCompteId = 0;
|
||
|
||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='client' AND reference_id=? LIMIT 1");
|
||
$st->execute([(int)$client_id]);
|
||
$clientCompteId = (int)($st->fetchColumn() ?: 0);
|
||
|
||
$st = $pdo->prepare("SELECT id FROM compte WHERE role='praticien' AND reference_id=? LIMIT 1");
|
||
$st->execute([(int)$praticien_id]);
|
||
$praticienCompteId = (int)($st->fetchColumn() ?: 0);
|
||
|
||
// ✅ Badge RDV : +1 pour le client (RDV créé par secrétaire)
|
||
if ($clientCompteId > 0) {
|
||
$pdo->prepare("
|
||
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
|
||
VALUES (:cid, 0, 1)
|
||
ON DUPLICATE KEY UPDATE
|
||
rdv_unread = LEAST(99, rdv_unread + 1),
|
||
updated_at = NOW()
|
||
")->execute([':cid' => $clientCompteId]);
|
||
}
|
||
|
||
// ✅ Badge RDV : +1 pour le praticien (nouveau RDV à confirmer/voir)
|
||
if ($praticienCompteId > 0) {
|
||
$pdo->prepare("
|
||
INSERT INTO push_badge_counters (compte_id, msg_unread, rdv_unread)
|
||
VALUES (:pid, 0, 1)
|
||
ON DUPLICATE KEY UPDATE
|
||
rdv_unread = LEAST(99, rdv_unread + 1),
|
||
updated_at = NOW()
|
||
")->execute([':pid' => $praticienCompteId]);
|
||
}
|
||
|
||
$afterJson = json_encode([
|
||
'statut' => 'prévu',
|
||
'date_rdv' => $date_sql,
|
||
'duree_minutes' => (int)$duree,
|
||
'lieu' => $mode, // 'online'/'offline'
|
||
'praticien_id' => (int)$praticien_id,
|
||
'client_id' => (int)$client_id,
|
||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||
|
||
$logStmt = $pdo->prepare("
|
||
INSERT INTO rdv_actions_log
|
||
(rdv_id, action, by_user_id, by_role, before_json, after_json, created_at)
|
||
VALUES
|
||
(?, 'create', ?, 'secretaire', NULL, ?, NOW())
|
||
");
|
||
$logStmt->execute([$rdvIdCreated, $secretaire_user_id, $afterJson]);
|
||
|
||
// =========================
|
||
// =========================
|
||
// PUSH RDV — création (par secrétaire)
|
||
// =========================
|
||
try {
|
||
|
||
// 🔔 Client : nouveau RDV créé
|
||
push_rdv_to_client(
|
||
$pdo,
|
||
(int)$client_id,
|
||
$translations['push_rdv_created_title'] ?? 'Новая запись',
|
||
$translations['push_rdv_created_body'] ?? 'Для вас создана новая запись.',
|
||
'/public/client/mes_rendez_vous.php',
|
||
[
|
||
'rdv_id' => (int)$rdvIdCreated,
|
||
'action' => 'create',
|
||
'from_role' => 'secretaire',
|
||
'praticien_id' => (int)$praticien_id,
|
||
'client_id' => (int)$client_id,
|
||
'date_rdv' => $date_sql,
|
||
]
|
||
);
|
||
|
||
// 🔔 Praticien : nouveau RDV créé par secrétaire
|
||
push_rdv_to_praticien(
|
||
$pdo,
|
||
(int)$praticien_id,
|
||
$translations['push_rdv_created_praticien_title'] ?? 'Новая запись',
|
||
$translations['push_rdv_created_praticien_body'] ?? 'Секретарь создал(а) новую запись.',
|
||
'/public/praticien/rendez_vous.php',
|
||
[
|
||
'rdv_id' => (int)$rdvIdCreated,
|
||
'action' => 'create',
|
||
'from_role' => 'secretaire',
|
||
'secretaire_id' => (int)$secretaire_user_id,
|
||
'praticien_id' => (int)$praticien_id,
|
||
'client_id' => (int)$client_id,
|
||
'date_rdv' => $date_sql,
|
||
]
|
||
);
|
||
|
||
} catch (Throwable $e) {
|
||
error_log('push rdv create (secretaire) failed: '.$e->getMessage());
|
||
}
|
||
|
||
|
||
|
||
// Récup infos complètes pour e-mails
|
||
$q = $pdo->prepare("
|
||
SELECT rv.id AS rdv_id, rv.date_rdv, rv.duree_minutes, rv.lieu,
|
||
p.nom AS nom_praticien, p.prenom AS prenom_praticien, p.email AS email_praticien, p.telephone AS tel_praticien,
|
||
p.adresse AS p_adresse, p.ville AS p_ville, p.code_postal AS p_cp,
|
||
c.email AS email_client, c.nom AS nom_client, c.prenom AS prenom_client, c.telephone AS tel_client
|
||
FROM rendez_vous rv
|
||
JOIN praticien p ON rv.praticien_id = p.id
|
||
JOIN client c ON rv.client_id = c.id
|
||
WHERE rv.uuid_ical = ?
|
||
LIMIT 1
|
||
");
|
||
$q->execute([$uuid]);
|
||
$rdv = $q->fetch(PDO::FETCH_ASSOC);
|
||
if (!$rdv) {
|
||
echo json_encode(['ok'=>false,'html'=>'<div class="msg"><h2>RDV introuvable</h2></div>']); exit;
|
||
}
|
||
|
||
// Construire la réponse UI immédiatement
|
||
$tz='Europe/Paris'; $tzObj = new DateTimeZone($tz);
|
||
$start = new DateTime($rdv['date_rdv'], $tzObj);
|
||
$end = (clone $start)->modify('+'.(int)$rdv['duree_minutes'].' minutes');
|
||
$when_a = $start->format('d/m/Y') . ' ' . ($translations['a'] ?? 'à') . ' ' . $start->format('H:i');
|
||
|
||
$html = '<div class="card-notice" style="background:#fff;border-radius:22px;box-shadow:0 12px 40px rgba(0,0,0,.08);padding:22px;">
|
||
<h1 class="cn-title" style="margin:0 0 6px">'.h($translations['block_1'] ?? 'Спасибо за запись').'</h1>
|
||
<div class="cn-date" style="color:#60646c;margin-bottom:8px">'.h($when_a).'</div>
|
||
<p>'.h($translations['block_2'] ?? 'Ожидает подтверждения').'</p>
|
||
<div class="actions" style="margin-top:12px">
|
||
<a class="btn btn-outline" href="/public/cabinet/rendez_vous.php?praticien_id='.(int)$praticien_id.'">'.h($translations['retour_a_la_liste'] ?? 'Retour à la liste').'</a>
|
||
</div>
|
||
</div>';
|
||
|
||
// envoyer la réponse puis continuer (si FastCGI)
|
||
session_write_close();
|
||
echo json_encode(['ok'=>true,'html'=>$html]);
|
||
if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
|
||
ignore_user_abort(true);
|
||
|
||
// e-mails + ICS en arrière-plan
|
||
try {
|
||
$addr = trim(implode(', ', array_filter([
|
||
trim((string)$rdv['p_adresse']),
|
||
trim((string)$rdv['p_cp'].' '.$rdv['p_ville'])
|
||
])));
|
||
$is_offline = ($rdv['lieu'] === 'offline');
|
||
$loc = $is_offline ? ($addr ?: ($translations['option_cabinet'] ?? 'Очно')) : ($translations['option_online'] ?? 'Онлайн');
|
||
|
||
$uid = $uuid;
|
||
$ics_client = ics_build(
|
||
$uid,$start,$end,$tz,
|
||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
|
||
'', $loc,
|
||
[
|
||
['cn'=>$rdv['prenom_praticien'].' '.$rdv['nom_praticien'],'email'=>$rdv['email_praticien']],
|
||
['cn'=>$rdv['prenom_client'].' '.$rdv['nom_client'],'email'=>$rdv['email_client']]
|
||
],
|
||
'REQUEST','CONFIRMED'
|
||
);
|
||
$ics_prat = ics_build(
|
||
$uid,$start,$end,$tz,
|
||
($translations['rdv_avec'] ?? 'Rendez-vous avec').' '.($rdv['prenom_client'].' '.$rdv['nom_client']),
|
||
'', $loc,
|
||
[
|
||
['cn'=>$rdv['prenom_client'].' '.$rdv['nom_client'],'email'=>$rdv['email_client']],
|
||
['cn'=>$rdv['prenom_praticien'].' '.$rdv['nom_praticien'],'email'=>$rdv['email_praticien']]
|
||
],
|
||
'REQUEST','CONFIRMED'
|
||
);
|
||
|
||
$cta_client_url='/public/client/mes_rendez_vous.php';
|
||
$cta_prat_url ='/public/praticien/rendez_vous.php?id='.(int)$rdv['rdv_id'];
|
||
$cta_label = $translations['consultez_espace_pour_details'] ?? 'Voir les détails';
|
||
|
||
// Client — même template propre que pour le praticien
|
||
$subj_c = $translations['email_confirmation_rdv_sujet'] ?? 'Confirmation de rendez-vous';
|
||
|
||
$dataC = [
|
||
'hello' => sprintf($translations['mail_valid_bonjour_nom'] ?? 'Bonjour %s',
|
||
trim(($rdv['prenom_client'] ?? '').' '.($rdv['nom_client'] ?? ''))),
|
||
'intro' => ($translations['votre_rdv_attente'] ?? 'Votre rendez-vous est en attente de confirmation').' '.
|
||
($translations['email_avec'] ?? 'avec').' '.
|
||
trim(($rdv['prenom_praticien'] ?? '').' '.($rdv['nom_praticien'] ?? '')).'.',
|
||
'who_label' => $translations['praticien_label'] ?? 'Praticien',
|
||
'who_value' => trim(($rdv['prenom_praticien'] ?? '').' '.($rdv['nom_praticien'] ?? '')),
|
||
'when' => $when_a,
|
||
'format_line' => ($rdv['lieu'] === 'offline')
|
||
? ($translations['option_cabinet'] ?? 'В кабинете')
|
||
: ($translations['option_online'] ?? 'Онлайн'),
|
||
'address_line'=> ($rdv['lieu'] === 'offline') ? ($addr ?: '') : '',
|
||
'cta_url' => $cta_client_url,
|
||
'cta_label' => $translations['consultez_espace_pour_details'] ?? 'Voir les détails',
|
||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||
];
|
||
|
||
$text_c = email_tpl_text($dataC, $translations);
|
||
$html_c = email_tpl_html($dataC, $translations);
|
||
|
||
send_mail_ics(
|
||
(string)$rdv['email_client'],
|
||
(string)trim(($rdv['prenom_client'] ?? '').' '.($rdv['nom_client'] ?? '')),
|
||
$subj_c, $text_c, $html_c,
|
||
'rdv_'.$uid.'_client.ics', $ics_client, 'REQUEST'
|
||
);
|
||
|
||
// Praticien
|
||
$subj_p = ($translations['nouveau_rdv_avec'] ?? 'Nouveau rendez-vous avec').' '.($rdv['prenom_client'].' '.$rdv['nom_client']);
|
||
|
||
$dataP = [
|
||
'hello' => ($translations['email_bonjour_dr'] ?? 'Bonjour').' '.($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
|
||
'intro' => ($translations['nouveau_rdv_pris_avec'] ?? 'Un nouveau rendez-vous a été pris.'),
|
||
'who_label' => $translations['client_label'] ?? 'Client',
|
||
'who_value' => ($rdv['prenom_client'].' '.$rdv['nom_client']),
|
||
'when' => $when_a,
|
||
'format_line' => ($rdv['lieu'] === 'offline')
|
||
? ($translations['option_cabinet'] ?? 'В кабинете')
|
||
: ($translations['option_online'] ?? 'Онлайн'),
|
||
'address_line'=> ($rdv['lieu'] === 'offline') ? ($addr ?: '') : '',
|
||
'cta_url' => $cta_prat_url,
|
||
'cta_label' => $cta_label,
|
||
'footer' => $translations['merci_simple'] ?? 'Merci.',
|
||
];
|
||
|
||
$text_p = email_tpl_text($dataP, $translations);
|
||
$html_p = email_tpl_html($dataP, $translations);
|
||
|
||
send_mail_ics(
|
||
(string)$rdv['email_praticien'],
|
||
(string)($rdv['prenom_praticien'].' '.$rdv['nom_praticien']),
|
||
$subj_p, $text_p, $html_p,
|
||
'rdv_'.$uid.'_praticien.ics', $ics_prat, 'REQUEST'
|
||
);
|
||
|
||
// WhatsApp côté praticien (optionnel)
|
||
if (function_exists('Sms\\notify_rdv_en_attente')) {
|
||
$waTo = (string)($rdv['tel_praticien'] ?? '');
|
||
if ($waTo) {
|
||
try { \Sms\notify_rdv_en_attente($waTo, trim($rdv['prenom_client'].' '.$rdv['nom_client']), $start->format('d/m/Y H:i'), (int)$rdv['rdv_id']); }
|
||
catch(Throwable $e){ error_log('WA notify error: '.$e->getMessage()); }
|
||
}
|
||
}
|
||
} catch(Throwable $e){
|
||
error_log('cabinet valider_rdv bg: '.$e->getMessage());
|
||
}
|
||
exit;
|
||
}
|
||
|
||
http_response_code(400);
|
||
exit('Bad request');
|
||
}
|
||
|
||
/* ================== Mode GET : UI ================== */
|
||
$title = $translations['titre_validation_secretaire'] ?? 'Validation du rendez-vous (secrétaire)';
|
||
?>
|
||
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title><?= h($title) ?></title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<script src="/js/sweetalert2@11.js"></script>
|
||
<style>
|
||
body{margin:0;background:#8CA9FF;font-family:system-ui,Segoe UI,Roboto,Arial}
|
||
.wrap{max-width:720px;margin:32px auto;padding:16px}
|
||
.card{background:#fff;border-radius:18px;box-shadow:0 12px 36px rgba(0,0,0,.08);padding:20px}
|
||
.row{display:grid;gap:12px}
|
||
label{font-weight:600}
|
||
input,select{width:97%;padding:10px;border:1px solid #e2e8f0;border-radius:10px}
|
||
.btn{display:inline-block;border-radius:999px;padding:10px 16px;text-decoration:none;font-weight:700}
|
||
.btn-primary{background:#E03C3F;color:#fff}
|
||
.btn-outline{background:#fff;border:1px solid #e2e8f0;color:#38378B}
|
||
.actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
|
||
.muted{color:#6b7280}
|
||
.hint{font-size:12px;color:#6b7280}
|
||
.pill{display:inline-block;padding:2px 8px;border-radius:999px;border:1px solid #e5e7eb;font-size:12px}
|
||
.res-item{padding:8px 10px;border:1px solid #e5e7eb;border-radius:10px;margin-bottom:8px;background:#fafafa}
|
||
.res-item .meta{color:#6b7280;font-size:12px}
|
||
.err{color:#E03C3F;font-size:12px;display:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<div class="card">
|
||
<h2 style="margin:0 0 8px"><?= h($title) ?></h2>
|
||
<p class="muted" style="margin:0 0 18px">
|
||
<?= h(($praticien['prenom'] ?? '').' '.($praticien['nom'] ?? '')) ?> — <?= h($startIso ?: '') ?>
|
||
</p>
|
||
|
||
<?php if (!$praticien_id || !$startIso): ?>
|
||
<div class="row">
|
||
<p><?= h($translations['donnees_manquantes_reservation'] ?? 'Données manquantes pour la réservation.') ?></p>
|
||
<div class="actions">
|
||
<a class="btn btn-outline" href="/public/cabinet/rendez_vous.php?praticien_id=<?= (int)$praticien_id ?>"><?= h($translations['retour_a_la_liste'] ?? 'Retour à la liste') ?></a>
|
||
</div>
|
||
</div>
|
||
<?php elseif ($client_id > 0): ?>
|
||
<!-- On a déjà un client -> lance validation directe -->
|
||
<form id="f-validate" method="post">
|
||
<input type="hidden" name="csrf" value="<?= h($CSRF) ?>">
|
||
<input type="hidden" name="action" value="validate">
|
||
<input type="hidden" name="praticien_id" value="<?= (int)$praticien_id ?>">
|
||
<input type="hidden" name="start" value="<?= h($startIso) ?>">
|
||
<input type="hidden" name="client_id" value="<?= (int)$client_id ?>">
|
||
<input type="hidden" name="mode_rdv" value="<?= h($mode_rdv_in ?? $c_online) ?>">
|
||
<p class="muted"><?= h($translations['chargement_validation'] ?? 'Подождите, идёт подтверждение...') ?></p>
|
||
</form>
|
||
<script>
|
||
(function(){
|
||
const f = document.getElementById('f-validate');
|
||
fetch(location.pathname, {
|
||
method:'POST',
|
||
headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
|
||
body: new URLSearchParams(new FormData(f))
|
||
}).then(r=>r.json()).then(d=>{
|
||
document.querySelector('.card').innerHTML = d.html || '<p>Erreur</p>';
|
||
}).catch(()=>{ document.querySelector('.card').innerHTML = '<p>Erreur réseau</p>'; });
|
||
})();
|
||
</script>
|
||
|
||
<?php else: ?>
|
||
<!-- Sélection / création client -->
|
||
<?php
|
||
// clients déjà liés à ce praticien
|
||
$cli = $pdo->prepare("
|
||
SELECT c.id, c.prenom, c.nom, c.email, c.telephone
|
||
FROM client_dossiers d
|
||
JOIN client c ON c.id = d.client_id
|
||
WHERE d.praticien_id = ?
|
||
ORDER BY c.prenom, c.nom
|
||
");
|
||
$cli->execute([$praticien_id]);
|
||
$clients = $cli->fetchAll(PDO::FETCH_ASSOC);
|
||
?>
|
||
<div class="row" style="margin-bottom:16px">
|
||
<label><?= h($translations['selectionner_client'] ?? 'Sélectionner un client') ?></label>
|
||
<select id="clientSelect">
|
||
<option value="0"><?= h($translations['client_existant_placeholder'] ?? '— Choisir un client existant —') ?></option>
|
||
<?php foreach ($clients as $c): ?>
|
||
<option value="<?= (int)$c['id'] ?>">
|
||
<?= h(trim(($c['prenom'] ?? '').' '.($c['nom'] ?? ''))) ?>
|
||
<?php if(!empty($c['email'])) echo ' — '.h($c['email']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Recherche globale des clients plateforme non liés à ce praticien -->
|
||
<div class="row" style="border-top:1px dashed #e2e8f0;padding-top:12px">
|
||
<label><?= h($translations['recherche_client_plateforme'] ?? 'Rechercher un client (plateforme)') ?></label>
|
||
<input type="text" id="searchClient" placeholder="<?= h($translations['placeholder_recherche_client'] ?? 'Email, Nom Prénom ou Téléphone') ?>">
|
||
<div class="hint"><?= h($translations['hint_clients_non_lies'] ?? 'Astuce : les résultats n’incluent pas les clients déjà liés à ce praticien.') ?></div>
|
||
<div id="searchResults"></div>
|
||
</div>
|
||
|
||
<div class="row" style="border-top:1px dashed #e2e8f0;padding-top:12px">
|
||
<label><?= h($translations['creer_client'] ?? 'Créer un nouveau client') ?></label>
|
||
<div class="row">
|
||
<input type="text" id="newPrenom" placeholder="<?= h($translations['prenom'] ?? 'Prénom') ?>">
|
||
<input type="text" id="newNom" placeholder="<?= h($translations['nom'] ?? 'Nom') ?>">
|
||
<input type="email" id="newEmail" placeholder="<?= h($translations['email'] ?? 'Email') ?>">
|
||
<small id="emailError" class="err"><?= h($translations['email_deja_utilise'] ?? 'Cet email est déjà utilisé.') ?></small>
|
||
<input type="text" id="newTel" placeholder="<?= h($translations['telephone'] ?? 'Téléphone') ?>">
|
||
<input type="date" id="newDob" placeholder="<?= h($translations['date_naissance'] ?? 'Date de naissance') ?>">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="actions">
|
||
<a href="#" class="btn btn-primary" id="btn-continue"><?= h($translations['btn_continuer'] ?? 'Continuer') ?></a>
|
||
<a href="/public/cabinet/rendez_vous.php?praticien_id=<?= (int)$praticien_id ?>" class="btn btn-outline"><?= h($translations['btn_annuler'] ?? 'Annuler') ?></a>
|
||
</div>
|
||
|
||
<script>
|
||
(function(){
|
||
const csrf = <?= json_encode($CSRF) ?>;
|
||
const praticien_id = <?= (int)$praticien_id ?>;
|
||
const start = <?= json_encode($startIso) ?>;
|
||
const mode_rdv = <?= json_encode($mode_rdv_in ?? $c_online) ?>;
|
||
|
||
function validateWith(client_id){
|
||
const body = new URLSearchParams({ action:'validate', csrf, praticien_id, start, client_id, mode_rdv });
|
||
fetch(location.pathname, { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body })
|
||
.then(r=>r.json()).then(d=>{
|
||
document.querySelector('.card').innerHTML = d.html || '<p>Erreur</p>';
|
||
}).catch(()=>{ Swal.fire('Erreur', 'Réseau', 'error'); });
|
||
}
|
||
|
||
// Recherche dynamique (clients plateforme non liés)
|
||
const search = document.getElementById('searchClient');
|
||
const results = document.getElementById('searchResults');
|
||
let tId = null;
|
||
function renderResults(items){
|
||
results.innerHTML = '';
|
||
if (!items || !items.length) {
|
||
results.innerHTML = '<div class="hint"><?= h($translations['cl_aucun_resultat'] ?? 'Aucun résultat') ?></div>';
|
||
return;
|
||
}
|
||
items.forEach(it=>{
|
||
const div = document.createElement('div');
|
||
div.className = 'res-item';
|
||
const name = [it.prenom||'', it.nom||''].join(' ').trim();
|
||
const adr = [it.adresse||'', [it.code_postal||'', it.ville||''].join(' ').trim()].filter(Boolean).join(', ');
|
||
div.innerHTML =
|
||
'<div><strong>'+ (name || '—') +'</strong> <span class="pill">'+ (it.email || '—') +'</span></div>'+
|
||
'<div class="meta"><?= h($translations['telephone'] ?? 'Téléphone') ?>: '+ (it.telephone || '—') + (adr? ' • '+adr : '') +'</div>'+
|
||
'<div style="margin-top:6px"><button class="btn btn-outline" data-id="'+ it.id +'"><?= h($translations['choisir'] ?? 'Choisir') ?></button></div>';
|
||
results.appendChild(div);
|
||
});
|
||
results.querySelectorAll('button[data-id]').forEach(btn=>{
|
||
btn.addEventListener('click', (e)=>{
|
||
e.preventDefault();
|
||
const id = parseInt(btn.getAttribute('data-id'), 10)||0;
|
||
if (id>0) validateWith(id);
|
||
});
|
||
});
|
||
}
|
||
function doSearch(q){
|
||
if (!q || q.length < 2) { results.innerHTML = ''; return; }
|
||
const body = new URLSearchParams({ action:'search_client', csrf, praticien_id, q });
|
||
fetch(location.pathname, { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body })
|
||
.then(r => r.json())
|
||
.then(d => {
|
||
if (d.ok) { renderResults(d.items || []); }
|
||
else { results.innerHTML = '<div class="hint">'+ (d.msg || 'Erreur') +'</div>'; }
|
||
})
|
||
.catch(() => { results.innerHTML = '<div class="hint">Erreur réseau</div>'; });
|
||
}
|
||
|
||
search.addEventListener('input', function(){
|
||
if (tId) clearTimeout(tId);
|
||
tId = setTimeout(()=>doSearch(search.value.trim()), 250);
|
||
});
|
||
|
||
// Vérif e-mail live (utilise /verifier_email.php à la racine)
|
||
const emailInput = document.getElementById('newEmail');
|
||
const emailErr = document.getElementById('emailError');
|
||
let emailOk = true;
|
||
function checkEmail(){
|
||
const v = (emailInput.value||'').trim();
|
||
emailOk = true;
|
||
emailErr.style.display = 'none';
|
||
if (!v.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) return;
|
||
fetch('/verifier_email.php', {
|
||
method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
|
||
body: 'email='+encodeURIComponent(v)
|
||
}).then(r=>r.text()).then(tx=>{
|
||
if ((tx||'').trim()==='existe') {
|
||
emailOk = false;
|
||
emailErr.style.display = 'block';
|
||
}
|
||
}).catch(()=>{ /* silence */ });
|
||
}
|
||
emailInput.addEventListener('input', checkEmail);
|
||
|
||
document.getElementById('btn-continue').addEventListener('click', function(e){
|
||
e.preventDefault();
|
||
const selected = parseInt(document.getElementById('clientSelect').value, 10) || 0;
|
||
if (selected > 0) return validateWith(selected);
|
||
|
||
const prenom = document.getElementById('newPrenom').value.trim();
|
||
const nom = document.getElementById('newNom').value.trim();
|
||
const email = document.getElementById('newEmail').value.trim();
|
||
const tel = document.getElementById('newTel').value.trim();
|
||
const dob = document.getElementById('newDob').value.trim();
|
||
|
||
if (!prenom || !nom || !email || !tel) {
|
||
Swal.fire('<?= addslashes($translations['donnees_manquantes_reservation'] ?? 'Champs manquants') ?>','','warning');
|
||
return;
|
||
}
|
||
if (!emailOk) {
|
||
Swal.fire('<?= addslashes($translations['email_deja_utilise'] ?? 'Cet email est déjà utilisé.') ?>','','warning');
|
||
return;
|
||
}
|
||
|
||
const body = new URLSearchParams({ action:'create_client', csrf, praticien_id, nom, prenom, email, telephone:tel, date_naissance:dob });
|
||
fetch(location.pathname, { method:'POST', headers:{'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'}, body })
|
||
.then(r=>r.json()).then(d=>{
|
||
if (!d.ok) throw new Error(d.msg || 'Erreur');
|
||
validateWith(d.client_id);
|
||
}).catch(err=>{
|
||
Swal.fire('Erreur', err.message || 'Erreur', 'error');
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
|