V1.18.19 Mise en place des collaborateurs

This commit is contained in:
2026-09-10 06:24:16 +00:00
parent 67614908c3
commit 0dee1c7ebd
12 changed files with 407 additions and 28 deletions
+312 -17
View File
@@ -455,6 +455,90 @@
}
.postit .pastille-urgence-postit.rouge{ background:#c0392b; }
.postit .pastille-urgence-postit.orange{ background:#ff5a1f; }
/* Portraits des assistants, écran 2 (9 septembre 2026 — voir journal).
Même principe d'attachement que #postits-layer pour le fond liège :
#assistants-layer est déplacé (au sens JS) à l'intérieur de la slide du
fond "tasseaux gris" (index 1) à chaque fois qu'elle existe, voir
creerFondSlide()/changerFond() plus bas — invisible tant qu'un autre
fond est affiché, sans dupliquer sa logique de position. position:absolute
(pas fixed) : positionné par rapport à ce conteneur local (lui-même
inset:0 dans la slide, elle-même calée sur le rectangle "contain"), pas
par rapport à la fenêtre — voir positionner().
Nombre de colonnes non codé en dur (décision de Jean-Marc, 9 septembre
2026) : une carte par agent réellement renvoyé par agents.php, largeur
de colonne = largeur totale / nombre d'agents — s'adapte donc tout seul
si un agent est ajouté ou retiré, sans repasser par ce fichier. */
#assistants-layer{
position:absolute;
inset:0;
}
.assistant-carte{
position:absolute;
display:flex;
flex-direction:column;
align-items:center;
pointer-events:none; /* purement visuel pour l'instant, comme les post-its */
}
.assistant-cadre{
background:#fff;
box-shadow:2px 4px 8px rgba(0,0,0,.35);
box-sizing:border-box;
display:block;
}
.assistant-photo{
display:block;
width:100%;
height:100%;
object-fit:cover;
}
/* Repli visuel pour un agent sans AGN_Photo renseignée (ex. le temps que
Jean-Marc dépose la photo) : même esprit que le "?" utilisé dans les
essais visuels, jamais un cadre vide sans explication. */
.assistant-photo-repli{
display:flex;align-items:center;justify-content:center;
width:100%;height:100%;
background:#e0e0dc;
color:#9a9a95;
font-weight:700;
font-family:"Segoe UI","Arial",sans-serif;
}
.assistant-nom{
margin-top:6px;
font-weight:700;
color:#2a2a2a;
font-family:"Segoe UI","Arial",sans-serif;
white-space:nowrap;
line-height:1.15;
}
.assistant-fonction{
color:#3a3a3a;
font-weight:700; /* forcé en gras le 9 septembre 2026 (retour Jean-Marc) : la ligne passait inaperçue sur le fond tasseaux */
font-family:"Segoe UI","Arial",sans-serif;
white-space:nowrap;
line-height:1.15;
}
/* Panneau "Bureau des assistants" (9 septembre 2026) : même esprit que les
cadres photo (liseré blanc, ombre portée) mais fond gris plutôt que
blanc — posé une seule fois (pas par agent) dans l'espace qui reste
libre sous les 2 dernières colonnes (aujourd'hui Léon et Sophia, voir
positionner()), tant qu'il n'y a rien d'autre à y afficher. */
.bureau-panneau{
position:absolute;
box-sizing:border-box;
background:#cfcfc9;
border-style:solid;
border-color:#fff;
box-shadow:2px 4px 8px rgba(0,0,0,.35);
display:flex;
align-items:center;
justify-content:center;
text-align:center;
color:#2a2a2a;
font-weight:700;
font-family:"Segoe UI","Arial",sans-serif;
pointer-events:none;
}
</style>
</head>
<body>
@@ -625,19 +709,24 @@ Créé par Mevelia Solutions">
// milieu géométrique — même principe que le 7 septembre). labelCx = centre
// du rectangle blanc de l'étiquette, mesuré indépendamment (les 9
// rectangles ne sont pas exactement centrés dans leur classeur).
// Nouvelle répartition (9 septembre 2026, réflexion stratégique de
// Jean-Marc — regroupement plus cohérent des rubriques, ex. Prospection
// fusionnée dans Contacts, En-cours+Journalier scindés différemment en
// Missions/Propositions/Livraisons/Journalier). Reprend exactement les 9
// positions physiques déjà mesurées sur l'avant-plan (x1/x2/labelCx
// inchangés, dans l'ordre des classeurs sur la photo) : seuls les
// libellés/ids/href changent, aucune nouvelle mesure de pixels nécessaire
// tant que l'avant-plan lui-même ne change pas.
const CLASSEURS = [
{id:'consignes', label:'Consignes', x1:44, x2:83, labelCx:63.5, href:null},
{id:'contacts', label:'Contacts', x1:83, x2:120, labelCx:101.0, href:'liste_contacts.html'},
{id:'catalogue', label:'Catalogue', x1:120, x2:158, labelCx:138.5, href:null},
// "Documentations" (id interne conservé pour l'historique) reste affiché
// "Documents", police forcée sur le plafond commun — voir raisonnement
// inchangé du 7 septembre 2026 (toujours valable, indépendant de l'image).
{id:'documentations', label:'Documents', x1:158, x2:197, labelCx:177.0, href:null},
{id:'prospection', label:'Prospection', x1:197, x2:239, labelCx:216.0, href:null},
{id:'communication', label:'Réseaux', x1:239, x2:280, labelCx:259.0, href:null},
{id:'en-cours', label:'En cours', x1:280, x2:321, labelCx:300.5, href:null},
{id:'factures', label:'Factures', x1:321, x2:363, labelCx:341.5, href:null},
{id:'journalier', label:'Journalier', x1:363, x2:415, labelCx:383.5, href:null},
{id:'catalogue', label:'Catalogue', x1:44, x2:83, labelCx:63.5, href:null},
{id:'documents', label:'Documents', x1:83, x2:120, labelCx:101.0, href:null},
{id:'contacts', label:'Contacts', x1:120, x2:158, labelCx:138.5, href:'liste_contacts.html'},
{id:'missions', label:'Missions', x1:158, x2:197, labelCx:177.0, href:null},
{id:'propositions', label:'Propositions', x1:197, x2:239, labelCx:216.0, href:null},
{id:'livraisons', label:'Livraisons', x1:239, x2:280, labelCx:259.0, href:null},
{id:'factures', label:'Factures', x1:280, x2:321, labelCx:300.5, href:null},
{id:'journalier', label:'Journalier', x1:321, x2:363, labelCx:341.5, href:null},
{id:'gestion', label:'Gestion', x1:363, x2:415, labelCx:383.5, href:null},
];
// Zone cliquable/survolable du micro et centre de l'icône : remesurés le
@@ -666,7 +755,11 @@ Créé par Mevelia Solutions">
// Y remesuré le 8 septembre 2026 (soir), même recadrage.
// Redescendue un peu le 8 septembre 2026 (soir), retour de Jean-Marc
// après 6ᵉ test réel (trop remontée la fois précédente) : 401 → 404.
const PASTILLE_CONTACTS_CENTRE_X = 101, PASTILLE_CONTACTS_CENTRE_Y = 404;
// X déplacé le 9 septembre 2026 : "Contacts" passe du 2ᵉ au 3ᵉ classeur
// dans la nouvelle répartition (voir tableau CLASSEURS) — X reprend le
// labelCx du nouvel emplacement (138.5) pour que la pastille reste sous
// le bon classeur ; Y inchangé (même hauteur de trou sur les 9 classeurs).
const PASTILLE_CONTACTS_CENTRE_X = 138.5, PASTILLE_CONTACTS_CENTRE_Y = 404;
const PASTILLE_CONTACTS_DIAM_FRAC = 17 / IMG_W;
// Zone des post-its personnels (2 septembre 2026, premier affichage réel
@@ -709,6 +802,38 @@ Créé par Mevelia Solutions">
const FLECHE_PRECEDENTE_CENTRE_X = 962, FLECHE_CENTRE_Y = 410;
const FLECHE_SUIVANTE_CENTRE_X = 998;
// -------- Portraits des assistants, écran 2 (9 septembre 2026) --------
// Réglages issus des essais visuels validés par Jean-Marc (voir journal) :
// marge réduite au-dessus de la tête, visage recentré, cadrage descendant
// sur le buste, taille réduite de 15% par rapport au tout premier essai.
// ASSISTANT_PHOTO_RATIO doit rester cohérent avec le recadrage utilisé pour
// produire les fichiers assistant_<prenom>.jpg (outillage côté Claude) — à
// revoir ensemble si Jean-Marc fournit un jour des photos au format déjà
// différent.
const ASSISTANT_ZONE_TOP = 14; // marge haute, px de référence (IMG_H)
const ASSISTANT_PHOTO_H_MAX = 162; // hauteur max de la photo (hors liseré), px de référence
const ASSISTANT_PHOTO_RATIO = 0.657; // largeur / hauteur de la photo
const ASSISTANT_MAT = 5; // épaisseur du liseré blanc, px de référence
const ASSISTANT_COL_MARGE = 0.85; // fraction de la largeur de colonne réellement utilisable (reste = gouttière)
const ASSISTANT_NOM_POLICE_FRAC = 17 / IMG_W;
const ASSISTANT_FONCTION_POLICE_FRAC = 12 / IMG_W;
// Panneau "Bureau des assistants" (9 septembre 2026, retour Jean-Marc) :
// posé dans l'espace qui reste libre sous les 2 dernières colonnes de
// portraits (aujourd'hui Léon et Sophia), entre le bas du texte des
// cartes et le haut du plateau du bureau. BUREAU_PANNEAU_BAS reprend la
// même limite que CLASSEURS_Y2/MICRO_ZONE_Y2 plus haut (haut du plateau,
// commun à toute la largeur de l'image) avec la même petite marge de
// sécurité que ZONE_POSTITS_Y2.
const BUREAU_PANNEAU_BAS = 390; // px de référence (IMG_H)
const BUREAU_PANNEAU_GAP = 8; // espace entre le texte des cartes et le panneau, px de référence
const BUREAU_PANNEAU_POLICE_FRAC = 16 / IMG_W;
// Le panneau est un encadré ajusté au texte (mesuré, voir positionner()),
// pas une zone qui occupe tout l'espace disponible — cet espace ne sert
// qu'à centrer l'encadré (retour Jean-Marc, 9 septembre 2026).
const BUREAU_PANNEAU_PAD_H = 16; // marge intérieure horizontale, px de référence
const BUREAU_PANNEAU_PAD_V = 10; // marge intérieure verticale, px de référence
/**
* Petit générateur pseudo-aléatoire déterministe (0..1), à partir d'un
* entier (TDL_Incr) — pour que le jitter (rotation/décalage) d'un post-it
@@ -924,6 +1049,92 @@ Créé par Mevelia Solutions">
postit.style.top = (caseTop + (caseH - postitTaille) / 2 + decalageY) + 'px';
postit.style.transform = 'rotate(' + rotation.toFixed(1) + 'deg)';
});
// Portraits des assistants (écran 2) : nombre de colonnes = nombre de
// cartes réellement créées par creerCartesAssistants() (voir plus bas),
// pas de valeur codée en dur — la largeur de colonne se recalcule donc
// toute seule si un agent est ajouté ou retiré côté ASP_Agents. scale
// convertit un px de référence (IMG_W/IMG_H) en px écran ; identique
// dans les deux sens puisque le rectangle "contain" respecte toujours le
// ratio IMG_W/IMG_H (voir calcul de dispW/dispH plus haut).
const cartesAssistants = coucheAssistants.querySelectorAll('.assistant-carte');
if (cartesAssistants.length > 0) {
const scale = dispW / IMG_W;
const colW = dispW / cartesAssistants.length;
const matPx = ASSISTANT_MAT * scale;
let photoH = ASSISTANT_PHOTO_H_MAX * scale;
// Contrainte de largeur : si la colonne est trop étroite pour la
// hauteur visée (beaucoup d'agents), on réduit la photo en proportion
// plutôt que de déborder sur la colonne voisine.
const largeurMaxCarte = colW * ASSISTANT_COL_MARGE;
if (photoH * ASSISTANT_PHOTO_RATIO + 2 * matPx > largeurMaxCarte) {
photoH = (largeurMaxCarte - 2 * matPx) / ASSISTANT_PHOTO_RATIO;
}
const photoW = photoH * ASSISTANT_PHOTO_RATIO;
const carteW = photoW + 2 * matPx;
const carteH = photoH + 2 * matPx;
const topPx = ASSISTANT_ZONE_TOP * scale;
const nomPx = ASSISTANT_NOM_POLICE_FRAC * dispW;
const fonctionPx = ASSISTANT_FONCTION_POLICE_FRAC * dispW;
cartesAssistants.forEach(function (carte, index) {
const cx = colW * index + colW / 2;
carte.style.left = (cx - carteW / 2) + 'px';
carte.style.top = topPx + 'px';
carte.style.width = carteW + 'px';
const cadre = carte.querySelector('.assistant-cadre');
cadre.style.width = carteW + 'px';
cadre.style.height = carteH + 'px';
cadre.style.padding = matPx + 'px';
const nomEl = carte.querySelector('.assistant-nom');
if (nomEl) nomEl.style.fontSize = nomPx + 'px';
const fonctionEl = carte.querySelector('.assistant-fonction');
if (fonctionEl) fonctionEl.style.fontSize = fonctionPx + 'px';
const repli = carte.querySelector('.assistant-photo-repli');
if (repli) repli.style.fontSize = (photoH * 0.3) + 'px';
});
// Panneau "Bureau des assistants" : la zone sous les 2 dernières
// colonnes (aujourd'hui Léon et Sophia) ne sert qu'à CENTRER
// l'encadré — l'encadré lui-même est ajusté au texte (mesuré via
// ctxMesure, voir plus haut), pas étiré sur toute la zone (retour
// Jean-Marc, 9 septembre 2026). Bas du texte des cartes calculé
// analytiquement (pas mesuré en DOM) : coucheAssistants peut être
// parquée hors document (fond non affiché), où
// offsetHeight/getBoundingClientRect renverraient 0.
if (cartesAssistants.length >= 2) {
const nomLigne = nomPx * 1.15; // line-height:1.15, voir .assistant-nom
const nomMargeHaut = 6 * scale; // margin-top:6px, voir .assistant-nom
const fonctionLigne = fonctionPx * 1.15; // line-height:1.15, voir .assistant-fonction
const basCarte = topPx + carteH + nomMargeHaut + nomLigne + fonctionLigne;
const zoneTop = basCarte + BUREAU_PANNEAU_GAP * scale;
const zoneBas = BUREAU_PANNEAU_BAS * scale;
const zoneGauche = colW * (cartesAssistants.length - 2);
const zoneLargeur = colW * 2;
const zoneCentreX = zoneGauche + zoneLargeur / 2;
const zoneCentreY = zoneTop + (zoneBas - zoneTop) / 2;
const panneauPolicePx = BUREAU_PANNEAU_POLICE_FRAC * dispW;
ctxMesure.font = '700 ' + panneauPolicePx + 'px "Segoe UI","Arial",sans-serif';
const texteLargeur = ctxMesure.measureText(panneauBureau.textContent).width;
const padH = BUREAU_PANNEAU_PAD_H * scale;
const padV = BUREAU_PANNEAU_PAD_V * scale;
const panneauLargeur = texteLargeur + 2 * padH;
const panneauHauteur = panneauPolicePx * 1.15 + 2 * padV;
panneauBureau.style.display = 'flex';
panneauBureau.style.left = (zoneCentreX - panneauLargeur / 2) + 'px';
panneauBureau.style.top = (zoneCentreY - panneauHauteur / 2) + 'px';
panneauBureau.style.width = panneauLargeur + 'px';
panneauBureau.style.height = panneauHauteur + 'px';
panneauBureau.style.borderWidth = matPx + 'px';
panneauBureau.style.fontSize = panneauPolicePx + 'px';
} else {
panneauBureau.style.display = 'none';
}
} else {
panneauBureau.style.display = 'none';
}
}
// -------- Fonds interchangeables (8 septembre 2026) --------
// 3 fonds codés en dur (décision de Jean-Marc, pas de liste extensible
@@ -948,16 +1159,36 @@ Créé par Mevelia Solutions">
const couchePostits = document.createElement('div');
couchePostits.id = 'postits-layer';
// Portraits des assistants (écran 2, 9 septembre 2026) : même principe que
// couchePostits ci-dessus — un noeud créé une seule fois, rempli de façon
// asynchrone par chargerAgentsEcranAssistants() plus bas, déplacé (jamais
// dupliqué) entre les slides par creerFondSlide().
const coucheAssistants = document.createElement('div');
coucheAssistants.id = 'assistants-layer';
// Panneau "Bureau des assistants" (voir commentaire CSS .bureau-panneau et
// constantes BUREAU_PANNEAU_* plus haut) : contenu statique, créé une
// seule fois ici et jamais recréé par creerCartesAssistants() (qui ne
// touche qu'aux .assistant-carte) — seule sa position/taille est
// recalculée à chaque appel de positionner().
const panneauBureau = document.createElement('div');
panneauBureau.className = 'bureau-panneau';
panneauBureau.textContent = 'Bureau des assistants';
coucheAssistants.appendChild(panneauBureau);
function creerFondSlide(index) {
const slide = document.createElement('div');
slide.className = 'fond-slide';
slide.style.backgroundImage = "url('" + FONDS[index] + "')";
// Le fond liège (index 0) embarque la couche des post-its : un simple
// appendChild déplace le noeud existant (le retire au passage de son
// ancien parent, s'il en avait un) — pas de duplication, pas de perte du
// contenu déjà chargé par chargerMesPostits().
// Le fond liège (index 0) embarque la couche des post-its, le fond
// tasseaux gris (index 1, écran assistants) la couche des portraits : un
// simple appendChild déplace le noeud existant (le retire au passage de
// son ancien parent, s'il en avait un) — pas de duplication, pas de perte
// du contenu déjà chargé.
if (index === 0) {
slide.appendChild(couchePostits);
} else if (index === 1) {
slide.appendChild(coucheAssistants);
}
return slide;
}
@@ -1151,6 +1382,70 @@ Créé par Mevelia Solutions">
}
chargerMesPostits();
// Portraits des assistants, écran 2 (9 septembre 2026 — voir journal et
// réflexions stratégiques). Source : agents.php, qui sert déjà
// AGN_Prenom/AGN_Fonction pour le pilotage vocal — AGN_Photo (nom de
// fichier, ex. "assistant_matilda.jpg") et le tri par AGN_OrdreAffichage
// s'y ajoutent pour cet écran (voir commentaire d'en-tête de agents.php). Un agent sans
// AGN_Photo reste affiché (repli visuel "?", voir .assistant-photo-repli) :
// jamais de colonne manquante silencieusement. Nombre de colonnes non codé
// en dur : une carte par agent réellement renvoyé (voir positionner()).
function creerCartesAssistants() {
// Ne retire que les cartes (pas le panneau "Bureau des assistants",
// ajouté une seule fois à la création de coucheAssistants — voir
// panneauBureau plus haut).
coucheAssistants.querySelectorAll('.assistant-carte').forEach(function (el) { el.remove(); });
(agentsEcranAssistants || []).forEach(function (agent) {
const carte = document.createElement('div');
carte.className = 'assistant-carte';
const cadre = document.createElement('div');
cadre.className = 'assistant-cadre';
if (agent.AGN_Photo) {
const img = document.createElement('img');
img.className = 'assistant-photo';
img.alt = agent.AGN_Prenom || '';
img.src = agent.AGN_Photo;
cadre.appendChild(img);
} else {
const repli = document.createElement('div');
repli.className = 'assistant-photo-repli';
repli.textContent = '?';
cadre.appendChild(repli);
}
carte.appendChild(cadre);
const nom = document.createElement('div');
nom.className = 'assistant-nom';
nom.textContent = agent.AGN_Prenom || '';
carte.appendChild(nom);
if (agent.AGN_Fonction) {
const fonction = document.createElement('div');
fonction.className = 'assistant-fonction';
fonction.textContent = agent.AGN_Fonction;
carte.appendChild(fonction);
}
coucheAssistants.appendChild(carte);
});
positionner(); // place les cartes qui viennent d'être (re)créées
}
let agentsEcranAssistants = [];
async function chargerAgentsEcranAssistants() {
try {
const reponse = await fetch('agents.php');
if (!reponse.ok) throw new Error('HTTP ' + reponse.status);
const data = await reponse.json();
agentsEcranAssistants = data.agents || [];
creerCartesAssistants();
} catch (erreur) {
console.error('Erreur de chargement de agents.php (écran assistants) :', erreur);
}
}
chargerAgentsEcranAssistants();
// Lien visible "→ Liste des tâches" retiré le 2 septembre 2026 à la
// demande de Jean-Marc. Échap avait été conservé ce soir-là comme
// échappatoire silencieuse vers liste_todolist.html, pour ne pas rester