812 lines
29 KiB
PHP
812 lines
29 KiB
PHP
<?php
|
||
require_once __DIR__ . '/../includes/init_praticien.php';
|
||
|
||
$id = $_SESSION['reference_id'] ?? null;
|
||
if (! $id) {
|
||
http_response_code(403);
|
||
exit('Forbidden');
|
||
}
|
||
|
||
$currentLang = $lang ?? 'ru';
|
||
if (! in_array($currentLang, ['ru', 'fr', 'en'], true)) {
|
||
$currentLang = 'ru';
|
||
}
|
||
$isRu = ($currentLang === 'ru');
|
||
|
||
$ro = $isRu ? '' : 'readonly';
|
||
$dis = $isRu ? '' : 'disabled';
|
||
|
||
/**
|
||
* Upsert traduction
|
||
*/
|
||
function upsertPraticienTraduction(PDO $pdo, int $praticienId, string $langue, array $data): void
|
||
{
|
||
$sql = "
|
||
INSERT INTO praticien_traduction (
|
||
praticien_id,
|
||
langue,
|
||
nom_affiche,
|
||
prenom_affiche,
|
||
ville_affichee,
|
||
titre,
|
||
specialite,
|
||
presentation,
|
||
disponibilite,
|
||
statut,
|
||
created_at,
|
||
updated_at
|
||
) VALUES (
|
||
:praticien_id,
|
||
:langue,
|
||
:nom_affiche,
|
||
:prenom_affiche,
|
||
:ville_affichee,
|
||
:titre,
|
||
:specialite,
|
||
:presentation,
|
||
:disponibilite,
|
||
:statut,
|
||
NOW(),
|
||
NOW()
|
||
)
|
||
ON DUPLICATE KEY UPDATE
|
||
nom_affiche = VALUES(nom_affiche),
|
||
prenom_affiche = VALUES(prenom_affiche),
|
||
ville_affichee = VALUES(ville_affichee),
|
||
titre = VALUES(titre),
|
||
specialite = VALUES(specialite),
|
||
presentation = VALUES(presentation),
|
||
disponibilite = VALUES(disponibilite),
|
||
statut = VALUES(statut),
|
||
updated_at = NOW()
|
||
";
|
||
|
||
$stmt = $pdo->prepare($sql);
|
||
$stmt->execute([
|
||
'praticien_id' => $praticienId,
|
||
'langue' => $langue,
|
||
'nom_affiche' => $data['nom_affiche'],
|
||
'prenom_affiche' => $data['prenom_affiche'],
|
||
'ville_affichee' => $data['ville_affichee'],
|
||
'titre' => $data['titre'],
|
||
'specialite' => $data['specialite'],
|
||
'presentation' => $data['presentation'],
|
||
'disponibilite' => $data['disponibilite'],
|
||
'statut' => $data['statut'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Lecture traduction par langue
|
||
*/
|
||
function getPraticienTraduction(PDO $pdo, int $praticienId, string $langue): ?array
|
||
{
|
||
$stmt = $pdo->prepare("
|
||
SELECT *
|
||
FROM praticien_traduction
|
||
WHERE praticien_id = ? AND langue = ?
|
||
LIMIT 1
|
||
");
|
||
$stmt->execute([$praticienId, $langue]);
|
||
$row = $stmt->fetch();
|
||
return $row ?: null;
|
||
}
|
||
|
||
/**
|
||
* Translittération simple cyrillique -> latin
|
||
* Suffisant pour test / affichage.
|
||
*/
|
||
function transliterateToLatin(string $text): string
|
||
{
|
||
$map = [
|
||
'А' => 'A', 'а' => 'a', 'Б' => 'B', 'б' => 'b', 'В' => 'V', 'в' => 'v', 'Г' => 'G', 'г' => 'g',
|
||
'Д' => 'D', 'д' => 'd', 'Е' => 'E', 'е' => 'e', 'Ё' => 'Yo', 'ё' => 'yo', 'Ж' => 'Zh', 'ж' => 'zh',
|
||
'З' => 'Z', 'з' => 'z', 'И' => 'I', 'и' => 'i', 'Й' => 'Y', 'й' => 'y', 'К' => 'K', 'к' => 'k',
|
||
'Л' => 'L', 'л' => 'l', 'М' => 'M', 'м' => 'm', 'Н' => 'N', 'н' => 'n', 'О' => 'O', 'о' => 'o',
|
||
'П' => 'P', 'п' => 'p', 'Р' => 'R', 'р' => 'r', 'С' => 'S', 'с' => 's', 'Т' => 'T', 'т' => 't',
|
||
'У' => 'U', 'у' => 'u', 'Ф' => 'F', 'ф' => 'f', 'Х' => 'Kh', 'х' => 'kh', 'Ц' => 'Ts', 'ц' => 'ts',
|
||
'Ч' => 'Ch', 'ч' => 'ch', 'Ш' => 'Sh', 'ш' => 'sh', 'Щ' => 'Shch', 'щ' => 'shch',
|
||
'Ы' => 'Y', 'ы' => 'y', 'Э' => 'E', 'э' => 'e', 'Ю' => 'Yu', 'ю' => 'yu', 'Я' => 'Ya', 'я' => 'ya',
|
||
'Ь' => '', 'ь' => '', 'Ъ' => '', 'ъ' => '',
|
||
];
|
||
return strtr($text, $map);
|
||
}
|
||
|
||
/**
|
||
* Stub traduction.
|
||
* Pour l’instant :
|
||
* - renvoie le texte tel quel si pas de vrai moteur branché
|
||
* - tu pourras remplacer ici par DeepL/API plus tard
|
||
*/
|
||
function translateText(string $text, string $sourceLang, string $targetLang): string
|
||
{
|
||
$text = trim($text);
|
||
if ($text === '') {
|
||
return '';
|
||
}
|
||
|
||
$sourceLang = strtoupper($sourceLang);
|
||
$targetLang = strtoupper($targetLang);
|
||
|
||
if ($sourceLang === $targetLang) {
|
||
return $text;
|
||
}
|
||
|
||
if (! defined('DEEPL_API_KEY') || DEEPL_API_KEY === '') {
|
||
throw new RuntimeException('Clé API DeepL manquante.');
|
||
}
|
||
|
||
$endpoint = 'https://api-free.deepl.com/v2/translate';
|
||
|
||
$postFields = http_build_query([
|
||
'text' => $text,
|
||
'source_lang' => $sourceLang,
|
||
'target_lang' => $targetLang,
|
||
]);
|
||
|
||
$ch = curl_init($endpoint);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_POSTFIELDS => $postFields,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 20,
|
||
CURLOPT_HTTPHEADER => [
|
||
'Authorization: DeepL-Auth-Key ' . DEEPL_API_KEY,
|
||
'Content-Type: application/x-www-form-urlencoded',
|
||
],
|
||
]);
|
||
|
||
$response = curl_exec($ch);
|
||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$curlErr = curl_error($ch);
|
||
curl_close($ch);
|
||
|
||
if ($response === false || $curlErr) {
|
||
throw new RuntimeException('Erreur réseau DeepL : ' . $curlErr);
|
||
}
|
||
|
||
if ($httpCode < 200 || $httpCode >= 300) {
|
||
throw new RuntimeException('Erreur DeepL HTTP ' . $httpCode . ' : ' . $response);
|
||
}
|
||
|
||
$data = json_decode($response, true);
|
||
|
||
if (
|
||
! is_array($data)
|
||
|| empty($data['translations'])
|
||
|| ! isset($data['translations'][0]['text'])
|
||
) {
|
||
throw new RuntimeException('Réponse DeepL invalide.');
|
||
}
|
||
|
||
return trim($data['translations'][0]['text']);
|
||
}
|
||
|
||
/**
|
||
* Détecte si une traduction est manuelle
|
||
*/
|
||
function isManualTranslation(?array $row): bool
|
||
{
|
||
return isset($row['statut']) && $row['statut'] === 'manuel';
|
||
}
|
||
|
||
/**
|
||
* Synchronise les traductions à partir de la source RU dans praticien
|
||
* - ru toujours sync
|
||
* - fr/en mis à jour seulement si absents ou non manuels
|
||
*/
|
||
function syncTranslationsFromSource(PDO $pdo, array $praticien): void
|
||
{
|
||
$praticienId = (int) $praticien['id'];
|
||
|
||
$ruData = [
|
||
'nom_affiche' => $praticien['nom'] ?? '',
|
||
'prenom_affiche' => $praticien['prenom'] ?? '',
|
||
'ville_affichee' => $praticien['ville'] ?? '',
|
||
'titre' => $praticien['titre'] ?? '',
|
||
'specialite' => $praticien['specialite'] ?? '',
|
||
'presentation' => $praticien['presentation'] ?? '',
|
||
'disponibilite' => $praticien['disponibilite'] ?? '',
|
||
'statut' => 'auto',
|
||
];
|
||
upsertPraticienTraduction($pdo, $praticienId, 'ru', $ruData);
|
||
|
||
foreach (['fr', 'en'] as $langue) {
|
||
$existing = getPraticienTraduction($pdo, $praticienId, $langue);
|
||
|
||
if ($existing && isManualTranslation($existing)) {
|
||
continue;
|
||
}
|
||
|
||
$nomLatin = transliterateToLatin((string) ($praticien['nom'] ?? ''));
|
||
$prenomLatin = transliterateToLatin((string) ($praticien['prenom'] ?? ''));
|
||
|
||
try {
|
||
$data = [
|
||
'nom_affiche' => $nomLatin,
|
||
'prenom_affiche' => $prenomLatin,
|
||
'ville_affichee' => translateText((string) ($praticien['ville'] ?? ''), 'RU', $langue),
|
||
'titre' => translateText((string) ($praticien['titre'] ?? ''), 'RU', $langue),
|
||
'specialite' => translateText((string) ($praticien['specialite'] ?? ''), 'RU', $langue),
|
||
'presentation' => translateText((string) ($praticien['presentation'] ?? ''), 'RU', $langue),
|
||
'disponibilite' => translateText((string) ($praticien['disponibilite'] ?? ''), 'RU', $langue),
|
||
'statut' => 'auto',
|
||
];
|
||
} catch (Throwable $e) {
|
||
error_log('DeepL translation error [' . $langue . '] praticien_id=' . $praticienId . ' : ' . $e->getMessage());
|
||
//die($e->getMessage());
|
||
|
||
$data = [
|
||
'nom_affiche' => $nomLatin,
|
||
'prenom_affiche' => $prenomLatin,
|
||
'ville_affichee' => (string) ($praticien['ville'] ?? ''),
|
||
'titre' => (string) ($praticien['titre'] ?? ''),
|
||
'specialite' => (string) ($praticien['specialite'] ?? ''),
|
||
'presentation' => (string) ($praticien['presentation'] ?? ''),
|
||
'disponibilite' => (string) ($praticien['disponibilite'] ?? ''),
|
||
'statut' => 'erreur',
|
||
];
|
||
}
|
||
|
||
upsertPraticienTraduction($pdo, $praticienId, $langue, $data);
|
||
}
|
||
}
|
||
|
||
// Base praticien
|
||
$stmt = $pdo->prepare("SELECT * FROM praticien WHERE id = ?");
|
||
$stmt->execute([$id]);
|
||
$praticien = $stmt->fetch();
|
||
|
||
if (! $praticien) {
|
||
die(htmlspecialchars($translations['praticien_introuvable'] ?? 'Introuvable.'));
|
||
}
|
||
|
||
// Traduction langue courante
|
||
$traduction = getPraticienTraduction($pdo, (int) $id, $currentLang);
|
||
|
||
// Valeurs formulaire avec fallback
|
||
$formNom = $traduction['nom_affiche'] ?? $praticien['nom'] ?? '';
|
||
$formPrenom = $traduction['prenom_affiche'] ?? $praticien['prenom'] ?? '';
|
||
$formVilleAffichee = $traduction['ville_affichee'] ?? $praticien['ville'] ?? '';
|
||
$formTitre = $traduction['titre'] ?? $praticien['titre'] ?? '';
|
||
$formSpecialite = $traduction['specialite'] ?? $praticien['specialite'] ?? '';
|
||
$formPresentation = $traduction['presentation'] ?? $praticien['presentation'] ?? '';
|
||
$formDispo = $traduction['disponibilite'] ?? $praticien['disponibilite'] ?? '';
|
||
|
||
// ==== POST ====
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
require_post_csrf();
|
||
$titre = trim($_POST['titre'] ?? '');
|
||
$nomAffiche = trim($_POST['nom'] ?? '');
|
||
$prenomAffiche = trim($_POST['prenom'] ?? '');
|
||
$telephone = trim($_POST['telephone'] ?? '');
|
||
$specialite = trim($_POST['specialite'] ?? '');
|
||
$adresse = trim($_POST['adresse'] ?? '');
|
||
$ville = trim($_POST['ville'] ?? '');
|
||
$code_postal = trim($_POST['code_postal'] ?? '');
|
||
$presentation = trim($_POST['presentation'] ?? '');
|
||
$duree_rdv = (int) ($_POST['duree_rdv'] ?? 60);
|
||
$disponibilite = trim($_POST['disponibilite'] ?? '');
|
||
$photo = $praticien['photo'] ?? null;
|
||
|
||
if ($specialite === '' || $presentation === '') {
|
||
die(htmlspecialchars($translations['err_champs_obligatoires'] ?? 'Merci de remplir les champs obligatoires.'));
|
||
}
|
||
|
||
// En FR/EN on ignore tout update des champs globaux même si quelqu’un bidouille le HTML
|
||
if (! $isRu) {
|
||
$telephone = $praticien['telephone'] ?? '';
|
||
$adresse = $praticien['adresse'] ?? '';
|
||
$ville = $praticien['ville'] ?? '';
|
||
$code_postal = $praticien['code_postal'] ?? '';
|
||
$duree_rdv = (int) ($praticien['duree_rdv'] ?? 60);
|
||
}
|
||
|
||
// Upload photo : uniquement RU
|
||
if ($isRu && ! empty($_FILES['photo']['tmp_name'])) {
|
||
$file = $_FILES['photo'];
|
||
$maxSize = 5 * 1024 * 1024;
|
||
|
||
if ($file['size'] > $maxSize) {
|
||
die("Erreur : Le fichier est trop volumineux (max 5 Mo).");
|
||
}
|
||
|
||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||
$mimeType = $finfo->file($file['tmp_name']);
|
||
$allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||
|
||
if (! in_array($mimeType, $allowedMimeTypes, true)) {
|
||
die("Erreur : Type de fichier non autorisé (Seuls JPG, PNG, GIF et WEBP sont acceptés).");
|
||
}
|
||
|
||
$nom_nettoye = strtolower(preg_replace('/[^A-Za-z0-9]/', '', transliterateToLatin($nomAffiche)));
|
||
$prenom_nettoye = strtolower(preg_replace('/[^A-Za-z0-9]/', '', transliterateToLatin($prenomAffiche)));
|
||
$nouveau_nom_fichier = $nom_nettoye . '_' . $prenom_nettoye . '_' . time() . '.png';
|
||
$chemin_destination = __DIR__ . '/../uploads/' . $nouveau_nom_fichier;
|
||
|
||
$image_source = @imagecreatefromstring(file_get_contents($file['tmp_name']));
|
||
if (! $image_source) {
|
||
die("Erreur : Le fichier image semble corrompu.");
|
||
}
|
||
|
||
[$largeur_orig, $hauteur_orig] = getimagesize($file['tmp_name']);
|
||
$largeur_max = 800;
|
||
|
||
if ($largeur_orig > $largeur_max) {
|
||
$ratio = $largeur_max / $largeur_orig;
|
||
$nouvelle_largeur = $largeur_max;
|
||
$nouvelle_hauteur = (int) ($hauteur_orig * $ratio);
|
||
} else {
|
||
$nouvelle_largeur = $largeur_orig;
|
||
$nouvelle_hauteur = $hauteur_orig;
|
||
}
|
||
|
||
$image_dest = imagecreatetruecolor($nouvelle_largeur, $nouvelle_hauteur);
|
||
imagealphablending($image_dest, false);
|
||
imagesavealpha($image_dest, true);
|
||
|
||
imagecopyresampled(
|
||
$image_dest,
|
||
$image_source,
|
||
0, 0, 0, 0,
|
||
$nouvelle_largeur,
|
||
$nouvelle_hauteur,
|
||
$largeur_orig,
|
||
$hauteur_orig
|
||
);
|
||
|
||
if (imagepng($image_dest, $chemin_destination, 7)) {
|
||
if (! empty($praticien['photo'])) {
|
||
$ancien_chemin = __DIR__ . '/../uploads/' . $praticien['photo'];
|
||
if (file_exists($ancien_chemin)) {
|
||
unlink($ancien_chemin);
|
||
}
|
||
}
|
||
$photo = $nouveau_nom_fichier;
|
||
}
|
||
|
||
imagedestroy($image_source);
|
||
imagedestroy($image_dest);
|
||
}
|
||
|
||
$pdo->beginTransaction();
|
||
|
||
try {
|
||
if ($isRu) {
|
||
$siret = preg_replace('/\D/', '', $_POST['siret'] ?? '');
|
||
|
||
if (strlen($siret) !== 14) {
|
||
throw new RuntimeException($translations['siret_format_invalide'] ?? 'SIRET invalide (14 chiffres).');
|
||
}
|
||
|
||
$luhnOk = (function ($num) {
|
||
$sum = 0;
|
||
$alt = false;
|
||
for ($i = strlen($num) - 1; $i >= 0; $i--) {
|
||
$n = (int) $num[$i];
|
||
if ($alt) {
|
||
$n *= 2;
|
||
if ($n > 9) {
|
||
$n -= 9;
|
||
}
|
||
|
||
}
|
||
$sum += $n;
|
||
$alt = ! $alt;
|
||
}
|
||
return $sum % 10 === 0;
|
||
})($siret);
|
||
|
||
if (! $luhnOk) {
|
||
throw new RuntimeException($translations['siret_format_invalide'] ?? 'SIRET invalide (Luhn).');
|
||
}
|
||
|
||
$chk = $pdo->prepare("SELECT id FROM praticien WHERE siret = :s AND id <> :id LIMIT 1");
|
||
$chk->execute(['s' => $siret, 'id' => $id]);
|
||
if ($chk->fetch()) {
|
||
throw new RuntimeException($translations['err_siret_deja_utilise'] ?? 'Ce SIRET est déjà utilisé.');
|
||
}
|
||
|
||
$stmt = $pdo->prepare("
|
||
UPDATE praticien SET
|
||
titre = :titre,
|
||
nom = :nom,
|
||
prenom = :prenom,
|
||
telephone = :telephone,
|
||
specialite = :specialite,
|
||
adresse = :adresse,
|
||
ville = :ville,
|
||
code_postal = :code_postal,
|
||
presentation = :presentation,
|
||
photo = :photo,
|
||
disponibilite = :disponibilite,
|
||
duree_rdv = :duree_rdv,
|
||
siret = :siret,
|
||
langue_source = 'ru'
|
||
WHERE id = :id
|
||
");
|
||
$stmt->execute([
|
||
'titre' => $titre,
|
||
'nom' => $nomAffiche,
|
||
'prenom' => $prenomAffiche,
|
||
'telephone' => $telephone,
|
||
'specialite' => $specialite,
|
||
'adresse' => $adresse,
|
||
'ville' => $ville,
|
||
'code_postal' => $code_postal,
|
||
'presentation' => $presentation,
|
||
'photo' => $photo,
|
||
'disponibilite' => $disponibilite,
|
||
'duree_rdv' => $duree_rdv,
|
||
'siret' => $siret,
|
||
'id' => $id,
|
||
]);
|
||
|
||
// Recharge la source après update
|
||
$stmt = $pdo->prepare("SELECT * FROM praticien WHERE id = ?");
|
||
$stmt->execute([$id]);
|
||
$praticien = $stmt->fetch();
|
||
|
||
if (! $praticien) {
|
||
throw new RuntimeException('Praticien introuvable après mise à jour.');
|
||
}
|
||
|
||
syncTranslationsFromSource($pdo, $praticien);
|
||
} else {
|
||
upsertPraticienTraduction($pdo, (int) $id, $currentLang, [
|
||
'nom_affiche' => $nomAffiche,
|
||
'prenom_affiche' => $prenomAffiche,
|
||
'ville_affichee' => $traduction['ville_affichee'] ?? $praticien['ville'] ?? '',
|
||
'titre' => $titre,
|
||
'specialite' => $specialite,
|
||
'presentation' => $presentation,
|
||
'disponibilite' => $disponibilite,
|
||
'statut' => 'manuel',
|
||
]);
|
||
}
|
||
|
||
$pdo->commit();
|
||
} catch (Throwable $e) {
|
||
if ($pdo->inTransaction()) {
|
||
$pdo->rollBack();
|
||
}
|
||
die(htmlspecialchars($e->getMessage()));
|
||
}
|
||
|
||
header('Location: /public/praticien/informations.php');
|
||
exit;
|
||
}
|
||
|
||
$PAGE_TITLE = $translations['modifier_infos_title'];
|
||
$HEAD_EXTRA = <<<HTML
|
||
<style>
|
||
.message-erreur {
|
||
color: red;
|
||
font-size: 0.9em;
|
||
display: none;
|
||
margin-bottom: 10px;
|
||
}
|
||
</style>
|
||
HTML;
|
||
|
||
require_once __DIR__ . '/../includes/header.php';
|
||
?>
|
||
|
||
<div class="main1">
|
||
<div class="container py-5">
|
||
<form id="form-praticien-edit" method="POST" enctype="multipart/form-data" class="shadow p-4 rounded bg-light">
|
||
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(ensure_csrf_token(), ENT_QUOTES, 'UTF-8'); ?>">
|
||
<h2 class="text-center mb-4"><?php echo htmlspecialchars($translations['modifier_infos_title']) ?></h2>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_nom']) ?> *</label>
|
||
<input type="text" name="nom" class="form-control" value="<?php echo htmlspecialchars($formNom) ?>" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_prenom']) ?> *</label>
|
||
<input type="text" name="prenom" class="form-control" value="<?php echo htmlspecialchars($formPrenom) ?>" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_telephone']) ?> *</label>
|
||
<input type="tel" name="telephone" id="telephone" class="form-control"
|
||
value="<?php echo htmlspecialchars($praticien['telephone'] ?? '') ?>"
|
||
required
|
||
placeholder="+33600000000"
|
||
pattern="^\+?[1-9]\d{6,14}$"
|
||
title="<?php echo htmlspecialchars($translations['telephone_format_message'] ?? '') ?>"
|
||
<?php echo $ro; ?>>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['placeholder_siret']) ?> *</label>
|
||
<input type="text"
|
||
name="siret"
|
||
id="siret"
|
||
class="form-control"
|
||
value="<?php echo htmlspecialchars($praticien['siret'] ?? '') ?>"
|
||
required
|
||
maxlength="17"
|
||
inputmode="numeric"
|
||
pattern="[0-9 ]+"
|
||
>
|
||
<div id="siret-feedback" class="message-erreur" style="display:none;"></div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_specialite']) ?> *</label>
|
||
<input type="text" name="specialite" class="form-control" value="<?php echo htmlspecialchars($formSpecialite) ?>" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['tarif_consultation']) ?></label>
|
||
<input type="text" name="titre" class="form-control" value="<?php echo htmlspecialchars($formTitre) ?>">
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['moyens_payement']) ?></label>
|
||
<input type="text" name="disponibilite" class="form-control" value="<?php echo htmlspecialchars($formDispo) ?>">
|
||
</div>
|
||
|
||
<div class="mb-3 position-relative">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_adresse']) ?></label>
|
||
<textarea name="adresse" id="adresse" class="form-control" <?php echo $ro; ?>><?php echo htmlspecialchars($praticien['adresse'] ?? '') ?></textarea>
|
||
<ul id="suggestions"></ul>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_ville']) ?></label>
|
||
<input type="text" name="ville" id="ville" class="form-control"
|
||
value="<?php echo htmlspecialchars($praticien['ville'] ?? '') ?>" readonly>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_code_postal']) ?></label>
|
||
<input type="text" name="code_postal" id="code_postal" class="form-control"
|
||
value="<?php echo htmlspecialchars($praticien['code_postal'] ?? '') ?>"
|
||
<?php echo $ro; ?>>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_presentation']) ?> *</label>
|
||
<textarea name="presentation" class="form-control" required><?php echo htmlspecialchars($formPresentation) ?></textarea>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_duree_rdv']) ?></label>
|
||
<input type="number" name="duree_rdv" class="form-control"
|
||
value="<?php echo (int) ($praticien['duree_rdv'] ?? 60); ?>"
|
||
min="15" max="180" required
|
||
<?php echo $ro; ?>>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label"><?php echo htmlspecialchars($translations['label_photo']) ?></label>
|
||
<input type="file" name="photo" class="form-control" accept="image/*" <?php echo $dis; ?>>
|
||
</div>
|
||
|
||
<?php if (! empty($praticien['photo'])): ?>
|
||
<div class="mb-3">
|
||
<p><?php echo htmlspecialchars($translations['photo_actuelle']) ?></p>
|
||
<img src="/public/uploads/<?php echo htmlspecialchars($praticien['photo']) ?>"
|
||
class="img-fluid rounded-4 shadow"
|
||
style="width:230px;height:auto;">
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="mt-4 text-center">
|
||
<button type="submit" class="btn btn-primary"><?php echo htmlspecialchars($translations['btn_enregistrer']) ?></button>
|
||
</div>
|
||
|
||
<?php echo $translations['champs_obligatoire'] ?>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
const tel = document.getElementById('telephone');
|
||
|
||
tel?.addEventListener('input', function () {
|
||
const pos = this.selectionStart;
|
||
this.value = this.value.replace(/[\s\-._()/]/g, '');
|
||
try { this.setSelectionRange(pos, pos); } catch(_) {}
|
||
});
|
||
|
||
tel?.addEventListener('blur', function() {
|
||
let val = this.value.trim().replace(/[\s\-._()/]/g, '');
|
||
if (val.startsWith('00')) val = '+' + val.slice(2);
|
||
if (/^0\d{9}$/.test(val)) val = '+33' + val.substring(1);
|
||
if (val && !val.startsWith('+')) val = '+' + val;
|
||
this.value = val;
|
||
});
|
||
|
||
tel?.form?.addEventListener('submit', function (e) {
|
||
tel.dispatchEvent(new Event('blur'));
|
||
if (!tel.checkValidity()) {
|
||
e.preventDefault();
|
||
tel.reportValidity();
|
||
tel.focus();
|
||
}
|
||
});
|
||
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const adresseInput = document.getElementById("adresse");
|
||
const villeInput = document.getElementById("ville");
|
||
const cpInput = document.getElementById("code_postal");
|
||
const suggestions = document.getElementById("suggestions");
|
||
|
||
if (adresseInput.hasAttribute('readonly')) {
|
||
return;
|
||
}
|
||
|
||
function buildStreet(props) {
|
||
const num = (props.housenumber || '').trim();
|
||
const street = (props.street || props.name || '').trim();
|
||
return [num, street].filter(Boolean).join(' ');
|
||
}
|
||
|
||
function getCity(props) {
|
||
if (props.city) return props.city;
|
||
if (props.context) {
|
||
const parts = props.context.split(',').map(s => s.trim());
|
||
return parts[1] || parts[0] || '';
|
||
}
|
||
return '';
|
||
}
|
||
|
||
let abortCtrl = null;
|
||
|
||
adresseInput.addEventListener("input", function () {
|
||
const query = adresseInput.value.trim();
|
||
if (query.length < 3) {
|
||
suggestions.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
try { abortCtrl?.abort(); } catch(_) {}
|
||
abortCtrl = new AbortController();
|
||
|
||
fetch("https://api-adresse.data.gouv.fr/search/?limit=5&q=" + encodeURIComponent(query), {
|
||
signal: abortCtrl.signal
|
||
})
|
||
.then(res => res.json())
|
||
.then(data => {
|
||
suggestions.innerHTML = '';
|
||
(data.features || []).forEach(feature => {
|
||
const props = feature.properties || {};
|
||
const li = document.createElement("li");
|
||
li.className = 'list-group-item list-group-item-action';
|
||
li.textContent = props.label || buildStreet(props);
|
||
|
||
li.addEventListener("click", function () {
|
||
adresseInput.value = buildStreet(props);
|
||
villeInput.value = getCity(props);
|
||
cpInput.value = props.postcode || '';
|
||
suggestions.innerHTML = '';
|
||
});
|
||
|
||
suggestions.appendChild(li);
|
||
});
|
||
})
|
||
.catch(() => {});
|
||
});
|
||
|
||
document.addEventListener("click", function (e) {
|
||
if (!suggestions.contains(e.target) && e.target !== adresseInput) {
|
||
suggestions.innerHTML = '';
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
function luhnCheck(num) {
|
||
const arr = (num + '').split('').reverse().map(x => parseInt(x, 10));
|
||
const sum = arr.reduce((acc, val, i) => {
|
||
if (i % 2) { val *= 2; if (val > 9) val -= 9; }
|
||
return acc + val;
|
||
}, 0);
|
||
return (sum % 10) === 0;
|
||
}
|
||
|
||
(function(){
|
||
const form = document.getElementById('form-praticien-edit');
|
||
const siretInput = document.getElementById('siret');
|
||
const feedback = document.getElementById('siret-feedback');
|
||
|
||
if (!siretInput || siretInput.hasAttribute('readonly')) {
|
||
return;
|
||
}
|
||
|
||
form.dataset.siretExists = '0';
|
||
let debounceId = null;
|
||
let lastQueried = '';
|
||
|
||
const MSG = {
|
||
invalide: '<?php echo addslashes($translations["siret_format_invalide"] ?? "Numéro SIRET invalide.") ?>',
|
||
valide: '<?php echo addslashes($translations["siret_valide_format"] ?? "Format SIRET valide.") ?>',
|
||
existe: '<?php echo addslashes($translations["siret_trouve"] ?? "Numéro reconnu dans la base INSEE.") ?>',
|
||
nexiste: '<?php echo addslashes($translations["siret_non_trouve"] ?? "Numéro introuvable dans la base INSEE.") ?>',
|
||
attente: '<?php echo addslashes($translations["siret_verification_en_cours"] ?? "Vérification en cours…") ?>'
|
||
};
|
||
|
||
function formatSiret(value) {
|
||
value = value.replace(/\D/g, '');
|
||
if (value.length > 3) value = value.slice(0,3) + ' ' + value.slice(3);
|
||
if (value.length > 7) value = value.slice(0,7) + ' ' + value.slice(7);
|
||
if (value.length > 11) value = value.slice(0,11) + ' ' + value.slice(11);
|
||
return value;
|
||
}
|
||
|
||
function setFeedback(text, color) {
|
||
feedback.textContent = text;
|
||
feedback.style.color = color;
|
||
feedback.style.display = 'block';
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
if (/^\d{14}$/.test(siretInput.value)) {
|
||
siretInput.value = formatSiret(siretInput.value);
|
||
}
|
||
});
|
||
|
||
function checkSiretImmediate() {
|
||
const formatted = formatSiret(siretInput.value);
|
||
if (siretInput.value !== formatted) siretInput.value = formatted;
|
||
|
||
const raw = siretInput.value.replace(/\s+/g, '');
|
||
|
||
if (raw.length === 0) { feedback.style.display = 'none'; form.dataset.siretExists = '0'; return; }
|
||
if (raw.length !== 14) { setFeedback(MSG.invalide, 'red'); form.dataset.siretExists = '0'; return; }
|
||
if (!luhnCheck(raw)) { setFeedback(MSG.invalide, 'red'); form.dataset.siretExists = '0'; return; }
|
||
|
||
setFeedback(MSG.attente, '#666');
|
||
form.dataset.siretExists = '0';
|
||
|
||
if (raw === lastQueried) return;
|
||
lastQueried = raw;
|
||
|
||
clearTimeout(debounceId);
|
||
debounceId = setTimeout(() => {
|
||
fetch('/../../js/ajax_verif_siret.php?siret=' + encodeURIComponent(raw))
|
||
.then(r => r.json())
|
||
.then(data => {
|
||
if (data && data.exists) {
|
||
setFeedback(MSG.existe, 'green');
|
||
form.dataset.siretExists = '1';
|
||
} else {
|
||
setFeedback(MSG.nexiste, 'red');
|
||
form.dataset.siretExists = '0';
|
||
}
|
||
})
|
||
.catch(() => {
|
||
setFeedback(MSG.nexiste, 'red');
|
||
form.dataset.siretExists = '0';
|
||
});
|
||
}, 500);
|
||
}
|
||
|
||
siretInput.addEventListener('input', checkSiretImmediate);
|
||
|
||
form.addEventListener('submit', function(e){
|
||
const raw = siretInput.value.replace(/\s+/g, '');
|
||
if (raw.length !== 14 || !luhnCheck(raw)) {
|
||
e.preventDefault();
|
||
setFeedback(MSG.invalide, 'red');
|
||
siretInput.focus();
|
||
return;
|
||
}
|
||
siretInput.value = raw;
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
</div>
|
||
<footer class="site-footer">
|
||
<div class="container">
|
||
<p><?php echo $translations['copyr'] ?></p>
|
||
</div>
|
||
</footer>
|
||
</body>
|
||
</html>
|