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