Files
maria_uslugi/public/praticien/clients/fiche.php
T
2026-03-05 10:25:39 +01:00

288 lines
12 KiB
PHP

<?php
require_once __DIR__ . '/../../includes/init_praticien.php';
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function humanSize($bytes){
$u=['B','KB','MB','GB']; $i=0;
while($bytes>=1024 && $i<count($u)-1){ $bytes/=1024; $i++; }
return number_format($bytes, $i?1:0, ',', ' ').' '.$u[$i];
}
function fmtDT(?string $s, string $format='d/m/Y H:i'): string {
$s = trim((string)$s);
if ($s === '' || $s === '0000-00-00' || $s === '0000-00-00 00:00:00') return '—';
try { return (new DateTime($s))->format($format); } catch(Throwable $e){ return '—'; }
}
$praticien_id = (int)($_SESSION['reference_id'] ?? 0);
$dossier_id = (int)($_GET['dossier_id'] ?? 0);
if ($praticien_id<=0 || $dossier_id<=0) { header('Location: index.php'); exit; }
/* ----- Dossier + Client (vérif propriétaire) ----- */
$st = $pdo->prepare("
SELECT
d.id, d.praticien_id, d.client_id, d.tags,
c.nom, c.prenom, c.email, c.telephone, c.adresse, c.ville, c.code_postal, c.date_naissance
FROM client_dossiers d
JOIN client c ON c.id = d.client_id
WHERE d.id = :d AND d.praticien_id = :p AND d.archived_at IS NULL
LIMIT 1
");
$st->execute([':d'=>$dossier_id, ':p'=>$praticien_id]);
$dos = $st->fetch(PDO::FETCH_ASSOC);
if(!$dos){ header('Location: index.php'); exit; }
/* ----- Historique RDV ----- */
$stR = $pdo->prepare("
SELECT date_rdv, duree_minutes, lieu, statut
FROM rendez_vous
WHERE praticien_id = :p AND client_id = :c AND (statut = 'terminé' OR statut = 'confirmé')
ORDER BY date_rdv DESC
LIMIT 100
");
$stR->execute([':p'=>$praticien_id, ':c'=>$dos['client_id']]);
$rdvs = $stR->fetchAll(PDO::FETCH_ASSOC);
/* ----- Notes + fichiers ----- */
$stN = $pdo->prepare("
SELECT s.*
FROM client_suivi s
WHERE s.dossier_id = :d
ORDER BY s.date_rdv DESC, s.id DESC
");
$stN->execute([':d'=>$dossier_id]);
$notes = $stN->fetchAll(PDO::FETCH_ASSOC);
$filesByEntry = [];
if ($notes) {
$ids = array_column($notes, 'id');
$ph = implode(',', array_fill(0, count($ids), '?'));
$stF = $pdo->prepare("
SELECT f.id, f.entry_id, f.path, f.mime, f.size_bytes
FROM client_suivi_files f
WHERE f.entry_id IN ($ph)
ORDER BY f.id ASC
");
$stF->execute($ids);
while($f = $stF->fetch(PDO::FETCH_ASSOC)){
$filesByEntry[(int)$f['entry_id']][] = $f;
}
}
/* ----- CSRF pour actions (si besoin) ----- */
if (empty($_SESSION['cl_csrf'])) $_SESSION['cl_csrf'] = bin2hex(random_bytes(32));
$CSRF = $_SESSION['cl_csrf']; ?>
<form id="csrf-form" class="d-none">
<input type="hidden" name="csrf" value="<?= h($CSRF) ?>">
</form>
<?php
/* Initiales avatar */
$initiales = mb_strtoupper(
mb_substr((string)$dos['prenom'],0,1,'UTF-8').
mb_substr((string)$dos['nom'],0,1,'UTF-8'),
'UTF-8'
);
$fullName = trim(($dos['prenom'] ?? '').' '.($dos['nom'] ?? ''));
require_once __DIR__ . '/../../includes/header.php';
?>
<div class="main1">
<div class="container py-5">
<div class="shadow p-4 rounded bg-light">
<main class="container my-4">
<div class="row g-3">
<!-- ===== COLONNE GAUCHE : Identité client ===== -->
<div class="col-12">
<div class="card p-3 shadow-sm">
<h4 class="mb-3"><?= h($translations['cl_identite'] ?? 'Identité') ?></h4>
<div class="d-flex align-items-center gap-3">
<!--<div class="avatar"><?= h($initiales) ?></div>-->
<div>
<div class="fw-bold fs-5"><?= h($fullName) ?></div>
<div class="text-muted">
<?= h($dos['email']??'') ?><?= !empty($dos['telephone']) ? ' - '.h($dos['telephone']) : '' ?>
</div>
<?php
$addr = trim(($dos['adresse']??'').' '.($dos['code_postal']??'').' '.($dos['ville']??''));
if ($addr !== ''):
?>
<div class="text-muted"><?= h($addr) ?></div>
<?php endif; ?>
<?php if (!empty($dos['date_naissance'])): ?>
<div class="text-muted">
<?= h($translations['label_date_naissance'] ?? 'Date de naissance') ?> :
<?= fmtDT($dos['date_naissance'], 'd/m/Y') ?>
</div>
<?php endif; ?>
<?php if (!empty($dos['tags'])): ?>
<div class="mt-2"><span class="badge bg-light text-dark"><?= h($dos['tags']) ?></span></div>
<?php endif; ?>
</div>
</div>
<div class="mt-3 d-flex gap-2">
<a class="btn btn-outline-secondary" href="ajouter_note.php?dossier_id=<?= (int)$dossier_id ?>">
<?= h($translations['cl_btn_ajouter_note'] ?? 'Ajouter une note') ?>
</a>
<a class="btn btn-outline-secondary" href="index.php">
<?= h($translations['cl_pagination_prec'] ?? 'Retour') ?>
</a>
</div>
</div>
</div>
<!-- ===== COLONNE DROITE : RDV + Notes ===== -->
<div class="col-12">
<!-- Notes de suivi -->
<div class="card p-3 shadow-sm">
<h4 class="mb-3"><?= h($translations['cl_notes_suivi'] ?? 'Notes de suivi') ?></h4>
<?php if(!$notes): ?>
<div class="text-muted"><?= h($translations['cl_aucun_resultat'] ?? 'Aucun résultat.') ?></div>
<?php else: ?>
<?php foreach($notes as $n):
$files = $filesByEntry[(int)$n['id']] ?? [];
?>
<div class="mb-3 p-3 border rounded-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<div class="fw-bold"><?= h($n['titre']) ?></div>
<div class="text-muted small">
<?= h(fmtDT($n['date_rdv'])) ?>
· <?= h($translations['cl_type_rdv'] ?? 'Type') ?>: <?= h($n['type_rdv']) ?>
<?php if(!empty($n['duree_minutes'])): ?>
· <?= h($translations['cl_duree'] ?? 'Durée') ?>: <?= (int)$n['duree_minutes'] ?> min
<?php endif; ?>
<?php if(!empty($n['prochaine_action'])): ?>
<br><b><?= h($translations['cl_prochaine_action'] ?? 'Prochaine action') ?>: <?= h($n['prochaine_action']) ?></b>
<?php endif; ?>
</div>
</div>
</div>
<div class="mt-2"><?= nl2br(h($n['notes'])) ?></div>
<?php if ($files): ?>
<div class="mt-3">
<div class="fw-semibold mb-1">📎 <?= h($translations['pieces_jointes'] ?? 'Pièces jointes') ?></div>
<ul class="list-unstyled mb-0">
<?php foreach($files as $f):
$name = basename((string)$f['path']);
?>
<li class="d-flex align-items-center gap-2 mb-1">
<span class="small"><?= h($name) ?></span>
<?php if(!empty($f['size_bytes'])): ?>
<span class="text-muted small">· <?= h(humanSize((int)$f['size_bytes'])) ?></span>
<?php endif; ?>
<a href="download.php?id=<?= (int)$f['id'] ?>" class="btn btn-outline-secondary btn-sm" aria-label="Télécharger">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<path d="M.5 9.9a.5.5 0 0 1 .5.5V13a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V10.4a.5.5 0 0 1 1 0V13a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V10.4a.5.5 0 0 1 .5-.5z"/>
<path d="M7.646 10.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 9.293V1.5a.5.5 0 0 0-1 0v7.793L5.354 7.146a.5.5 0 1 0-.708.708l3 3z"/>
</svg>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<br>
<a class="btn btn-outline-secondary"
href="editer_note.php?note_id=<?= (int)$n['id'] ?>">
<?= h($translations['modifier'] ?? 'Modifier') ?>
</a>
<!-- Ajout de la classe attendue + data-note existant -->
<button type="button"
class="btn btn-outline-danger btn--no-shadow btn-del-note"
data-note="<?= (int)$n['id'] ?>">
<?= h($translations['supprimer'] ?? 'Supprimer') ?>
</button>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<br>
<!-- Historique des rendez-vous -->
<div class="card p-3 shadow-sm mb-3">
<h4 class="mb-3"><?= h($translations['cl_histo_rdv'] ?? 'Historique des rendez-vous') ?></h4>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th><?= h($translations['date_label'] ?? 'Date') ?></th>
<th><?= h($translations['cl_type_rdv'] ?? 'Type') ?></th>
<th><?= h($translations['cl_duree'] ?? 'Durée') ?></th>
<th><?= h($translations['cl_col_tags'] ?? 'Statut') ?></th>
</tr>
</thead>
<tbody>
<?php if(!$rdvs): ?>
<tr><td colspan="4" class="text-muted"><?= h($translations['cl_aucun_resultat'] ?? 'Aucun résultat.') ?></td></tr>
<?php else: foreach($rdvs as $r): ?>
<tr>
<td><?= h(fmtDT($r['date_rdv'])) ?></td>
<td><?= h($r['lieu']==='online'
? ($translations['cl_type_online']??'En ligne')
: ($r['lieu']==='offline' ? ($translations['cl_type_cabinet']??'Au cabinet') : ($translations['cl_type_autre']??'Autre'))) ?></td>
<td><?= (int)($r['duree_minutes']??0) ?> min</td>
<td><?= h($r['statut']) ?></td>
</tr>
<?php endforeach; endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
</div>
<footer class="site-footer">
<div class="container">
<p><?= $translations['copyr'] ?></p>
</div>
</footer>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
<script src="/js/sweetalert2@11.js"></script>
<script>
document.addEventListener('click', async (e)=>{
// Plus robuste : on accepte soit .btn-del-note soit tout élément portant data-note
const btn = e.target.closest('.btn-del-note, [data-note]');
if (!btn) return;
const noteId = btn.getAttribute('data-note') || '';
const res = await Swal.fire({
title: <?= json_encode($translations['cl_confirmer_suppression']) ?>,
text: <?= json_encode($translations['cl_action_irreversible']) ?>,
icon: 'warning',
showCancelButton: true,
confirmButtonText: <?= json_encode($translations['btn_supprimer']) ?>,
cancelButtonText: <?= json_encode($translations['btn_annuler']) ?>
});
if (!res.isConfirmed) return;
const fd = new FormData(document.getElementById('csrf-form'));
fd.append('note_id', noteId);
try{
const r = await fetch('ajax_supprimer_note.php', {
method:'POST',
body: fd,
credentials:'same-origin'
});
const j = await r.json();
if (j.ok) {
location.reload();
} else {
Swal.fire('Oops', j.message || 'Erreur', 'error');
}
}catch(err){
Swal.fire('Oops', 'Erreur réseau', 'error');
}
});
</script>