mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-07-21 16:39:12 +02:00
449 lines
14 KiB
JavaScript
449 lines
14 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')
|
||
})
|
||
|
||
|
||
// 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)
|
||
})
|
||
})
|
||
}
|
||
} |