/**
* Jetons d'accès de terrain (`PTA.modules.accessTokens`), écran administration.php
* — V7.27.
*
* Le Service Enfance équipe ses agents et ses responsables de lieu : création
* d'un compte, régénération d'un jeton perdu, désactivation, suppression, et
* génération en lot pour les identités qui n'en ont pas encore.
*
* PÉRIMÈTRE : agents et responsables de lieu, RIEN D'AUTRE. Les comptes Service
* Enfance et DSI ne sont ni listés ni créables. Ce module ne les propose pas, et
* le serveur les refuse en 403 (AccessAccountController) : l'écran ne fait que
* refléter une frontière qu'il ne décide pas.
*
* RÈGLE CENTRALE, héritée de l'espace DSI : un jeton n'existe en clair que dans
* la réponse qui vient de le produire. Il est affiché dans un panneau dédié, qui
* reste ouvert tant qu'il n'a pas été refermé, et n'est jamais rechargé depuis
* le serveur — la base n'en garde qu'une empreinte.
*/
(() => {
'use strict';
const PTA = window.PTA;
// core.js absent : la page reste lisible, simplement non interactive.
if (!PTA) return;
const { readJsonResponse, apiErrorMessage, escapeHtml } = PTA.utils;
const el = (id) => document.getElementById(id);
const LIBELLES_ROLE = {
RESPONSABLE_STRUCTURE: 'Responsable de structure',
AGENT: 'Agent',
};
/**
* Ce qu'il faut renseigner change selon le type de compte : l'écran
* l'explique plutôt que de laisser deviner pourquoi un champ change.
*/
const AIDES_ROLE = {
AGENT: 'Compte en lecture seule sur son propre planning. Seuls les agents actifs qui n’ont pas encore de compte sont proposés.',
RESPONSABLE_STRUCTURE: 'Compte d’édition des brouillons d’un seul lieu. Seuls les lieux actifs qui n’ont pas encore de compte sont proposés.',
};
/** Message dans un des bandeaux de la section. */
function message(id, texte, type = 'info') {
const boite = el(id);
if (!boite) return;
boite.className = `message message-${type}`;
boite.textContent = texte;
}
/** Appel d'API mutualisé : toute réponse non-OK devient une exception. */
async function api(url, options = {}) {
const reponse = await fetch(url, options);
const donnees = await readJsonResponse(reponse);
if (!reponse.ok) throw new Error(apiErrorMessage(donnees, 'Opération impossible.'));
return donnees;
}
/** Raccourci des appels mutants : POST JSON avec le jeton CSRF dans le corps. */
function poster(url, charge) {
return api(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, ...charge }),
});
}
/** « 2026-08-20 09:41:07 » → « 20/08/2026 09:41 ». */
function formatDateHeure(valeur) {
if (!valeur) return '—';
const [date, heure = ''] = String(valeur).split(' ');
const [annee, mois, jour] = date.split('-');
return `${jour}/${mois}/${annee} ${heure.slice(0, 5)}`;
}
// ------------------------------------------------------------------ jetons
/**
* Affiche les jetons produits par la dernière action.
*
* @param {Array<{libelle: string, role: string, jeton: string}>} jetons
*/
function afficherJetons(jetons) {
const panneau = el('access-token-panel');
const liste = el('access-token-list');
if (!panneau || !liste || !jetons.length) return;
liste.innerHTML = jetons.map((entree) => `
`).join('');
panneau.hidden = false;
message('access-token-message', `${jetons.length} jeton(s) à transmettre. Ils ne seront plus affichés après fermeture.`, 'warning');
panneau.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
/**
* Copie les jetons affichés dans le presse-papiers.
*
* L'API Clipboard n'existe QUE dans un contexte sécurisé : sur une
* installation en http:// sur le réseau de la collectivité, elle est
* absente. D'où le repli par zone de texte masquée, puis, en dernier
* ressort, l'invitation à sélectionner à la main.
*/
async function copierJetons() {
const lignes = Array.from(document.querySelectorAll('#access-token-list .dsi-token-row')).map((ligne) => {
const identite = ligne.querySelector('strong')?.textContent || '';
const jeton = ligne.querySelector('.dsi-token-value')?.textContent || '';
return `${identite}\t${jeton}`;
});
if (!lignes.length) return;
const texte = lignes.join('\n');
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(texte);
} else {
const zone = document.createElement('textarea');
zone.value = texte;
zone.setAttribute('readonly', 'readonly');
zone.className = 'dsi-clipboard-relay';
document.body.appendChild(zone);
zone.select();
document.execCommand('copy');
zone.remove();
}
message('access-token-message', `${lignes.length} jeton(s) copié(s) dans le presse-papiers.`, 'success');
} catch (erreur) {
message('access-token-message', 'Copie impossible sur ce navigateur : sélectionnez les jetons à la main.', 'error');
}
}
/** Referme le panneau des jetons ; leur contenu est perdu pour de bon. */
function fermerJetons() {
const panneau = el('access-token-panel');
if (!panneau) return;
panneau.hidden = true;
el('access-token-list').innerHTML = '';
message('access-token-message', '');
}
// ----------------------------------------------------------------- comptes
/** Une ligne du tableau des comptes. */
function ligneCompte(compte) {
const identite = [
`${escapeHtml(compte.identite_libelle)}`,
compte.matricule ? ` Matricule ${escapeHtml(compte.matricule)}` : '',
compte.commentaire ? ` ${escapeHtml(compte.commentaire)}` : '',
].join('');
const etats = [];
if (!compte.actif) etats.push('Désactivé');
else if (!compte.identite_active) etats.push('Identité inactive');
else etats.push('Actif');
const connexions = compte.nombre_connexions > 0
? ` ${compte.nombre_connexions} connexion(s)`
: '';
return `