Import initial de Vivier depuis l'hebergement OVH
This commit is contained in:
commit
839e5e57c5
126 files changed
+22447
No files matched your search
@@ -0,0 +1,480 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var csrf = document.querySelector('meta[name="csrf"]').content;
|
||||
var textes = JSON.parse(document.querySelector('meta[name="textes"]').content);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Menu des écrans étroits
|
||||
// -------------------------------------------------------------------------
|
||||
var boutonMenu = document.querySelector('.menu-bouton');
|
||||
if (boutonMenu) {
|
||||
var menu = document.getElementById('menu');
|
||||
boutonMenu.addEventListener('click', function () {
|
||||
var ouvert = menu.classList.toggle('ouvert');
|
||||
boutonMenu.setAttribute('aria-expanded', ouvert ? 'true' : 'false');
|
||||
});
|
||||
}
|
||||
|
||||
// Sous-menus (Gestion, compte, CV IT Club) : un seul ouvert à la fois, fermés au clic ailleurs et à la touche Échap
|
||||
var sousMenus = Array.prototype.slice.call(document.querySelectorAll('.menu-plus, .menu-deroulant'));
|
||||
sousMenus.forEach(function (m) {
|
||||
m.addEventListener('toggle', function () {
|
||||
if (m.open) { sousMenus.forEach(function (autre) { if (autre !== m) { autre.open = false; } }); }
|
||||
});
|
||||
});
|
||||
document.addEventListener('click', function (e) {
|
||||
sousMenus.forEach(function (m) { if (m.open && !m.contains(e.target)) { m.open = false; } });
|
||||
});
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key !== 'Escape') { return; }
|
||||
sousMenus.forEach(function (m) { if (m.open) { m.open = false; m.querySelector('summary').focus(); } });
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Envoi long : le bouton annonce l'attente et empêche un second envoi
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('form[data-attente]').forEach(function (form) {
|
||||
form.addEventListener('submit', function () {
|
||||
var bouton = form.querySelector('button[type="submit"]');
|
||||
bouton.disabled = true;
|
||||
bouton.textContent = form.dataset.attenteBouton || textes.analyseEnCours;
|
||||
var p = document.createElement('p');
|
||||
p.className = 'flash flash-info';
|
||||
p.setAttribute('role', 'status');
|
||||
p.textContent = form.dataset.attente;
|
||||
form.appendChild(p);
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Kanban : glisser-déposer entre colonnes
|
||||
// -------------------------------------------------------------------------
|
||||
var kanban = document.querySelector('.kanban');
|
||||
if (kanban && window.Sortable) {
|
||||
var dialogue = document.getElementById('dialogue-motif');
|
||||
var annonce = document.getElementById('annonce');
|
||||
|
||||
var compter = function () {
|
||||
kanban.querySelectorAll('.colonne').forEach(function (col) {
|
||||
col.querySelector('.compteur').textContent = col.querySelectorAll('.fiche').length;
|
||||
});
|
||||
};
|
||||
|
||||
var annuler = function (evt, message) {
|
||||
evt.from.insertBefore(evt.item, evt.from.children[evt.oldIndex] || null);
|
||||
compter();
|
||||
if (message) {
|
||||
annonce.textContent = message;
|
||||
annonce.hidden = false;
|
||||
}
|
||||
};
|
||||
|
||||
var envoyer = function (evt, motif) {
|
||||
var corps = new URLSearchParams();
|
||||
corps.set('csrf', csrf);
|
||||
corps.set('id', evt.item.dataset.id);
|
||||
corps.set('statut', evt.to.dataset.statut);
|
||||
corps.set('ordre', Array.prototype.map.call(evt.to.children, function (li) { return li.dataset.id; }).join(','));
|
||||
if (motif) { corps.set('motif', motif); }
|
||||
|
||||
fetch(kanban.dataset.url, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Requested-With': 'fetch' },
|
||||
body: corps,
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok && j.ok, erreur: j.erreur }; }); })
|
||||
.then(function (res) {
|
||||
if (!res.ok) { annuler(evt, res.erreur || textes.echecEnregistrement); return; }
|
||||
annonce.hidden = true;
|
||||
var ancien = evt.item.querySelector('.motif');
|
||||
if (ancien) { ancien.remove(); }
|
||||
if (motif) {
|
||||
var p = document.createElement('p');
|
||||
p.className = 'motif';
|
||||
p.textContent = motif;
|
||||
evt.item.appendChild(p);
|
||||
}
|
||||
})
|
||||
.catch(function () { annuler(evt, textes.connexionPerdue); });
|
||||
};
|
||||
|
||||
var demanderMotif = function (evt) {
|
||||
var zone = dialogue.querySelector('textarea');
|
||||
zone.value = '';
|
||||
dialogue.returnValue = '';
|
||||
dialogue.addEventListener('close', function () {
|
||||
var motif = zone.value.trim();
|
||||
if (dialogue.returnValue === 'valider' && motif) { envoyer(evt, motif); } else { annuler(evt); }
|
||||
}, { once: true });
|
||||
dialogue.showModal();
|
||||
};
|
||||
|
||||
dialogue.querySelector('button[value="annuler"]').addEventListener('click', function () {
|
||||
dialogue.close('annuler');
|
||||
});
|
||||
|
||||
kanban.querySelectorAll('.cartes').forEach(function (liste) {
|
||||
Sortable.create(liste, {
|
||||
group: 'kanban',
|
||||
animation: 150,
|
||||
// Sur écran tactile, un appui bref fait défiler la page ; un appui maintenu saisit la carte
|
||||
delay: 180,
|
||||
delayOnTouchOnly: true,
|
||||
disabled: kanban.dataset.modifiable !== '1',
|
||||
ghostClass: 'fantome',
|
||||
chosenClass: 'saisie',
|
||||
dragClass: 'glisse',
|
||||
onStart: function () { kanban.classList.add('en-glisse'); },
|
||||
onEnd: function (evt) {
|
||||
kanban.classList.remove('en-glisse');
|
||||
if (evt.from === evt.to && evt.oldIndex === evt.newIndex) { return; }
|
||||
compter();
|
||||
if (evt.to.dataset.statut === 'non_retenu' && evt.from !== evt.to) {
|
||||
demanderMotif(evt);
|
||||
} else {
|
||||
envoyer(evt, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Kanban : filtre instantané des cartes
|
||||
// -------------------------------------------------------------------------
|
||||
var filtre = document.getElementById('filtre-kanban');
|
||||
if (filtre && kanban) {
|
||||
var sansAccent = function (t) { return t.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); };
|
||||
var texte = document.getElementById('filtre-texte');
|
||||
var seniorite = document.getElementById('filtre-seniorite');
|
||||
var filtrer = function () {
|
||||
var mots = sansAccent(texte.value).split(/\s+/).filter(Boolean);
|
||||
kanban.querySelectorAll('.colonne').forEach(function (col) {
|
||||
var visibles = 0;
|
||||
col.querySelectorAll('.fiche').forEach(function (fiche) {
|
||||
var contenu = sansAccent(fiche.textContent);
|
||||
var garde = (!seniorite.value || fiche.dataset.seniorite === seniorite.value) &&
|
||||
mots.every(function (m) { return contenu.indexOf(m) !== -1; });
|
||||
fiche.classList.toggle('cachee', !garde);
|
||||
if (garde) { visibles++; }
|
||||
});
|
||||
col.querySelector('.compteur').textContent = visibles;
|
||||
});
|
||||
};
|
||||
filtre.addEventListener('submit', function (e) { e.preventDefault(); });
|
||||
texte.addEventListener('input', filtrer);
|
||||
seniorite.addEventListener('change', filtrer);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Import en lot : les CV en attente sont analysés l'un après l'autre, une requête par CV
|
||||
// -------------------------------------------------------------------------
|
||||
var lot = document.getElementById('lot');
|
||||
if (lot && lot.dataset.auto === '1') {
|
||||
var enAttente = Array.prototype.slice.call(lot.querySelectorAll('tr[data-etat="en_attente"]'));
|
||||
var avancement = document.getElementById('lot-avancement');
|
||||
var total = enAttente.length;
|
||||
enAttente.forEach(function (tr) {
|
||||
var b = tr.querySelector('.bouton-analyse');
|
||||
if (b) { b.hidden = true; }
|
||||
});
|
||||
var analyserSuivant = function () {
|
||||
var tr = enAttente.shift();
|
||||
if (!tr) {
|
||||
if (total) {
|
||||
if (avancement) { avancement.textContent = textes.lotTermine; }
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var badge = tr.querySelector('.etat-lot');
|
||||
var detail = tr.querySelector('.detail-lot');
|
||||
badge.textContent = textes.analyseEnCours;
|
||||
badge.className = 'badge badge-lot-analyse etat-lot';
|
||||
var corps = new URLSearchParams();
|
||||
corps.set('csrf', csrf);
|
||||
corps.set('id', tr.dataset.id);
|
||||
fetch(lot.dataset.url, { method: 'POST', headers: { 'X-Requested-With': 'fetch' }, body: corps, credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (j) {
|
||||
var etat = j.etat || 'erreur';
|
||||
tr.dataset.etat = etat;
|
||||
badge.className = 'badge badge-lot-' + etat + ' etat-lot';
|
||||
badge.textContent = etat === 'pret' ? textes.lotPret : (etat === 'erreur' ? textes.lotErreur : textes.lotEnAttente);
|
||||
if (j.candidat) { tr.querySelector('.candidat-lot').textContent = j.candidat; }
|
||||
if (j.erreur) { detail.textContent = j.erreur; detail.hidden = false; }
|
||||
})
|
||||
.catch(function () {
|
||||
badge.className = 'badge badge-lot-erreur etat-lot';
|
||||
badge.textContent = textes.lotErreur;
|
||||
detail.textContent = textes.connexionPerdue;
|
||||
detail.hidden = false;
|
||||
})
|
||||
.then(analyserSuivant);
|
||||
};
|
||||
analyserSuivant();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Messages : fermeture d'un clic ; une confirmation s'efface seule après 6 secondes
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('main > .flash[role="status"]').forEach(function (flash) {
|
||||
var partir = function () {
|
||||
flash.classList.add('part');
|
||||
window.setTimeout(function () { flash.remove(); }, 260);
|
||||
};
|
||||
var texte = document.createElement('span');
|
||||
while (flash.firstChild) { texte.appendChild(flash.firstChild); }
|
||||
var fermer = document.createElement('button');
|
||||
fermer.type = 'button';
|
||||
fermer.className = 'fermer';
|
||||
fermer.setAttribute('aria-label', textes.fermer);
|
||||
fermer.textContent = '×';
|
||||
fermer.addEventListener('click', partir);
|
||||
flash.appendChild(texte);
|
||||
flash.appendChild(fermer);
|
||||
flash.classList.add('fermable');
|
||||
if (flash.classList.contains('flash-succes')) { window.setTimeout(partir, 6000); }
|
||||
});
|
||||
|
||||
// Photo introuvable : les initiales la remplacent
|
||||
document.querySelectorAll('img.avatar').forEach(function (img) {
|
||||
var remplacer = function () {
|
||||
var s = document.createElement('span');
|
||||
s.className = img.className + ' avatar-vide';
|
||||
s.setAttribute('aria-hidden', 'true');
|
||||
s.textContent = img.dataset.initiales || '';
|
||||
img.replaceWith(s);
|
||||
};
|
||||
if (img.complete && img.naturalWidth === 0) { remplacer(); } else { img.addEventListener('error', remplacer); }
|
||||
});
|
||||
|
||||
// Bouton d'impression (aide)
|
||||
document.querySelectorAll('[data-imprimer]').forEach(function (b) {
|
||||
b.addEventListener('click', function () { window.print(); });
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Confirmation avant une action irréversible
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('form[data-confirmer]').forEach(function (form) {
|
||||
form.addEventListener('submit', function (e) {
|
||||
if (!window.confirm(form.dataset.confirmer)) { e.preventDefault(); }
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fiche profil : le motif n'apparaît que pour « Non retenu »
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('select[data-motif]').forEach(function (select) {
|
||||
var champ = document.getElementById(select.dataset.motif);
|
||||
var saisie = champ.querySelector('input');
|
||||
var basculer = function () {
|
||||
var refus = select.value === 'non_retenu';
|
||||
champ.hidden = !refus;
|
||||
saisie.required = refus;
|
||||
};
|
||||
select.addEventListener('change', basculer);
|
||||
basculer();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fiche profil : onglets Profil, Suivi, Missions, Dossier
|
||||
// L'adresse garde l'onglet (#suivi) ou une section (#relances) : un retour après
|
||||
// enregistrement, un lien de la page Suivi ou un rechargement rouvre le bon onglet.
|
||||
// -------------------------------------------------------------------------
|
||||
var barreOnglets = document.querySelector('[data-onglets]');
|
||||
if (barreOnglets) {
|
||||
var onglets = Array.prototype.slice.call(barreOnglets.querySelectorAll('[data-onglet]'));
|
||||
var panneaux = onglets.map(function (o) { return document.getElementById(o.dataset.onglet); });
|
||||
|
||||
document.body.classList.add('avec-onglets');
|
||||
barreOnglets.setAttribute('role', 'tablist');
|
||||
onglets.forEach(function (o, i) {
|
||||
o.setAttribute('role', 'tab');
|
||||
o.setAttribute('aria-controls', panneaux[i].id);
|
||||
panneaux[i].setAttribute('role', 'tabpanel');
|
||||
panneaux[i].setAttribute('tabindex', '-1');
|
||||
});
|
||||
|
||||
var activer = function (n) {
|
||||
onglets.forEach(function (o, i) {
|
||||
var choisi = i === n;
|
||||
o.setAttribute('aria-selected', choisi ? 'true' : 'false');
|
||||
o.tabIndex = choisi ? 0 : -1;
|
||||
panneaux[i].hidden = !choisi;
|
||||
});
|
||||
};
|
||||
|
||||
// Ancre → onglet à ouvrir et élément à montrer. Une ancre inconnue ne change rien.
|
||||
var cibleDe = function (ancre) {
|
||||
var id = decodeURIComponent((ancre || '').replace(/^#/, ''));
|
||||
if (!id) { return null; }
|
||||
var el = document.getElementById(id);
|
||||
var panneau = el && el.closest('.panneau');
|
||||
return panneau ? { n: panneaux.indexOf(panneau), el: el, section: el !== panneau } : null;
|
||||
};
|
||||
|
||||
// Le panneau commence sous la barre collée : on n'y descend que si la barre est déjà collée
|
||||
var montrer = function (cible) {
|
||||
if (cible.section) {
|
||||
cible.el.scrollIntoView({ block: 'start' });
|
||||
var replie = cible.el.closest('details');
|
||||
if (replie) { replie.open = true; }
|
||||
} else if (barreOnglets.getBoundingClientRect().top <= parseFloat(getComputedStyle(barreOnglets).top) + 1) {
|
||||
cible.el.scrollIntoView({ block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
var aller = function (ancre, defiler) {
|
||||
var cible = cibleDe(ancre);
|
||||
if (!cible) { return false; }
|
||||
activer(cible.n);
|
||||
if (defiler) { montrer(cible); }
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!aller(window.location.hash, true)) { activer(0); }
|
||||
|
||||
// Clic sur un onglet ou sur un lien interne de la fiche (ligne « À faire »)
|
||||
document.addEventListener('click', function (e) {
|
||||
var lien = e.target.closest('a[href^="#"]');
|
||||
if (!lien || e.defaultPrevented || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
var ancre = lien.getAttribute('href');
|
||||
if (!cibleDe(ancre)) { return; }
|
||||
e.preventDefault();
|
||||
if (window.history.replaceState) { window.history.replaceState(null, '', ancre); }
|
||||
aller(ancre, true);
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', function () { aller(window.location.hash, true); });
|
||||
|
||||
// Clavier : flèches, Début et Fin d'un onglet à l'autre ; Espace ouvre l'onglet
|
||||
barreOnglets.addEventListener('keydown', function (e) {
|
||||
var i = onglets.indexOf(document.activeElement);
|
||||
if (i === -1) { return; }
|
||||
var n = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: onglets.length - 1 }[e.key];
|
||||
if (e.key === ' ') { e.preventDefault(); onglets[i].click(); return; }
|
||||
if (n === undefined) { return; }
|
||||
e.preventDefault();
|
||||
n = (n + onglets.length) % onglets.length;
|
||||
onglets[n].focus();
|
||||
onglets[n].click();
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Filtres : une liste déroulante changée relance la recherche, sans bouton à cliquer.
|
||||
// Sur écran étroit, les filtres des Profils se replient derrière un bouton « Filtres ».
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('form[data-auto]').forEach(function (form) {
|
||||
form.querySelectorAll('select').forEach(function (select) {
|
||||
select.addEventListener('change', function () {
|
||||
if (form.requestSubmit) { form.requestSubmit(); } else { form.submit(); }
|
||||
});
|
||||
});
|
||||
form.querySelectorAll('[data-auto-masquer]').forEach(function (b) { b.hidden = true; });
|
||||
});
|
||||
|
||||
document.querySelectorAll('form[data-repli]').forEach(function (form) {
|
||||
var listes = Array.prototype.slice.call(form.querySelectorAll('select'));
|
||||
var actifs = listes.filter(function (s) { return s.selectedIndex > 0; }).length;
|
||||
var bouton = document.createElement('button');
|
||||
bouton.type = 'button';
|
||||
bouton.className = 'secondaire bouton-filtres';
|
||||
bouton.textContent = textes.filtres + (actifs ? ' (' + actifs + ')' : '');
|
||||
var replier = function (replie) {
|
||||
form.classList.toggle('replie', replie);
|
||||
bouton.setAttribute('aria-expanded', replie ? 'false' : 'true');
|
||||
};
|
||||
bouton.addEventListener('click', function () { replier(!form.classList.contains('replie')); });
|
||||
var recherche = form.querySelector('label.large');
|
||||
form.insertBefore(bouton, recherche ? recherche.nextSibling : form.firstChild);
|
||||
replier(actifs === 0);
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Double authentification : le QR code se dessine à partir de l'adresse otpauth://
|
||||
// La clé reste écrite en clair à côté, pour une saisie à la main.
|
||||
// -------------------------------------------------------------------------
|
||||
document.querySelectorAll('[data-qr]').forEach(function (zone) {
|
||||
if (!window.qrcode) { zone.hidden = true; return; }
|
||||
var qr = window.qrcode(0, 'M');
|
||||
qr.addData(zone.dataset.qr);
|
||||
qr.make();
|
||||
zone.innerHTML = qr.createSvgTag({ cellSize: 5, margin: 3, scalable: true });
|
||||
});
|
||||
|
||||
// Copier un lien ou des codes dans le presse-papiers
|
||||
document.querySelectorAll('[data-copier]').forEach(function (bouton) {
|
||||
bouton.addEventListener('click', function () {
|
||||
var texte = bouton.dataset.copier;
|
||||
var fait = function () {
|
||||
var avant = bouton.textContent;
|
||||
bouton.textContent = bouton.dataset.copie || textes.copie;
|
||||
window.setTimeout(function () { bouton.textContent = avant; }, 2000);
|
||||
};
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(texte).then(fait);
|
||||
} else {
|
||||
var zone = document.createElement('textarea');
|
||||
zone.value = texte;
|
||||
document.body.appendChild(zone);
|
||||
zone.select();
|
||||
try { document.execCommand('copy'); fait(); } catch (e) { /* le texte reste sélectionnable à l'écran */ }
|
||||
zone.remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Traduction des profils en arrière-plan : une par requête, tant que la page reste ouverte.
|
||||
// Une erreur arrête la série ; la page Comptes la montre au gestionnaire.
|
||||
// -------------------------------------------------------------------------
|
||||
var fileTraduction = document.querySelector('meta[name="traductions"]');
|
||||
if (fileTraduction && parseInt(fileTraduction.content, 10) > 0) {
|
||||
var traduireSuivante = function () {
|
||||
var corps = new URLSearchParams();
|
||||
corps.set('csrf', csrf);
|
||||
fetch(fileTraduction.dataset.url, { method: 'POST', headers: { 'X-Requested-With': 'fetch' }, body: corps, credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (j) {
|
||||
if (j.fait && j.reste > 0) { window.setTimeout(traduireSuivante, 1500); }
|
||||
})
|
||||
.catch(function () { /* nouvel essai au prochain chargement de page */ });
|
||||
};
|
||||
window.setTimeout(traduireSuivante, 2500);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Modifier la fiche : la date n'apparaît que pour « Date précise » ;
|
||||
// le permis de travail ne concerne que les profils sans nationalité UE, EEE ou suisse
|
||||
// -------------------------------------------------------------------------
|
||||
var choixDispo = document.querySelector('select[name="disponibilite_code"]');
|
||||
if (choixDispo) {
|
||||
var champDate = document.getElementById('champ-disponible-le');
|
||||
var montrerDate = function () {
|
||||
var date = choixDispo.value === 'date';
|
||||
champDate.hidden = !date;
|
||||
champDate.querySelector('input').required = date;
|
||||
};
|
||||
choixDispo.addEventListener('change', montrerDate);
|
||||
montrerDate();
|
||||
}
|
||||
var nationalites = document.querySelectorAll('select[data-nationalite]');
|
||||
var blocPermis = document.getElementById('bloc-permis');
|
||||
if (nationalites.length && blocPermis) {
|
||||
var libres = (blocPermis.dataset.libres || '').split(',');
|
||||
var nonRequis = document.getElementById('permis-non-requis');
|
||||
var majPermis = function () {
|
||||
var codes = Array.prototype.map.call(nationalites, function (s) { return s.value; }).filter(Boolean);
|
||||
var libre = codes.some(function (c) { return libres.indexOf(c) !== -1; });
|
||||
blocPermis.hidden = libre;
|
||||
nonRequis.hidden = !libre;
|
||||
};
|
||||
nationalites.forEach(function (s) { s.addEventListener('change', majPermis); });
|
||||
majPermis();
|
||||
}
|
||||
})();
|
||||
Reference in new issue
Block a user