rozbudowa wykresow o kategorie i usuniecie dupliakcji kodu z apnelu admina

This commit is contained in:
Mateusz Gruszczyński
2025-08-01 11:31:17 +02:00
parent 2df64bbe2e
commit cfae8571de
11 changed files with 358 additions and 451 deletions

View File

@@ -1,11 +1,14 @@
document.addEventListener("DOMContentLoaded", function () {
let expensesChart = null;
let selectedCategoryId = "";
let categorySplit = false;
let categorySplit = true;
const rangeLabel = document.getElementById("chartRangeLabel");
function loadExpenses(range = "monthly", startDate = null, endDate = null) {
let url = '/user_expenses_data?range=' + range;
if (typeof window.selectedCategoryId === "undefined") {
window.selectedCategoryId = "";
}
function loadExpenses(range = "last30days", startDate = null, endDate = null) {
let url = '/expenses_data?range=' + range;
const showAllCheckbox = document.getElementById("showAllLists");
if (showAllCheckbox && showAllCheckbox.checked) {
url += '&show_all=true';
@@ -13,8 +16,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (startDate && endDate) {
url += `&start_date=${startDate}&end_date=${endDate}`;
}
if (selectedCategoryId) {
url += `&category_id=${selectedCategoryId}`;
if (window.selectedCategoryId) {
url += `&category_id=${window.selectedCategoryId}`;
}
if (categorySplit) {
url += '&by_category=true';
@@ -32,23 +35,13 @@ document.addEventListener("DOMContentLoaded", function () {
if (categorySplit) {
expensesChart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: data.datasets
},
data: { labels: data.labels, datasets: data.datasets },
options: {
responsive: true,
plugins: {
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function (context) {
const value = context.raw;
if (!value) return null;
return `${context.dataset.label}: ${value}`;
}
}
intersect: false
},
legend: { position: 'top' }
},
@@ -58,9 +51,7 @@ document.addEventListener("DOMContentLoaded", function () {
}
}
});
}
else {
// Tryb zwykły
} else {
expensesChart = new Chart(ctx, {
type: 'bar',
data: {
@@ -82,20 +73,23 @@ document.addEventListener("DOMContentLoaded", function () {
rangeLabel.textContent = `Widok: własny zakres (${startDate}${endDate})`;
} else {
let labelText = "";
if (range === "monthly") labelText = "Widok: miesięczne";
if (range === "last30days") labelText = "Widok: ostatnie 30 dni";
else if (range === "currentmonth") labelText = "Widok: bieżący miesiąc";
else if (range === "monthly") labelText = "Widok: miesięczne";
else if (range === "quarterly") labelText = "Widok: kwartalne";
else if (range === "halfyearly") labelText = "Widok: półroczne";
else if (range === "yearly") labelText = "Widok: roczne";
rangeLabel.textContent = labelText;
}
})
.catch(error => {
console.error("Błąd pobierania danych:", error);
});
.catch(error => console.error("Błąd pobierania danych:", error));
}
// Obsługa przycisku przełączania trybu
document.getElementById("toggleCategorySplit").addEventListener("click", function () {
// Udostępnienie globalne, żeby inne skrypty mogły wywołać reload
window.loadExpenses = loadExpenses;
const toggleBtn = document.getElementById("toggleCategorySplit");
toggleBtn.addEventListener("click", function () {
categorySplit = !categorySplit;
if (categorySplit) {
this.textContent = "🔵 Pokaż całościowo";
@@ -106,9 +100,13 @@ document.addEventListener("DOMContentLoaded", function () {
this.classList.remove("btn-outline-info");
this.classList.add("btn-outline-warning");
}
loadExpenses(); // przeładuj wykres
loadExpenses();
});
toggleBtn.textContent = "🔵 Pokaż całościowo";
toggleBtn.classList.remove("btn-outline-warning");
toggleBtn.classList.add("btn-outline-info");
const startDateInput = document.getElementById("startDate");
const endDateInput = document.getElementById("endDate");
@@ -119,8 +117,6 @@ document.addEventListener("DOMContentLoaded", function () {
startDateInput.value = formatDate(lastWeek);
endDateInput.value = formatDate(today);
loadExpenses();
document.getElementById('customRangeBtn').addEventListener('click', function () {
const startDate = startDateInput.value;
const endDate = endDateInput.value;
@@ -141,12 +137,13 @@ document.addEventListener("DOMContentLoaded", function () {
});
});
document.querySelectorAll('.category-filter').forEach(btn => {
btn.addEventListener('click', function () {
document.querySelectorAll('.category-filter').forEach(b => b.classList.remove('active'));
this.classList.add('active');
selectedCategoryId = this.dataset.categoryId || "";
loadExpenses();
});
// Automatyczne ładowanie danych po przełączeniu na zakładkę Wykres
document.getElementById('chart-tab').addEventListener('shown.bs.tab', function () {
loadExpenses();
});
// Jeśli jesteśmy od razu na zakładce Wykres
if (document.getElementById('chart-tab').classList.contains('active')) {
loadExpenses("last30days");
}
});

11
static/js/expense_tab.js Normal file
View File

@@ -0,0 +1,11 @@
document.addEventListener("DOMContentLoaded", function () {
// Sprawdzamy, czy hash w URL to #chartTab
if (window.location.hash === "#chartTab") {
const chartTabTrigger = document.querySelector('#chart-tab');
if (chartTabTrigger) {
// Wymuszenie aktywacji zakładki Bootstrap
const tab = new bootstrap.Tab(chartTabTrigger);
tab.show();
}
}
});

176
static/js/expense_table.js Normal file
View File

@@ -0,0 +1,176 @@
document.addEventListener('DOMContentLoaded', () => {
const checkboxes = document.querySelectorAll('.list-checkbox');
const totalEl = document.getElementById('listsTotal');
const filterButtons = document.querySelectorAll('.range-btn');
const rows = document.querySelectorAll('#listsTableBody tr');
const categoryButtons = document.querySelectorAll('.category-filter');
const onlyWith = document.getElementById('onlyWithExpenses');
window.selectedCategoryId = "";
let initialLoad = true; // flaga - true tylko przy pierwszym wejściu
function updateTotal() {
let total = 0;
checkboxes.forEach(cb => {
const row = cb.closest('tr');
if (cb.checked && row.style.display !== 'none') {
total += parseFloat(cb.dataset.amount);
}
});
totalEl.textContent = total.toFixed(2) + ' PLN';
}
function getISOWeek(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const dayDiff = (target - firstThursday) / 86400000;
return 1 + Math.floor(dayDiff / 7);
}
function filterByRange(range) {
const now = new Date();
const todayStr = now.toISOString().slice(0, 10);
const year = now.getFullYear();
const month = now.toISOString().slice(0, 7);
const week = `${year}-${String(getISOWeek(now)).padStart(2, '0')}`;
let startDate = null;
let endDate = null;
if (range === 'last30days') {
endDate = now;
startDate = new Date();
startDate.setDate(endDate.getDate() - 29);
}
if (range === 'currentmonth') {
startDate = new Date(year, now.getMonth(), 1);
endDate = now;
}
rows.forEach(row => {
const rDate = row.dataset.date;
const rMonth = row.dataset.month;
const rWeek = row.dataset.week;
const rYear = row.dataset.year;
const rowDateObj = new Date(rDate);
let show = true;
if (range === 'day') show = rDate === todayStr;
else if (range === 'month') show = rMonth === month;
else if (range === 'week') show = rWeek === week;
else if (range === 'year') show = rYear === String(year);
else if (range === 'all') show = true;
else if (range === 'last30days') show = rowDateObj >= startDate && rowDateObj <= endDate;
else if (range === 'currentmonth') show = rowDateObj >= startDate && rowDateObj <= endDate;
row.style.display = show ? '' : 'none';
});
}
function filterByLast30Days() {
filterByRange('last30days');
}
function applyExpenseFilter() {
if (!onlyWith || !onlyWith.checked) return;
rows.forEach(row => {
const amt = parseFloat(row.querySelector('.list-checkbox').dataset.amount || 0);
if (amt <= 0) row.style.display = 'none';
});
}
function applyCategoryFilter() {
if (!window.selectedCategoryId) return;
rows.forEach(row => {
const categoriesStr = row.dataset.categories || "";
const categories = categoriesStr ? categoriesStr.split(",") : [];
if (window.selectedCategoryId === "none") {
// Bez kategorii
if (categoriesStr.trim() !== "") {
row.style.display = 'none';
}
} else {
// Normalne filtrowanie po ID kategorii
if (!categories.includes(String(window.selectedCategoryId))) {
row.style.display = 'none';
}
}
});
}
// Obsługa checkboxów wierszy
checkboxes.forEach(cb => cb.addEventListener('change', updateTotal));
// Obsługa przycisków zakresu
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
initialLoad = false; // po kliknięciu wyłączamy tryb startowy
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const range = btn.dataset.range;
filterByRange(range);
applyExpenseFilter();
applyCategoryFilter();
updateTotal();
});
});
// Checkbox "tylko z wydatkami"
if (onlyWith) {
onlyWith.addEventListener('change', () => {
if (initialLoad) {
filterByLast30Days();
} else {
const activeRange = document.querySelector('.range-btn.active');
if (activeRange) {
filterByRange(activeRange.dataset.range);
}
}
applyExpenseFilter();
applyCategoryFilter();
updateTotal();
});
}
// Obsługa kliknięcia w kategorię
categoryButtons.forEach(btn => {
btn.addEventListener('click', () => {
categoryButtons.forEach(b => b.classList.remove('btn-success', 'active'));
categoryButtons.forEach(b => b.classList.add('btn-outline-light'));
btn.classList.remove('btn-outline-light');
btn.classList.add('btn-success', 'active');
window.selectedCategoryId = btn.dataset.categoryId || "";
if (initialLoad) {
filterByLast30Days();
} else {
const activeRange = document.querySelector('.range-btn.active');
if (activeRange) {
filterByRange(activeRange.dataset.range);
}
}
applyExpenseFilter();
applyCategoryFilter();
updateTotal();
const chartTab = document.querySelector('#chart-tab');
if (chartTab && chartTab.classList.contains('active') && typeof window.loadExpenses === 'function') {
window.loadExpenses();
}
});
});
// Start domyślnie ostatnie 30 dni
filterByLast30Days();
applyExpenseFilter();
applyCategoryFilter();
updateTotal();
});

View File

@@ -1,93 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
let expensesChart = null;
const rangeLabel = document.getElementById("chartRangeLabel");
function loadExpenses(range = "monthly", startDate = null, endDate = null) {
let url = '/admin/expenses_data?range=' + range;
if (startDate && endDate) {
url += `&start_date=${startDate}&end_date=${endDate}`;
}
fetch(url, { cache: "no-store" })
.then(response => response.json())
.then(data => {
const ctx = document.getElementById('expensesChart').getContext('2d');
if (expensesChart) {
expensesChart.destroy();
}
expensesChart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.labels,
datasets: [{
label: 'Suma wydatków [PLN]',
data: data.expenses,
backgroundColor: '#0d6efd'
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
if (startDate && endDate) {
rangeLabel.textContent = `Widok: własny zakres (${startDate}${endDate})`;
} else {
let labelText = "";
if (range === "monthly") labelText = "Widok: miesięczne";
else if (range === "quarterly") labelText = "Widok: kwartalne";
else if (range === "halfyearly") labelText = "Widok: półroczne";
else if (range === "yearly") labelText = "Widok: roczne";
rangeLabel.textContent = labelText;
}
})
.catch(error => {
console.error("Błąd pobierania danych:", error);
});
}
document.getElementById('loadExpensesBtn').addEventListener('click', function () {
loadExpenses();
});
document.querySelectorAll('.range-btn').forEach(btn => {
btn.addEventListener('click', function () {
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
const range = this.getAttribute('data-range');
loadExpenses(range);
});
});
document.getElementById('customRangeBtn').addEventListener('click', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
if (startDate && endDate) {
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
loadExpenses('custom', startDate, endDate);
} else {
alert("Proszę wybrać obie daty!");
}
});
});
document.addEventListener("DOMContentLoaded", function () {
const startDateInput = document.getElementById("startDate");
const endDateInput = document.getElementById("endDate");
const today = new Date();
const threeDaysAgo = new Date(today);
threeDaysAgo.setDate(today.getDate() - 7);
const formatDate = (d) => d.toISOString().split('T')[0];
startDateInput.value = formatDate(threeDaysAgo);
endDateInput.value = formatDate(today);
});

View File

@@ -1,22 +0,0 @@
document.addEventListener("DOMContentLoaded", function () {
const categoryButtons = document.querySelectorAll(".category-filter");
const rows = document.querySelectorAll("#listsTableBody tr");
categoryButtons.forEach(btn => {
btn.addEventListener("click", function () {
const selectedCat = this.dataset.category;
categoryButtons.forEach(b => b.classList.remove("active"));
this.classList.add("active");
rows.forEach(row => {
const rowCats = row.dataset.categories ? row.dataset.categories.split(",") : [];
if (selectedCat === "all" || rowCats.includes(selectedCat)) {
row.style.display = "";
} else {
row.style.display = "none";
}
});
});
});
});

View File

@@ -1,158 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const checkboxes = document.querySelectorAll('.list-checkbox');
const totalEl = document.getElementById('listsTotal');
const filterButtons = document.querySelectorAll('.range-btn');
const rows = document.querySelectorAll('#listsTableBody tr');
const onlyWith = document.getElementById('onlyWithExpenses');
const customStart = document.getElementById('customStart');
const customEnd = document.getElementById('customEnd');
if (localStorage.getItem('customStart')) {
customStart.value = localStorage.getItem('customStart');
}
if (localStorage.getItem('customEnd')) {
customEnd.value = localStorage.getItem('customEnd');
}
function updateTotal() {
let total = 0;
checkboxes.forEach(cb => {
const row = cb.closest('tr');
if (cb.checked && row.style.display !== 'none') {
total += parseFloat(cb.dataset.amount);
}
});
totalEl.textContent = total.toFixed(2) + ' PLN';
totalEl.parentElement.classList.add('animate__animated', 'animate__fadeIn');
setTimeout(() => {
totalEl.parentElement.classList.remove('animate__animated', 'animate__fadeIn');
}, 400);
}
checkboxes.forEach(cb => cb.addEventListener('change', updateTotal));
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const range = btn.dataset.range;
localStorage.removeItem('customStart');
localStorage.removeItem('customEnd');
const now = new Date();
const todayStr = now.toISOString().slice(0, 10);
const year = now.getFullYear();
const month = now.toISOString().slice(0, 7);
const week = `${year}-${String(getISOWeek(now)).padStart(2, '0')}`;
rows.forEach(row => {
const rDate = row.dataset.date;
const rMonth = row.dataset.month;
const rWeek = row.dataset.week;
const rYear = row.dataset.year;
let show = true;
if (range === 'day') show = rDate === todayStr;
if (range === 'month') show = rMonth === month;
if (range === 'week') show = rWeek === week;
if (range === 'year') show = rYear === String(year);
row.style.display = show ? '' : 'none';
});
applyExpenseFilter();
updateTotal();
});
});
function getISOWeek(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const dayDiff = (target - firstThursday) / 86400000;
return 1 + Math.floor(dayDiff / 7);
}
document.getElementById('applyCustomRange').addEventListener('click', () => {
const start = customStart.value;
const end = customEnd.value;
// Zapamiętaj daty
localStorage.setItem('customStart', start);
localStorage.setItem('customEnd', end);
filterButtons.forEach(b => b.classList.remove('active'));
rows.forEach(row => {
const date = row.dataset.date;
const show = (!start || date >= start) && (!end || date <= end);
row.style.display = show ? '' : 'none';
});
applyExpenseFilter();
updateTotal();
});
if (onlyWith) {
onlyWith.addEventListener('change', () => {
applyExpenseFilter();
updateTotal();
});
}
function applyExpenseFilter() {
if (!onlyWith || !onlyWith.checked) return;
rows.forEach(row => {
const amt = parseFloat(row.querySelector('.list-checkbox').dataset.amount || 0);
if (amt <= 0) row.style.display = 'none';
});
}
// Domyślnie kliknij „Miesiąc”
const defaultBtn = document.querySelector('.range-btn[data-range="month"]');
if (defaultBtn && !customStart.value && !customEnd.value) {
defaultBtn.click();
}
});
document.addEventListener("DOMContentLoaded", function () {
const toggleBtn = document.getElementById("toggleAllCheckboxes");
let allChecked = false;
toggleBtn?.addEventListener("click", () => {
const checkboxes = document.querySelectorAll(".list-checkbox");
allChecked = !allChecked;
checkboxes.forEach(cb => {
cb.checked = allChecked;
});
toggleBtn.textContent = allChecked ? "🚫 Odznacz wszystkie" : "✅ Zaznacz wszystkie";
const updateTotalEvent = new Event('change');
checkboxes.forEach(cb => cb.dispatchEvent(updateTotalEvent));
});
});
document.getElementById("applyCustomRange")?.addEventListener("click", () => {
const start = document.getElementById("customStart")?.value;
const end = document.getElementById("customEnd")?.value;
if (start && end) {
const url = `/user_expenses?start_date=${start}&end_date=${end}`;
window.location.href = url;
}
});
document.getElementById("showAllLists").addEventListener("change", function () {
const checked = this.checked;
const url = new URL(window.location.href);
if (checked) {
url.searchParams.set("show_all", "true");
} else {
url.searchParams.delete("show_all");
}
window.location.href = url.toString();
});