453 lines
14 KiB
JavaScript
453 lines
14 KiB
JavaScript
document.addEventListener("DOMContentLoaded", () => {
|
|
// Éléments du DOM
|
|
const yearFilter = document.getElementById("yearFilter");
|
|
const categoryFilter = document.getElementById("categoryFilter");
|
|
const typeFilter = document.getElementById("typeFilter");
|
|
const applyFilters = document.getElementById("applyFilters");
|
|
const transactionsList = document.getElementById("transactionsList");
|
|
const totalRevenue = document.getElementById("totalRevenue");
|
|
const totalExpenses = document.getElementById("totalExpenses");
|
|
const balanceAmount = document.getElementById("balanceAmount");
|
|
const revenuesByCategoryList = document.getElementById(
|
|
"revenuesByCategoryList"
|
|
);
|
|
const expensesByCategoryList = document.getElementById(
|
|
"expensesByCategoryList"
|
|
);
|
|
|
|
// Boutons d'onglets
|
|
const tabButtons = document.querySelectorAll(".tab-button");
|
|
const tabContents = document.querySelectorAll(".tab-content");
|
|
|
|
// Modal d'importation
|
|
const importButton = document.getElementById("importButton");
|
|
const importModal = document.getElementById("importModal");
|
|
const closeImportModal = document.getElementById("closeImportModal");
|
|
const importRevenues = document.getElementById("importRevenues");
|
|
const importExpenses = document.getElementById("importExpenses");
|
|
|
|
// Variables pour les graphiques
|
|
let revenuesChart = null;
|
|
let expensesChart = null;
|
|
|
|
// Initialiser la page
|
|
initPage();
|
|
|
|
// Gestionnaire d'événements pour les filtres
|
|
applyFilters.addEventListener("click", () => {
|
|
loadData({
|
|
year: yearFilter.value,
|
|
category: categoryFilter.value,
|
|
type: typeFilter.value,
|
|
});
|
|
});
|
|
|
|
// Gestionnaires d'événements pour les onglets
|
|
tabButtons.forEach((button) => {
|
|
button.addEventListener("click", () => {
|
|
const tabId = button.id.replace("tab", "").toLowerCase();
|
|
switchTab(tabId);
|
|
});
|
|
});
|
|
|
|
// Gestionnaires d'événements pour le modal d'importation
|
|
importButton.addEventListener("click", () => {
|
|
importModal.classList.remove("hidden");
|
|
});
|
|
|
|
closeImportModal.addEventListener("click", () => {
|
|
importModal.classList.add("hidden");
|
|
});
|
|
|
|
importRevenues.addEventListener("click", () => {
|
|
importData("revenue");
|
|
});
|
|
|
|
importExpenses.addEventListener("click", () => {
|
|
importData("expense");
|
|
});
|
|
|
|
// Fonction d'initialisation
|
|
function initPage() {
|
|
// Années pour le filtre (année courante et les deux précédentes)
|
|
const currentYear = new Date().getFullYear();
|
|
yearFilter.innerHTML = `<option value="">Toutes</option>`;
|
|
for (let year = currentYear; year >= currentYear - 2; year--) {
|
|
yearFilter.innerHTML += `<option value="${year}">${year}</option>`;
|
|
}
|
|
|
|
// Charger les données initiales
|
|
loadData();
|
|
|
|
// Générer les couleurs pour les graphiques
|
|
generateChartColors();
|
|
}
|
|
|
|
// Fonction pour charger les données
|
|
async function loadData(filters = {}) {
|
|
try {
|
|
const response = await fetch(
|
|
"/api/accounting?" + new URLSearchParams(filters)
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// Mettre à jour les statistiques
|
|
updateStatistics(data.statistics);
|
|
|
|
// Mettre à jour les transactions
|
|
displayTransactions(data.transactions);
|
|
|
|
// Mettre à jour les catégories de filtres
|
|
updateCategoryFilters(data.categories);
|
|
|
|
// Mettre à jour les graphiques
|
|
updateCharts(data.statistics);
|
|
} catch (error) {
|
|
console.error("Erreur lors du chargement des données:", error);
|
|
showMessage("Erreur lors du chargement des données", "error");
|
|
}
|
|
}
|
|
|
|
// Fonction pour importer des données
|
|
async function importData(type) {
|
|
try {
|
|
const response = await fetch(`/api/accounting/import/${type}`, {
|
|
method: "POST",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
showMessage(result.message || "Importation réussie");
|
|
importModal.classList.add("hidden");
|
|
|
|
// Recharger les données
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("Erreur lors de l'importation:", error);
|
|
showMessage("Erreur lors de l'importation", "error");
|
|
}
|
|
}
|
|
|
|
// Fonction pour mettre à jour les statistiques
|
|
function updateStatistics(stats) {
|
|
totalRevenue.textContent = `${stats.total_revenue.toFixed(2)} €`;
|
|
totalExpenses.textContent = `${stats.total_expenses.toFixed(2)} €`;
|
|
|
|
const balance = stats.balance;
|
|
balanceAmount.textContent = `${Math.abs(balance).toFixed(2)} €`;
|
|
|
|
if (balance >= 0) {
|
|
balanceAmount.classList.remove("text-red-500");
|
|
balanceAmount.classList.add("text-green-500");
|
|
} else {
|
|
balanceAmount.classList.remove("text-green-500");
|
|
balanceAmount.classList.add("text-red-500");
|
|
}
|
|
}
|
|
|
|
// Fonction pour mettre à jour les catégories du filtre
|
|
function updateCategoryFilters(categories) {
|
|
categoryFilter.innerHTML = '<option value="">Toutes</option>';
|
|
|
|
categories.forEach((category) => {
|
|
categoryFilter.innerHTML += `<option value="${category}">${category}</option>`;
|
|
});
|
|
}
|
|
|
|
// Fonction pour afficher les transactions
|
|
function displayTransactions(transactions) {
|
|
transactionsList.innerHTML = "";
|
|
|
|
transactions.forEach((transaction) => {
|
|
const row = document.createElement("tr");
|
|
|
|
const date = new Date(transaction.date);
|
|
const formattedDate = date.toLocaleDateString();
|
|
|
|
const isRevenue = transaction.type === "revenue";
|
|
const amount = `${isRevenue ? "+" : "-"} ${Math.abs(
|
|
transaction.amount
|
|
).toFixed(2)} €`;
|
|
const amountClass = isRevenue ? "text-green-500" : "text-red-500";
|
|
|
|
row.innerHTML = `
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${formattedDate}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${transaction.description}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium ${amountClass}">
|
|
${amount}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${transaction.category || "Non catégorisé"}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
|
${
|
|
isRevenue
|
|
? "bg-green-100 text-green-800"
|
|
: "bg-red-100 text-red-800"
|
|
}">
|
|
${isRevenue ? "Revenu" : "Dépense"}
|
|
</span>
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
|
<button onclick="editTransaction(${
|
|
transaction.id
|
|
})" class="text-blue-600 hover:text-blue-900 mr-3">
|
|
Éditer
|
|
</button>
|
|
<button onclick="deleteTransaction(${
|
|
transaction.id
|
|
})" class="text-red-600 hover:text-red-900">
|
|
Supprimer
|
|
</button>
|
|
</td>
|
|
`;
|
|
|
|
transactionsList.appendChild(row);
|
|
});
|
|
|
|
if (transactions.length === 0) {
|
|
transactionsList.innerHTML = `
|
|
<tr>
|
|
<td colspan="6" class="px-6 py-4 text-center text-sm text-gray-500">
|
|
Aucune transaction trouvée
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// Fonction pour changer d'onglet
|
|
function switchTab(tabId) {
|
|
// Mettre à jour les classes des boutons
|
|
tabButtons.forEach((button) => {
|
|
const buttonTabId = button.id.replace("tab", "").toLowerCase();
|
|
|
|
if (buttonTabId === tabId) {
|
|
button.classList.add("active", "border-blue-500", "text-blue-600");
|
|
button.classList.remove("border-transparent", "text-gray-500");
|
|
} else {
|
|
button.classList.remove("active", "border-blue-500", "text-blue-600");
|
|
button.classList.add("border-transparent", "text-gray-500");
|
|
}
|
|
});
|
|
|
|
// Afficher le contenu de l'onglet sélectionné
|
|
tabContents.forEach((content) => {
|
|
const contentId = content.id;
|
|
|
|
if (contentId === `${tabId}Content`) {
|
|
content.classList.remove("hidden");
|
|
} else {
|
|
content.classList.add("hidden");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Génération de couleurs pour les graphiques
|
|
function generateChartColors(count = 10) {
|
|
const colors = [
|
|
"#4F46E5",
|
|
"#10B981",
|
|
"#F59E0B",
|
|
"#EF4444",
|
|
"#EC4899",
|
|
"#8B5CF6",
|
|
"#06B6D4",
|
|
"#84CC16",
|
|
"#F97316",
|
|
"#6366F1",
|
|
];
|
|
|
|
// Si on a besoin de plus de couleurs, on les génère aléatoirement
|
|
if (count > colors.length) {
|
|
for (let i = colors.length; i < count; i++) {
|
|
const r = Math.floor(Math.random() * 200);
|
|
const g = Math.floor(Math.random() * 200);
|
|
const b = Math.floor(Math.random() * 200);
|
|
colors.push(`rgb(${r}, ${g}, ${b})`);
|
|
}
|
|
}
|
|
|
|
return colors;
|
|
}
|
|
|
|
// Mise à jour des graphiques
|
|
function updateCharts(statistics) {
|
|
updateRevenuesChart(statistics.revenue_by_category);
|
|
updateExpensesChart(statistics.expenses_by_category);
|
|
}
|
|
|
|
// Mise à jour du graphique des revenus
|
|
function updateRevenuesChart(revenuesByCategory) {
|
|
const ctx = document.getElementById("revenuesByCategoryChart");
|
|
const colors = generateChartColors(Object.keys(revenuesByCategory).length);
|
|
|
|
// Détruire le graphique précédent s'il existe
|
|
if (revenuesChart) {
|
|
revenuesChart.destroy();
|
|
}
|
|
|
|
// Créer le nouveau graphique
|
|
revenuesChart = new Chart(ctx, {
|
|
type: "pie",
|
|
data: {
|
|
labels: Object.keys(revenuesByCategory),
|
|
datasets: [
|
|
{
|
|
data: Object.values(revenuesByCategory),
|
|
backgroundColor: colors,
|
|
borderWidth: 1,
|
|
},
|
|
],
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: "right",
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function (context) {
|
|
const value = context.raw;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return `${value.toFixed(2)} € (${percentage}%)`;
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Mettre à jour la liste des revenus par catégorie
|
|
updateCategoryList(revenuesByCategoryList, revenuesByCategory);
|
|
}
|
|
|
|
// Mise à jour du graphique des dépenses
|
|
function updateExpensesChart(expensesByCategory) {
|
|
const ctx = document.getElementById("expensesByCategoryChart");
|
|
const colors = generateChartColors(Object.keys(expensesByCategory).length);
|
|
|
|
// Détruire le graphique précédent s'il existe
|
|
if (expensesChart) {
|
|
expensesChart.destroy();
|
|
}
|
|
|
|
// Créer le nouveau graphique
|
|
expensesChart = new Chart(ctx, {
|
|
type: "pie",
|
|
data: {
|
|
labels: Object.keys(expensesByCategory),
|
|
datasets: [
|
|
{
|
|
data: Object.values(expensesByCategory),
|
|
backgroundColor: colors,
|
|
borderWidth: 1,
|
|
},
|
|
],
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: "right",
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function (context) {
|
|
const value = context.raw;
|
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
|
const percentage = ((value / total) * 100).toFixed(1);
|
|
return `${value.toFixed(2)} € (${percentage}%)`;
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Mettre à jour la liste des dépenses par catégorie
|
|
updateCategoryList(expensesByCategoryList, expensesByCategory);
|
|
}
|
|
|
|
// Mise à jour des listes de catégories
|
|
function updateCategoryList(listElement, categoryData) {
|
|
listElement.innerHTML = "";
|
|
|
|
const total = Object.values(categoryData).reduce((a, b) => a + b, 0);
|
|
|
|
Object.entries(categoryData)
|
|
.sort((a, b) => b[1] - a[1]) // Trier par montant décroissant
|
|
.forEach(([category, amount]) => {
|
|
const percentage = ((amount / total) * 100).toFixed(1);
|
|
|
|
const row = document.createElement("tr");
|
|
row.innerHTML = `
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${category}
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${amount.toFixed(2)} €
|
|
</td>
|
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
${percentage}%
|
|
</td>
|
|
`;
|
|
|
|
listElement.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// Fonctions globales pour l'édition et la suppression
|
|
window.editTransaction = async (transactionId) => {
|
|
alert("Fonctionnalité d'édition à implémenter");
|
|
// TODO: Implémenter l'édition des transactions
|
|
};
|
|
|
|
window.deleteTransaction = async (transactionId) => {
|
|
if (confirm("Êtes-vous sûr de vouloir supprimer cette transaction ?")) {
|
|
try {
|
|
const response = await fetch(
|
|
`/api/accounting/transaction/${transactionId}`,
|
|
{
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
showMessage("Transaction supprimée avec succès");
|
|
loadData();
|
|
} catch (error) {
|
|
console.error("Erreur lors de la suppression:", error);
|
|
showMessage("Erreur lors de la suppression", "error");
|
|
}
|
|
}
|
|
};
|
|
|
|
// Fonction pour afficher des messages à l'utilisateur
|
|
function showMessage(message, type = "success") {
|
|
// On pourrait implémenter un système de notification plus élaboré ici
|
|
alert(message);
|
|
}
|
|
});
|