1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-09-03 18:10:01 +02:00
Files
nk20/apps/family/static/family/js/achievements.js

411 lines
13 KiB
JavaScript

// 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 <li> entry with a given id and text
*/
function li (id, text, extra_css) {
return '<li class="list-group-item py-1 px-2 d-flex justify-content-between align-items-center text-truncate ' +
(extra_css || '') + '"' + ' id="' + id + '">' + text + '</li>\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 <li> 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 = '<ul class="list-group list-group-flush">'
results.results.forEach(function (family) {
matched_html += li(family_prefix + '_' + family.id,
family.name,
'')
families.push(family)
})
matched_html += '</ul>'
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 <li>
* @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)
})
})
}
}