Skip to content
Extraits de code Groupes Projets
script.js 12,26 Kio

// Ce script permet d'afficher un tableau de tickets et de lancer l'ouverture de nouveaux tickets sur la ForgeEdu.

// ID du projet
const idProjet = 3318;
// URL de l'API
const apiUrl = `https://forge.apps.education.fr/api/v4/projects/${idProjet}/issues`;
// Préfixe appli pour le localStorage
const prefixeAppli = 'laboiteareves';

// Quelle heure est-il ?
const now = new Date();



const inputDescriptions = document.getElementById("inputDescriptions");
const tableBody = document.querySelector("#issuesTable tbody");

const etatTri = {
    cle: null, // La clé actuellement triée
    croissant: true // Par défaut, tri croissant
};

// Labels
const typesLabels = [

    {
        nom: 'help',
        description : "appel à contributions sur un projet",
        ticket : "Sur l'application ... j'aurais besoin de ...",
        couleur : 'rgb(204, 51, 139)'
    },

    {
        nom: 'idée',
        description : "proposition de nouvelle application",
        ticket : "J'ai un rêve d'application ...",
        couleur : 'rgb(0, 153, 102)'
    },

    {
        nom: 'bug',
        description : "signalement de bug",
        ticket : "J'ai constaté que...",
        couleur : '#dc143c'
    },

    {
        nom: 'épinglé',
        description : "pour afficher en haut",
        ticket : null,
        couleur : '#9400d3'
    }
];

let labelsActifs = ['help','idée'];
const tousLesSpanLabels = document.querySelectorAll('#choixLabels span');

//Lancement de l'appli
    async function lancementAppli() { 
    await verifierLocalStorage();
    preparerlesInputLabel();
    fetchIssues();
    toggleDescriptions();
    trierTableau(etatTri.cle);   
}
lancementAppli();

async function verifierLocalStorage() {
    let valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'labelsActifs');
    if (valeurARecuperer) {
        labelsActifs = Array.from(valeurARecuperer.split(','));
    }

    valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'descriptions');
    inputDescriptions.checked = valeurARecuperer != 'fase';

    valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'trierPar');
    etatTri.cle = valeurARecuperer || 'date';


    valeurARecuperer = localStorage.getItem(prefixeAppli+'-'+'triCroissant');
    etatTri.croissant = valeurARecuperer != 'false';
    
}


function preparerlesInputLabel() {

    tousLesSpanLabels.forEach(span => {

        // Récupérer la case à cocher
        const caseACocher = span.querySelector('input');
        // Cocher la case
        if (labelsActifs.includes(caseACocher.value)) {
            caseACocher.checked=true;
        }
        // Affecter la bonne couleur
        let couleur = typesLabels.find(item => item.nom === caseACocher.value).couleur;
        span.style.backgroundColor = couleur;

    });

}

// Fonction pour récupérer les tickets
async function fetchIssues() {

    try {

        // Récupération des tickets
        const response = await fetch(apiUrl);
        if (!response.ok) {
            throw new Error("Erreur lors de la récupération des données.");
        }
        let issues = await response.json();

        issues = issues
            .filter(issue => issue.state !== 'closed') // Suppression des issues fermées
            .filter(issue => !issue.labels.some(label => label.toLowerCase() === 'bug')) // Suppression des issues avec label "bug"
            .sort((a, b) => {
                // Priorité aux issues avec 'épinglé' dans leur label
                const aPinned = a.labels.some(label => label.toLowerCase().includes('épinglé'));
                const bPinned = b.labels.some(label => label.toLowerCase().includes('épinglé'));

                if (aPinned && !bPinned) return -1; // 'a' doit être avant 'b'
                if (!aPinned && bPinned) return 1;  // 'b' doit être avant 'a'

                // Sinon, tri par dernière réponse
                return new Date(b.updated_at) - new Date(a.updated_at);
        });

    


        // Affichage des tickets
        displayIssues(issues);

    } catch (error) {
        console.error("Erreur:", error);        
        tableBody.innerHTML = `<tr><td colspan="4">Impossible de charger les tickets.</td></tr>`;
    }
}

// Fonction pour afficher les tickets
function displayIssues(issues) {

    tableBody.innerHTML = ""; // Vider le tableau

    issues.forEach(issue => {

        console.log(issue.user_notes_count)

        let description = issue.description;
        // Limiter à 200 caractères la description
        if (description.length > 200) {
            description = `${description.slice(0, 200)} ...`;

            // Découper les 20 derniers caractères et construire la chaîne avec styles
            if (description.length > 50) {
                const mainText = description.slice(0, -50); // Texte principal sans les 20 derniers
                const fadingText = description.slice(-50); // Les 20 derniers caractères
                description = `
                <span class="text-main">${mainText}</span>
                <span class="text-fade">${fadingText}</span>
                `;
            }
        }
        
        // Récupération du label
        const label = issue.labels[0] || '';
               
        

        if (label) {

            // Trouver l'objet correspondant dans le tableau typesLabels
            let type = typesLabels.find(item => item.nom === label);

            // Récupérer la couleur ou définir une valeur par défaut si non trouvée
            let couleur = type ? type.couleur : 'yellow'; 

            spanLabel = `<span class="label" style="background-color:${couleur}">${label}</span>`;

        } else {

            spanLabel = '';

        }

        console.log(`
            ------------ TICKET ------------
            ${issue.title || 'Pas de titre'}
            ${issue.state || 'Statut indéfini'}
            ${label || 'Pas de label'}
            --------------------------------
            `)

        let date = new Date(issue.updated_at);
        const avatar = issue.author.avatar_url ? `<img class="avatar" src="${issue.author.avatar_url}">` : "👤";

        // Création d'une ligne dans le tableau
        const row = document.createElement("tr");
        row.innerHTML = `
            <td>${spanLabel}<a class="titre" href="${issue.web_url}" target="_blank">${issue.title}</a>
            <div class="description">
            ${description}
            <a class="bouton" href="${issue.web_url}" target="_blank">... la suite</a>
            </div>            
            </td>
            <td class="nombre">${issue.user_notes_count} 🗨️</td>
            <td class="nombre">${issue.upvotes} 👍</td>
            <td class="auteur">${avatar} <a target="_blank" href="${issue.author.web_url}">${issue.author.name}</a></td>
            <td>${formatDate(date)}</td>
        `;

        if (label) {

            row.label = label;
            
            if (label!='épinglé' && !labelsActifs.includes(label)) {
                row.classList.add('hide');
            }
        }

        row.auteur = issue.author.name;
        row.titre = issue.title;
        row.reponses = issue.user_notes_count;
        row.date = date;
        row.votes = issue.upvotes;

        tableBody.appendChild(row);
    });
}

// Pour afficher la date
function formatDate(date) {
    const diffMs = now - date; // Différence en millisecondes
    const diffMinutes = Math.floor(diffMs / 60000);
    const diffHours = Math.floor(diffMs / (3600 * 1000));

    // Vérifications
    if (diffMinutes < 5) {
        return "à l'instant";
    } else if (diffHours < 2) {
        return "- de 2H";
    } else if (date.toDateString() === now.toDateString()) {
        return "aujourd'hui";
    } else {
        const yesterday = new Date(now);
        yesterday.setDate(now.getDate() - 1);
        if (date.toDateString() === yesterday.toDateString()) {
            return "Hier";
        }
    }

    // Date par défaut
    return date.toLocaleDateString();
}


// Fonction pour ouvrir une fenêtre Gitlab de nouveau ticket
function ajouterTicket(label) {
    console.log('ajout de ticket',label);

    const url = 'https://forge.apps.education.fr/laboiteareves/laboiteareves.forge.apps.education.fr/-/issues/new'; // URL de création de ticket

    // Trouver l'objet correspondant dans le tableau typesLabels
    let type = typesLabels.find(item => item.nom === label);

    // Récupérer le motif ticket à utiliser
    let motif = type.ticket;

    const description = `/label ${label} <!-- Merci de ne pas supprimer cette ligne, qui permet de bien catégoriser votre proposition -->\n\n${motif} `;

    // Définir les paramètres pour pré-remplir le ticket
    const params = new URLSearchParams();
    //params.set('issue[title]', 'Nouveau ticket');
    params.set('issue[description]', description);

    const fullUrl = `${url}?${params.toString()}`;

    window.open(fullUrl, "_self");
}


// Fonction pour mettre à jour les labels actifs
function majLabelsActifs(){

    // On vide le tableau
    labelsActifs = [];

    // On le remplit
    tousLesSpanLabels.forEach(span => {

        // Récupérer la case à cocher
        const caseACocher = span.querySelector('input');

        // Ajouter le label au tableau
        if (caseACocher.checked) {
            labelsActifs.push(caseACocher.value);
        }
    });

    filtreTickets();
    localStorage.setItem(prefixeAppli+'-'+'labelsActifs',labelsActifs.join(','));

}


// Fonction pour filtrer les tickets
function filtreTickets() {

    // On récupère les lignes
    const lignesDuTableau = document.querySelectorAll('#issuesTable tbody tr');

    lignesDuTableau.forEach(ligne => {

        // On vérifie si le label est dans le tableau des labels actifs
        const estActif = labelsActifs.includes(ligne.label) || ligne.label === 'épinglé';

        // On met à jour la classe hide selon
        if (estActif) {
            ligne.classList.remove('hide');
        } else {
            ligne.classList.add('hide');
        }

    });

}



function trierTableau(cle) {

    console.log('Tri par', cle);

    let entete = document.getElementById(cle);

    const entetes = document.querySelectorAll('#issuesTable th');
    entetes.forEach(enteteCourante => {        
        enteteCourante.classList.remove('actif');
    });
    entete.classList.add('actif');

    const tableau = document.getElementById('issuesTable');
    const tbody = tableau.querySelector('tbody');
    const lignes = Array.from(tbody.querySelectorAll('tr'));

    // Déterminer le sens du tri
    if (etatTri.cle === cle) {
        // Inverser le sens si la même colonne est sélectionnée
        etatTri.croissant = !etatTri.croissant;
    } else {
        // Sinon, on trie par cette clé en croissant
        etatTri.cle = cle;
        etatTri.croissant = true;
    }

    localStorage.setItem(prefixeAppli+'-'+'trierPar',etatTri.cle);
    localStorage.setItem(prefixeAppli+'-'+'triCroissant',etatTri.croissant);


    // Trier les lignes en fonction du label 'épinglé' et des valeurs
    lignes.sort((a, b) => {
        // Vérifier si l'un des éléments est "épinglé"
        if (a.label === 'épinglé' && b.label !== 'épinglé') return -1;
        if (a.label !== 'épinglé' && b.label === 'épinglé') return 1;

        // Sinon, on effectue le tri habituel
        const valeurA = a[cle];
        const valeurB = b[cle];

        let comparaison = 0;

        if (valeurA instanceof Date && valeurB instanceof Date) {
            comparaison = valeurA - valeurB; // Tri par date
        } else if (typeof valeurA === 'number' && typeof valeurB === 'number') {
            comparaison = valeurA - valeurB; // Tri par nombre
        } else {
            comparaison = valeurA.localeCompare(valeurB, undefined, { sensitivity: 'base' }); // Tri par chaîne
        }

        // Inverser l'ordre si tri décroissant
        return etatTri.croissant ? comparaison : -comparaison;
    });

    // Réinsérer les lignes triées dans le tableau
    lignes.forEach(ligne => tbody.appendChild(ligne));

    console.log(`Tri effectué par '${cle}' en ordre ${etatTri.croissant ? 'croissant' : 'décroissant'}`);
}


function toggleDescriptions(){
    if (inputDescriptions.checked){
        tableBody.classList.remove('nodescriptions');
    } else {
        tableBody.classList.add('nodescriptions');
    }
    localStorage.setItem(prefixeAppli+'-'+'descriptions',inputDescriptions.checked);

}