// Copyright (C) 2018-2025 by BDE ENS Paris-Saclay // SPDX-License-Identifier: GPL-3.0-or-later // When a transaction is performed, lock the interface to prevent spam clicks. var LOCK = false /** * Refresh the history table on the consumptions page. */ function refreshHistory () { $('#history').load('/family/manage/ #history') } $(document).ready(function () { // If hash of a category in the URL, then select this category // else select the first one if (location.hash) { $("a[href='" + location.hash + "']").tab('show') } else { $("a[data-toggle='tab']").first().tab('show') } // When selecting a category, change URL $(document.body).on('click', "a[data-toggle='tab']", function () { location.hash = this.getAttribute('href') }) }) notes = [] notes_display = [] buttons = [] // When the user searches an alias, we update the auto-completion autoCompleteFamily('note', 'note_list', notes, notes_display, 'note', 'user_note', 'profile_pic', function () { return true }) /** * Add a transaction from a button. * @param fam Where the money goes * @param amount The price of the item * @param type The type of the transaction (content type id for RecurrentTransaction) * @param category_id The category identifier * @param category_name The category name * @param template_id The identifier of the button * @param template_name The name of the button */ function addChallenge (id, name, amount) { var challenge = null /** Ajout de 1 à chaque clic d'un bouton déjà choisi */ buttons.forEach(function (b) { if (b.id === id) { challenge = b } }) if (challenge == null) { challenge = { id: id, name: name, } buttons.push(challenge) } const dc_obj = true const list = 'consos_list' let html = '' buttons.forEach(function (challenge) { html += li('conso_button_' + challenge.id, challenge.name) }) document.getElementById(list).innerHTML = html buttons.forEach((button) => { document.getElementById(`conso_button_${button.id}`).addEventListener('click', () => { if (LOCK) { return } removeNote(button, 'conso_button', buttons, list)() }) }) } /** * Reset the page as its initial state. */ function reset () { notes_display.length = 0 notes.length = 0 buttons.length = 0 document.getElementById('note_list').innerHTML = '' document.getElementById('consos_list').innerHTML = '' document.getElementById('note').value = '' document.getElementById('note').dataset.originTitle = '' $('#note').tooltip('hide') document.getElementById('profile_pic').src = '/static/member/img/default_picture.png' document.getElementById('profile_pic_link').href = '#' refreshHistory() LOCK = false } /** * Apply all transactions: all notes in `notes` buy each item in `buttons` */ function consumeAll () { if (LOCK) { return } LOCK = true let error = false if (notes_display.length === 0) { // ... gestion erreur ... error = true } if (buttons.length === 0) { // ... gestion erreur ... error = true } if (error) { LOCK = false return } // Récupérer les IDs des familles et des challenges const family_ids = notes_display.map(fam => fam.id) const challenge_ids = buttons.map(chal => chal.id) $.ajax({ url: '/family/api/family/achievements/batch/', type: 'POST', data: JSON.stringify({ families: family_ids, challenges: challenge_ids }), contentType: 'application/json', headers: { 'X-CSRFToken': CSRF_TOKEN }, success: function (data) { reset() data.results.forEach(function (result) { if (result.status === 'created') { addMsg( interpolate(gettext('Invalid achievement for challenge %s ' + 'and family %s created.'), [result.challenge, result.family]), 'success', 5000 ) } else { addMsg( interpolate(gettext('An achievement for challenge %s ' + 'and family %s already exists.'), [result.challenge, result.family]), 'danger', 8000 ) } }) } }) } var searchbar = document.getElementById("search-input") var search_results = document.getElementById("search-results") var old_pattern = null; var firstMatch = null; /** * Updates the button search tab * @param force Forces the update even if the pattern didn't change */ function updateSearch(force = false) { let pattern = searchbar.value if (pattern === "") firstMatch = null; if ((pattern === old_pattern || pattern === "") && !force) return; firstMatch = null; const re = new RegExp(pattern, "i"); Array.from(search_results.children).forEach(function(b) { if (re.test(b.innerText)) { b.hidden = false; if (firstMatch === null) { firstMatch = b; } } else b.hidden = true; }); } searchbar.addEventListener("input", function (e) { debounce(updateSearch)() }); searchbar.addEventListener("keyup", function (e) { if (firstMatch && e.key === "Enter") firstMatch.click() }); function createshiny() { const list_btn = document.querySelectorAll('.btn-outline-dark') const shiny_class = list_btn[Math.floor(Math.random() * list_btn.length)].classList shiny_class.replace('btn-outline-dark', 'btn-outline-dark-shiny') } createshiny() /** * Query the 20 first matched notes with a given pattern * @param pattern The pattern that is queried * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. */ function getMatchedFamilies (pattern, fun) { $.getJSON('/api/family/family/?format=json&alias=' + pattern + '&search=family', fun) } /** * Generate a
  • entry with a given id and text */ function li (id, text, extra_css) { return '
  • ' + text + '
  • \n' } /** * Génère un champ d'auto-complétion pour rechercher une famille par son nom (version simplifiée sans alias) * @param field_id L'identifiant du champ texte où le nom est saisi * @param family_list_id L'identifiant du bloc div où les familles sélectionnées sont affichées * @param families Un tableau contenant les objets famille sélectionnés * @param families_display Un tableau contenant les infos des familles sélectionnées : [nom, id, objet famille, quantité] * @param family_prefix Le préfixe des
  • pour les familles sélectionnées * @param user_family_field L'identifiant du champ qui affiche la famille survolée (optionnel) * @param profile_pic_field L'identifiant du champ qui affiche la photo de la famille survolée (optionnel) * @param family_click Fonction appelée lors du clic sur un nom. Si elle existe et ne retourne pas true, la famille n'est pas affichée. */ function autoCompleteFamily(field_id, family_list_id, families, families_display, family_prefix = 'family', user_family_field = null, profile_pic_field = null, family_click = null) { const field = $('#' + field_id) // Configuration du tooltip field.tooltip({ html: true, placement: 'bottom', title: 'Chargement...', trigger: 'manual', container: field.parent(), fallbackPlacement: 'clockwise' }) // Masquer le tooltip lors d'un clic ailleurs $(document).click(function (e) { if (!e.target.id.startsWith(family_prefix)) { field.tooltip('hide') } }) let old_pattern = null // Réinitialiser la recherche au clic field.click(function () { field.tooltip('hide') field.removeClass('is-invalid') field.val('') old_pattern = '' }) // Sur "Entrée", sélectionner la première famille field.keypress(function (event) { if (event.originalEvent.charCode === 13 && families.length > 0) { const li_obj = field.parent().find('ul li').first() displayFamily(families[0], families[0].name, user_family_field, profile_pic_field) li_obj.trigger('click') } }) // Mise à jour des suggestions lors de la saisie field.keyup(function (e) { field.removeClass('is-invalid') if (e.originalEvent.charCode === 13) { return } const pattern = field.val() if (pattern === old_pattern) { return } old_pattern = pattern families.length = 0 if (pattern === '') { field.tooltip('hide') families.length = 0 return } // Appel à l'API pour récupérer les familles correspondantes $.getJSON('/api/family/family/?format=json&search=' + pattern, function (results) { if (pattern !== $('#' + field_id).val()) { return } let matched_html = '' field.attr('data-original-title', matched_html).tooltip('show') results.results.forEach(function (family) { const family_obj = $('#' + family_prefix + '_' + family.id) family_obj.hover(function () { displayFamily(family, family.name, user_family_field, profile_pic_field) }) family_obj.click(function () { var disp = null families_display.forEach(function (d) { if (d.id === family.id) { disp = d } }) if (disp == null) { disp = { name: family.name, id: family.id, family: family, } families_display.push(disp) } if (family_click && !family_click()) { return } const family_list = $('#' + family_list_id) let html = '' families_display.forEach(function (disp) { html += li(family_prefix + '_' + disp.id, disp.name, '') }) family_list.html(html) field.tooltip('update') families_display.forEach(function (disp) { const line_obj = $('#' + family_prefix + '_' + disp.id) line_obj.hover(function () { displayFamily(disp.family, disp.name, user_family_field, profile_pic_field) }) line_obj.click(removeFamily(disp, family_prefix, families_display, family_list_id, user_family_field, profile_pic_field)) }) }) }) }) }) } /** * Affiche le nom et la photo d'une famille * @param family L'objet famille à afficher * @param user_family_field L'identifiant du champ où afficher le nom (optionnel) * @param profile_pic_field L'identifiant du champ où afficher la photo (optionnel) */ function displayFamily(family, user_family_field = null, profile_pic_field = null) { if (!family.display_image) { family.display_image = '/static/member/img/default_picture.png' } if (user_family_field !== null) { $('#' + user_family_field).removeAttr('class') $('#' + user_family_field).text(family.name) if (profile_pic_field != null) { $('#' + profile_pic_field).attr('src', family.display_image) // Si tu veux un lien vers la page famille : $('#' + profile_pic_field + '_link').attr('href', '/family/detail/' + family.id + '/') } } } /** * Retire une famille de la liste sélectionnée. * @param d La famille à retirer * @param family_prefix Le préfixe des
  • * @param families_display Le tableau des familles sélectionnées * @param family_list_id L'id du bloc où sont affichées les familles * @param user_family_field Champ d'affichage (optionnel) * @param profile_pic_field Champ photo (optionnel) * @returns une fonction compatible avec les événements jQuery */ function removeFamily(d, family_prefix, families_display, family_list_id, user_family_field = null, profile_pic_field = null) { return function () { const new_families_display = [] let html = '' families_display.forEach(function (disp) { }) families_display.length = 0 new_families_display.forEach(function (disp) { families_display.push(disp) }) $('#' + family_list_id).html(html) families_display.forEach(function (disp) { const obj = $('#' + family_prefix + '_' + disp.id) obj.click(removeFamily(disp, family_prefix, families_display, family_list_id, user_family_field, profile_pic_field)) obj.hover(function () { displayFamily(disp.family, user_family_field, profile_pic_field) }) }) } }