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

+320
View File
@@ -0,0 +1,320 @@
<?php
// /public/cabinet/invitations.php
require_once __DIR__ . '/../includes/init_praticien.php';
if (!function_exists('h')) { function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); } }
$praticien_id = (int)($_SESSION['reference_id'] ?? 0);
if ($praticien_id<=0) { header('Location:/index.php'); exit; }
// CSRF
if (empty($_SESSION['csrf'])) $_SESSION['csrf']=bin2hex(random_bytes(16));
$csrf = $_SESSION['csrf'];
// Handle POST (invite or toggle)
$msg = '';
if (($_SERVER['REQUEST_METHOD']??'')==='POST' && hash_equals($csrf, $_POST['csrf'] ?? '')) {
if (isset($_POST['action']) && $_POST['action']==='invite') {
$email = trim((string)($_POST['email'] ?? ''));
$can_manage_rdv = !empty($_POST['can_manage_rdv']) ? 1 : 0;
$can_edit_slots = !empty($_POST['can_edit_slots']) ? 1 : 0;
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$msg = $translations['inv_email_invalide'] ?? 'Email invalide.';
} else {
// token & expiration
$token = bin2hex(random_bytes(24));
$expires = (new DateTime('+10 days'))->format('Y-m-d H:i:s');
// Insert invitation (role 'secretaire')
$ins = $pdo->prepare("INSERT INTO invitations (email, role, praticien_id, token, status, expires_at, invited_by)
VALUES (:email,'secretaire',:pid,:tk,'envoyee',:exp,:by)");
$ins->execute([
'email'=>$email,
'pid'=>$praticien_id,
'tk'=>$token,
'exp'=>$expires,
'by'=> (int)($_SESSION['compte_id'] ?? 0),
]);
// Stocker droits sélectionnés côté "mémo" (façon simple : dans token via table dédiée ?)
// Ici on ajoute un petit "mémo" côté invitations.meta_json si tu souhaites :
// Si tu veux, ajoute une colonne meta_json JSON dans invitations. Sinon, on appliquera des droits par défaut à l'acceptation, et on pourra basculer ensuite.
// Pour rester 100% compatible avec ta structure actuelle, on applique par défaut can_manage_rdv=1, can_edit_slots=0, puis on laisse l'UI permettre de modifier après acceptation.
// Envoi email (si tu as un mailer)
if (function_exists('uslugi_mail')) {
$acceptUrl = sprintf(
'%s/public/cabinet/accepter_invitation.php?token=%s',
rtrim((isset($_SERVER['REQUEST_SCHEME']) ? $_SERVER['REQUEST_SCHEME'].'://' : '').($_SERVER['HTTP_HOST']??''), '/'),
$token
);
$subject = $translations['inv_mail_sujet']
?? 'Invitation à rejoindre un cabinet sur uslugi.fr';
$body = '
<div style="font-family:Arial,Helvetica,sans-serif;font-size:15px;color:#333;background:#f9f9fb;padding:20px;">
<div style="max-width:600px;margin:0 auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 2px 10px rgba(0,0,0,.05);">
<!-- Titre -->
<div style="background:#38378B;color:#fff;padding:18px 24px;font-size:18px;font-weight:bold;">
<img src="https://uslugi.fr/public/uploads/img/logo_mail.png"
alt="uslugi.fr" style="max-height:30px;display:block;">
</div>
<!-- Contenu -->
<div style="padding:24px;">
<p>'.h($translations['inv_mail_bonjour'] ?? 'Bonjour,').'</p>
<p>'.
h($translations['inv_mail_intro'] ??
'Vous avez été invité(e) à rejoindre le secrétariat d’un praticien sur uslugi.fr.
Grâce à cet accès, vous pourrez gérer les rendez-vous et organiser l’agenda du cabinet.').
'</p>
<p style="margin:24px 0;text-align:center;">
<a href="'.h($acceptUrl).'"
style="background:#E03C3F;color:#fff;text-decoration:none;
padding:12px 22px;border-radius:999px;font-weight:bold;display:inline-block;">
'.h($translations['inv_mail_btn'] ?? 'Accepter l’invitation').'
</a>
</p>
<p>'.
h($translations['inv_mail_validite'] ??
'Ce lien est valide pendant 10 jours. Passé ce délai, il expirera automatiquement.').
'</p>
<p style="font-size:13px;color:#777;margin-top:24px;">
'.h($translations['inv_mail_ignore'] ??
'Si vous n’êtes pas concerné(e) par cette invitation, vous pouvez ignorer cet email.').'
</p>
</div>
<!-- Footer -->
<div style="background:#f1f1f1;color:#555;padding:12px 24px;font-size:13px;text-align:center;">
'.h($translations['inv_mail_footer'] ?? ('© '.date('Y').' uslugi.fr – Tous droits réservés')).'
</div>
</div>
</div>';
uslugi_mail($email, $subject, $body);
}
$msg = $translations['invitation_envoyee'] ?? 'Invitation envoyée.';
}
}
if (isset($_POST['action']) && $_POST['action']==='toggle_rights') {
$spid = (int)($_POST['spid'] ?? 0);
$field = ($_POST['field'] ?? '');
if ($spid>0 && in_array($field,['can_manage_rdv','can_edit_slots','is_active'],true)) {
// secure update scoped to this praticien
$upd = $pdo->prepare("UPDATE secretaire_praticien SET $field = 1 - $field WHERE id=:id AND praticien_id=:pid");
$upd->execute(['id'=>$spid,'pid'=>$praticien_id]);
$msg = $translations['droits_mis_a_jour'] ?? 'Droits mis à jour.';
}
}
}
// DATA: invitations + secretaire_praticien liés à CE praticien
$invitations = $pdo->prepare("SELECT id,email,status,expires_at,created_at FROM invitations WHERE praticien_id=:pid ORDER BY created_at DESC");
$invitations->execute(['pid'=>$praticien_id]);
$invRows = $invitations->fetchAll(PDO::FETCH_ASSOC);
// secrétaires liées
$secs = $pdo->prepare("
SELECT sp.id, sp.secretaire_user_id, sp.can_manage_rdv, sp.can_edit_slots, sp.is_active, sp.created_at,
c.email
FROM secretaire_praticien sp
JOIN compte c ON c.id = sp.secretaire_user_id
WHERE sp.praticien_id = :pid
ORDER BY sp.created_at DESC
");
$secs->execute(['pid'=>$praticien_id]);
$secRows = $secs->fetchAll(PDO::FETCH_ASSOC);
require_once __DIR__ . '/../includes/header.php';
?>
<div class="main1">
<div class="container py-5">
<div class="shadow p-4 rounded bg-light">
<h1 class="h3 mb-3"><?= h($translations['secretariat_titre'] ?? 'Secrétariat') ?></h1>
<?php if(!empty($msg)): ?>
<div class="alert alert-info"><?= h($msg) ?></div>
<?php endif; ?>
<div class="card mb-4">
<div class="card-body">
<h2 class="h5 mb-3"><?= h($translations['secretariat_inviter_secretaire'] ?? 'Inviter une secrétaire') ?></h2>
<form method="post" class="row g-3">
<input type="hidden" name="csrf" value="<?= h($csrf) ?>">
<input type="hidden" name="action" value="invite">
<div class="col-md-6">
<label class="form-label"><?= h($translations['inv_email_label'] ?? 'Email de la secrétaire') ?></label>
<input type="email" name="email" class="form-control" required placeholder="email@example.com">
</div>
<div class="col-md-6">
<label class="form-label"><?= h($translations['inv_droits_label'] ?? 'Droits initiaux') ?></label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_manage_rdv" id="c1" checked>
<label class="form-check-label" for="c1"><?= h($translations['droits_peut_gestion_rdv'] ?? 'Gérer les rendez-vous') ?></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="can_edit_slots" id="c2" checked>
<label class="form-check-label" for="c2"><?= h($translations['droits_peut_modifier_slots'] ?? 'Modifier les disponibilités') ?></label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary"><?= h($translations['btn_envoyer_invitation'] ?? 'Envoyer l’invitation') ?></button>
</div>
</form>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h2 class="h6 mb-3"><?= h($translations['inv_invitations_envoyees'] ?? 'Invitations envoyées') ?></h2>
<div class="table-responsive">
<table class="table align-middle mobile-cards1" id="table_rdv1">
<thead><tr>
<th>Email</th><th><?= h($translations['inv_statut'] ?? 'Statut') ?></th>
<th><?= h($translations['inv_expires'] ?? 'Expire le') ?></th>
<th><?= h($translations['inv_envoyee_le'] ?? 'Envoyée le') ?></th>
</tr></thead>
<tbody>
<?php foreach($invRows as $r): ?>
<tr>
<td><?= h($r['email']) ?></td>
<td><?= h($r['status']) ?></td>
<td><?= h($r['expires_at'] ?? '') ?></td>
<td><?= h($r['created_at'] ?? '') ?></td>
</tr>
<?php endforeach; if(!$invRows): ?>
<tr><td colspan="4" class="text-muted"><?= h($translations['inv_aucune'] ?? 'Aucune invitation pour le moment.') ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-5">
<div class="card-body">
<h2 class="h6 mb-3"><?= h($translations['inv_secretaires_liees'] ?? 'Secrétaires liées') ?></h2>
<div class="table-responsive">
<table class="table align-middle mobile-cards" id="table_rdv">
<thead><tr>
<th>Email</th><th><?= h($translations['droits_gestion_rdv'] ?? 'RDV') ?></th><th><?= h($translations['droits_dispos'] ?? 'Dispos') ?></th><th><?= h($translations['etat'] ?? 'État') ?></th>
</tr></thead>
<tbody>
<?php foreach($secRows as $r): ?>
<tr>
<td><?= h($r['email']) ?></td>
<td>
<form method="post" class="d-inline">
<input type="hidden" name="csrf" value="<?= h($csrf) ?>">
<input type="hidden" name="action" value="toggle_rights">
<input type="hidden" name="spid" value="<?= (int)$r['id'] ?>">
<input type="hidden" name="field" value="can_manage_rdv">
<button class="btn btn-sm <?= $r['can_manage_rdv']?'btn-success':'btn-outline-secondary' ?>">
<?= $r['can_manage_rdv'] ? h($translations['actif'] ?? 'Actif') : h($translations['inactif'] ?? 'Inactif') ?>
</button>
</form>
</td>
<td>
<form method="post" class="d-inline">
<input type="hidden" name="csrf" value="<?= h($csrf) ?>">
<input type="hidden" name="action" value="toggle_rights">
<input type="hidden" name="spid" value="<?= (int)$r['id'] ?>">
<input type="hidden" name="field" value="can_edit_slots">
<button class="btn btn-sm <?= $r['can_edit_slots']?'btn-success':'btn-outline-secondary' ?>">
<?= $r['can_edit_slots'] ? h($translations['actif'] ?? 'Actif') : h($translations['inactif'] ?? 'Inactif') ?>
</button>
</form>
</td>
<td>
<form method="post" class="d-inline">
<input type="hidden" name="csrf" value="<?= h($csrf) ?>">
<input type="hidden" name="action" value="toggle_rights">
<input type="hidden" name="spid" value="<?= (int)$r['id'] ?>">
<input type="hidden" name="field" value="is_active">
<button class="btn btn-sm <?= $r['is_active']?'btn-success':'btn-outline-secondary' ?>">
<?= $r['is_active'] ? h($translations['actif'] ?? 'Actif') : h($translations['inactif'] ?? 'Inactif') ?>
</button>
</form>
</td>
</tr>
<?php endforeach; if(!$secRows): ?>
<tr><td colspan="4" class="text-muted"><?= h($translations['inv_aucune_secretaire'] ?? 'Aucune secrétaire liée pour le moment.') ?></td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script defer>
(function(){
function initMobileCards(tbl){
if (!tbl) return;
const heads = Array.from(tbl.querySelectorAll('thead th'))
.map(th => th.textContent.trim());
tbl.querySelectorAll('tbody tr').forEach(tr => {
Array.from(tr.children).forEach((td, i) => {
if (!td.hasAttribute('data-label')) {
td.setAttribute('data-label', heads[i] || '');
}
});
});
}
function ready(fn){
if (document.readyState === 'loading')
document.addEventListener('DOMContentLoaded', fn);
else
fn();
}
ready(function(){
// 👉 liste de toutes les variantes
const selectors = [
'#table_rdv',
'table.mobile-cards',
'#table_rdv1',
'table.mobile-cards1'
];
// 👉 récupérer toutes les tables existantes dans la page
const tables = selectors
.map(sel => document.querySelector(sel))
.filter(Boolean); // retire les null
tables.forEach(initMobileCards);
if (tables.length > 0) {
document.documentElement.classList.add('mc-ready');
}
});
})();
</script>
<footer class="site-footer">
<div class="container">
<p><?= $translations['copyr'] ?? '&copy; '.date('Y') ?></p>
</div>
</footer>