Files
ASPIC/bo_collaborateurs.html
T

289 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASPIC — BackOffice — Collaborateurs</title>
<link rel="icon" href="mevelia-icone.png">
<link rel="stylesheet" href="backoffice.css">
<style>
/* bo_collaborateurs.html (11 septembre 2026) — reprend "Construire une
identité"/"Modifier l'expérience"/"Tester l'identité", débranché
d'ecran_fond.html (écran n°2) le même soir : ces outils n'ont jamais eu
leur place dans l'écran utilisateur final, ils rejoignent enfin le
BackOffice, leur vraie maison. Logique inchangée (mêmes endpoints,
mêmes champs) — seule l'enveloppe visuelle change : plus d'écran plein
cadre par-dessus le bureau, tout vit dans #bo-contenu. */
.ligne-photos-collab{display:flex;gap:18px;flex-wrap:wrap;margin-bottom:18px;}
.photo-collab-selection{display:flex;flex-direction:column;align-items:center;
gap:6px;cursor:pointer;background:none;border:none;padding:6px;border-radius:8px;}
.photo-collab-selection:hover{background:#1a1a1a;}
.photo-collab-selection.selectionne{background:#1a1a1a;box-shadow:0 0 0 1px #3a6d3a inset;}
.photo-collab-selection img,.photo-collab-selection .repli{
width:64px;height:64px;border-radius:8px;object-fit:cover;background:#222;}
.photo-collab-selection .repli{display:flex;align-items:center;justify-content:center;color:#888;font-weight:700;}
.photo-collab-selection .prenom-collab{font-size:12px;font-weight:700;color:#ddd;}
.photo-collab-selection .fonction-collab{font-size:10px;color:#888;}
.onglets-collab{display:flex;gap:8px;margin-bottom:14px;}
.onglets-collab button.actif{background:#233;color:#bde0f7;border-color:#3a5d7d;}
.panneau-corps{display:none;max-width:720px;}
.panneau-corps.visible{display:block;}
#texte-identite{width:100%;height:300px;resize:vertical;}
#compteur-identite{text-align:right;font-size:11px;color:#888;margin-top:4px;}
#compteur-identite.avertissement{color:#d9a84e;}
#compteur-identite.limite{color:#e14a5c;font-weight:700;}
.boutons-identite{display:flex;justify-content:flex-end;gap:10px;margin-top:12px;}
#message-identite{font-size:12px;margin-top:6px;min-height:14px;}
#message-identite.ok{color:#7be07b;}
#message-identite.ko{color:#e14a5c;}
#chat-messages{height:320px;overflow-y:auto;background:#000;border:1px solid #333;
border-radius:6px;padding:12px;display:flex;flex-direction:column;gap:8px;max-width:720px;}
.bulle-chat{max-width:80%;padding:8px 12px;border-radius:10px;font-size:13px;
line-height:1.5;white-space:pre-wrap;}
.bulle-chat.moi{align-self:flex-end;background:#264d73;color:#fff;}
.bulle-chat.collab{align-self:flex-start;background:#333;color:#eee;}
.bulle-chat.systeme{align-self:center;color:#7a7;font-size:11px;font-style:italic;}
#chat-input{width:100%;max-width:720px;resize:none;margin-top:10px;}
</style>
</head>
<body>
<div id="bo-cadre">
<div id="bo-corps">
<nav id="bo-menu"></nav>
<main id="bo-contenu">
<h1>Collaborateurs</h1>
<p class="sous-titre">Identité, expérience et test de dialogue — jamais consultable par un utilisateur final.</p>
<div class="ligne-photos-collab" id="ligne-photos"></div>
<div class="onglets-collab" id="onglets" style="display:none;">
<button type="button" id="onglet-identite">Identité</button>
<button type="button" id="onglet-experience">Expérience</button>
<button type="button" id="onglet-test">Tester</button>
</div>
<div id="panneau-identite" class="panneau-corps">
<textarea id="texte-identite" spellcheck="false"></textarea>
<div id="compteur-identite"></div>
<div class="boutons-identite">
<button type="button" id="btn-abandonner">Abandonner</button>
<button type="button" id="btn-valider" class="principal">Valider</button>
</div>
<div id="message-identite"></div>
</div>
<div id="panneau-test" class="panneau-corps">
<div id="chat-messages"></div>
<textarea id="chat-input" rows="2" placeholder="Écris un message…"></textarea>
<div class="boutons-identite">
<button type="button" id="btn-chat-envoyer" class="principal">Envoyer</button>
</div>
</div>
</main>
</div>
<footer id="bo-pied"></footer>
</div>
<script src="backoffice.js"></script>
<script>
initBackoffice({ cibleEchap: 'backoffice.html' });
// Ébauches proposées par Claude le 10 septembre 2026 — affichées comme point
// de départ tant qu'aucune identité n'a encore été enregistrée en base.
const EBAUCHES_IDENTITE = {
'Matilda': "Matilda a fait des études de gestion avant de passer quinze ans comme assistante de direction dans une PME industrielle familiale, où elle a appris à anticiper les besoins de son dirigeant avant même qu'il les formule. Rigoureuse sur les échéances, elle relit toujours deux fois avant d'envoyer et a un sens aigu de la confidentialité — ce qui passe par elle n'en ressort jamais sans son accord. Plutôt directe à l'oral, elle préfère dire clairement ce qui ne va pas plutôt que de laisser traîner un doute.",
'Viktor': "Viktor a démarré comme commercial terrain dans la distribution avant de se spécialiser dans la vente B2B pour des PME de services. Énergique et optimiste, il croit qu'un bon relationnel finit toujours par payer — quitte à pousser un peu fort quand il sent une affaire prête à aboutir. Il déteste laisser un prospect refroidir et relance volontiers plus tôt que prévu. Un travers qu'on lui connaît : il a tendance à sous-estimer le temps qu'une négociation de prix peut prendre côté client.",
'Léon': "Léon a passé plus de vingt-cinq ans comme contrôleur de gestion dans plusieurs entreprises avant de rejoindre l'équipe sur le tard, avec la conviction qu'une affaire mal chiffrée finit toujours par coûter cher. Posé, mesuré dans ses propos, il prend le temps de vérifier avant de conclure et n'hésite jamais à freiner un enthousiasme collectif s'il juge une marge trop courte. Mieux vaut arriver avec de vrais chiffres quand on lui soumet un dossier.",
'Nikita': "Nikita vient du monde de la veille digitale et des réseaux sociaux, où elle a appris à recouper une information avant de s'y fier. Curieuse et sociable, elle aime creuser au-delà du premier résultat et préfère dire honnêtement qu'elle n'a rien trouvé plutôt que d'avancer une réponse rassurante mais bancale. Elle se méfie instinctivement d'un profil qui n'existe nulle part en ligne.",
'Sophia': "Sophia a un parcours de communication et de storytelling de marque, passée par une agence avant de rejoindre l'équipe. Créative et spontanée, elle voit dans chaque situation — bonne ou mauvaise — une histoire à raconter, et pense volontiers en termes d'image et de témoignage client plutôt qu'en termes de process. Elle a tendance à s'enthousiasmer vite pour une idée, quitte à devoir être ramenée aux contraintes concrètes du terrain."
};
const ligne = document.getElementById('ligne-photos');
const onglets = document.getElementById('onglets');
const ongletIdentite = document.getElementById('onglet-identite');
const ongletExperience = document.getElementById('onglet-experience');
const ongletTest = document.getElementById('onglet-test');
const panneauIdentite = document.getElementById('panneau-identite');
const panneauTest = document.getElementById('panneau-test');
const texteIdentite = document.getElementById('texte-identite');
const compteurIdentite = document.getElementById('compteur-identite');
const messageIdentite = document.getElementById('message-identite');
const btnValider = document.getElementById('btn-valider');
const btnAbandonner = document.getElementById('btn-abandonner');
const chatMessages = document.getElementById('chat-messages');
const chatInput = document.getElementById('chat-input');
const btnChatEnvoyer = document.getElementById('btn-chat-envoyer');
const TEXTE_IDENTITE_MAX_OCTETS = 16777215;
const codeurUtf8 = new TextEncoder();
let collaborateurs = [];
let collaborateurActif = null;
let champActif = null; // 'AGN_Identite' | 'AGN_Experience'
let historiqueChatTest = [];
let premierEchangeSession = true;
function mettreAJourCompteur() {
const octets = codeurUtf8.encode(texteIdentite.value).length;
compteurIdentite.textContent = octets.toLocaleString('fr-FR') + ' / ' + TEXTE_IDENTITE_MAX_OCTETS.toLocaleString('fr-FR') + ' octets';
const depasse = octets > TEXTE_IDENTITE_MAX_OCTETS;
compteurIdentite.className = depasse ? 'limite' : (octets > TEXTE_IDENTITE_MAX_OCTETS * 0.9 ? 'avertissement' : '');
btnValider.disabled = depasse;
return depasse;
}
texteIdentite.addEventListener('input', mettreAJourCompteur);
function afficherPanneau(nom) {
panneauIdentite.classList.toggle('visible', nom === 'identite' || nom === 'experience');
panneauTest.classList.toggle('visible', nom === 'test');
[ongletIdentite, ongletExperience, ongletTest].forEach(function (b) { b.classList.remove('actif'); });
if (nom === 'identite') ongletIdentite.classList.add('actif');
if (nom === 'experience') ongletExperience.classList.add('actif');
if (nom === 'test') ongletTest.classList.add('actif');
}
function chargerChamp(champ) {
champActif = champ;
const estIdentite = champ === 'AGN_Identite';
texteIdentite.value = estIdentite
? (collaborateurActif.AGN_Identite || EBAUCHES_IDENTITE[collaborateurActif.AGN_Prenom] || '')
: (collaborateurActif.AGN_Experience || '');
messageIdentite.textContent = '';
messageIdentite.className = '';
mettreAJourCompteur();
afficherPanneau(estIdentite ? 'identite' : 'experience');
}
function ouvrirTest() {
historiqueChatTest = [];
premierEchangeSession = true;
chatMessages.innerHTML = '';
ajouterBulle('systeme', collaborateurActif.AGN_Prenom + ' ne voit jamais ce message — session de mise au point.');
afficherPanneau('test');
}
function ajouterBulle(role, texte) {
const b = document.createElement('div');
b.className = 'bulle-chat ' + role;
b.textContent = texte;
chatMessages.appendChild(b);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
async function chargerCollaborateurs() {
ligne.textContent = 'Chargement…';
const r = await fetch('collaborateurs_identite.php');
const data = await r.json();
if (!r.ok) { ligne.textContent = data.error || 'Erreur'; return; }
collaborateurs = data.collaborateurs || [];
ligne.innerHTML = '';
collaborateurs.forEach(function (c) {
const bouton = document.createElement('button');
bouton.type = 'button';
bouton.className = 'photo-collab-selection';
if (c.AGN_Photo) {
const img = document.createElement('img'); img.src = c.AGN_Photo; img.alt = c.AGN_Prenom || '';
bouton.appendChild(img);
} else {
const repli = document.createElement('div'); repli.className = 'repli'; repli.textContent = '?';
bouton.appendChild(repli);
}
const prenom = document.createElement('div'); prenom.className = 'prenom-collab'; prenom.textContent = c.AGN_Prenom || '';
bouton.appendChild(prenom);
if (c.AGN_Fonction) {
const fonction = document.createElement('div'); fonction.className = 'fonction-collab'; fonction.textContent = c.AGN_Fonction;
bouton.appendChild(fonction);
}
bouton.addEventListener('click', function () {
collaborateurActif = c;
ligne.querySelectorAll('.photo-collab-selection').forEach(function (x) { x.classList.remove('selectionne'); });
bouton.classList.add('selectionne');
onglets.style.display = 'flex';
chargerChamp('AGN_Identite');
});
ligne.appendChild(bouton);
});
}
ongletIdentite.addEventListener('click', function () { chargerChamp('AGN_Identite'); });
ongletExperience.addEventListener('click', function () { chargerChamp('AGN_Experience'); });
ongletTest.addEventListener('click', ouvrirTest);
btnAbandonner.addEventListener('click', function () { chargerChamp(champActif); });
btnValider.addEventListener('click', async function () {
if (!collaborateurActif || !champActif) return;
if (mettreAJourCompteur()) return;
btnValider.disabled = true;
messageIdentite.textContent = ''; messageIdentite.className = '';
try {
const corps = { AGN_Incr: collaborateurActif.AGN_Incr };
corps[champActif] = texteIdentite.value;
const r = await fetch('collaborateurs_identite.php', {
method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(corps)
});
const data = await r.json();
btnValider.disabled = false;
if (!r.ok) { messageIdentite.textContent = data.error || 'Erreur'; messageIdentite.className = 'ko'; return; }
collaborateurActif[champActif] = texteIdentite.value;
messageIdentite.textContent = 'Enregistré.'; messageIdentite.className = 'ok';
} catch (e) {
btnValider.disabled = false;
messageIdentite.textContent = 'Erreur réseau'; messageIdentite.className = 'ko';
}
});
async function enregistrerExperience(agnIncr, texteUtilisateur, texteCollaborateur) {
const premierEchange = premierEchangeSession;
premierEchangeSession = false;
try {
const r = await fetch('collaborateur_experience_append.php', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ AGN_Incr: agnIncr, premierEchange: premierEchange, utilisateur: texteUtilisateur, collaborateur: texteCollaborateur })
});
if (!r.ok) ajouterBulle('systeme', '⚠️ Échange non enregistré dans l\'expérience (' + r.status + ')');
} catch (e) {
ajouterBulle('systeme', "⚠️ Échange non enregistré dans l'expérience (erreur réseau)");
}
}
async function envoyerMessageTest() {
const texte = chatInput.value.trim();
if (!texte || !collaborateurActif) return;
chatInput.value = '';
chatInput.disabled = true; btnChatEnvoyer.disabled = true;
ajouterBulle('moi', texte);
historiqueChatTest.push({ role: 'user', content: texte });
const attente = document.createElement('div');
attente.className = 'bulle-chat systeme';
attente.textContent = (collaborateurActif.AGN_Prenom || 'Le collaborateur') + ' réfléchit…';
chatMessages.appendChild(attente);
try {
const r = await fetch('collaborateur_dialogue.php', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ AGN_Incr: collaborateurActif.AGN_Incr, messages: historiqueChatTest })
});
const data = await r.json();
attente.remove();
if (!r.ok) { ajouterBulle('systeme', data.error || 'Erreur'); historiqueChatTest.pop(); return; }
historiqueChatTest.push({ role: 'assistant', content: data.reponse });
ajouterBulle('collab', data.reponse);
enregistrerExperience(collaborateurActif.AGN_Incr, texte, data.reponse);
} catch (e) {
attente.remove();
ajouterBulle('systeme', 'Erreur réseau');
historiqueChatTest.pop();
} finally {
chatInput.disabled = false; btnChatEnvoyer.disabled = false; chatInput.focus();
}
}
btnChatEnvoyer.addEventListener('click', envoyerMessageTest);
chatInput.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); envoyerMessageTest(); }
});
chargerCollaborateurs();
</script>
</body>
</html>