1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-21 16:39:12 +02:00
Files
nk20/apps/family/static/family/js/achievements.js
2025-07-18 21:01:15 +02:00

449 lines
14 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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')
})
// Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS
document.getElementById("consume_all").addEventListener('click', consumeAll)
})
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) {
b.quantity += 1
challenge = b
}
})
if (challenge == null) {
challenge = {
id: id,
name: name,
quantity: 1,
amount: amount,
}
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 +
'<span class="badge badge-dark badge-pill">' + challenge.quantity + '</span>')
})
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 () {
console.log("reset lancée")
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 () {
reset()
addMsg("Défis validés pour les familles!", 'success', 5000)
},
error: function (e) {
reset()
addMsg("Erreur lors de la création des achievements.",'danger',5000)
}
})
}
/**
* Create a new achievement through the API.
* @param family The selected family
* @param challenge The selected challenge
*/
function grantAchievement (family, challenge) {
console.log("grant lancée",family,challenge)
$.post('/api/family/achievement/',
{
csrfmiddlewaretoken: CSRF_TOKEN,
family: family.id,
challenge: challenge.id,
})
.done(function () {
reset()
addMsg("Défi validé pour la famille!", 'success', 5000)
})
.fail(function (e) {
reset()
if (e.responseJSON) {
errMsg(e.responseJSON)
} else if (e.responseText) {
errMsg(e.responseText)
} else {
errMsg("Erreur inconnue lors de la création de l'achievement.")
}
})
}
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)
console.log("autoCompleteFamily commence")
// 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) {
d.quantity += 1
disp = d
}
})
if (disp == null) {
disp = {
name: family.name,
id: family.id,
family: family,
quantity: 1
}
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 +
'<span class="badge badge-dark badge-pill">' +
disp.quantity + '</span>',
'')
})
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) {
if (disp.quantity > 1 || disp.id !== d.id) {
disp.quantity -= disp.id === d.id ? 1 : 0
new_families_display.push(disp)
html += li(family_prefix + '_' + disp.id, disp.name +
'<span class="badge badge-dark badge-pill">' + disp.quantity + '</span>')
}
})
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)
})
})
}
}