zmiany w js
This commit is contained in:
151
static/js/chart_controls.js
Normal file
151
static/js/chart_controls.js
Normal file
@@ -0,0 +1,151 @@
|
||||
// chart_controls.js
|
||||
// Logika UI: wybór zakresu, przełączanie dzienny/miesięczny, kategorie, show_all.
|
||||
// Współpracuje z window.loadExpenses (z expense_chart.js).
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const toggleMonthlySplit = document.getElementById("toggleMonthlySplit");
|
||||
const toggleDailySplit = document.getElementById("toggleDailySplit");
|
||||
const toggleCategory = document.getElementById("toggleCategorySplit");
|
||||
const startDateInput = document.getElementById("startDate");
|
||||
const endDateInput = document.getElementById("endDate");
|
||||
const customRangeBtn = document.getElementById("customRangeBtn");
|
||||
const showAllCheckbox = document.getElementById("showAllLists");
|
||||
|
||||
// pomocnicze
|
||||
const iso = (d) => d.toISOString().split("T")[0];
|
||||
const today = () => new Date();
|
||||
const daysAgo = (n) => { const d = new Date(); d.setDate(d.getDate() - n); return d; };
|
||||
|
||||
function setActiveTimeSplit(active) {
|
||||
const on = (btn) => { btn.classList.add("btn-primary"); btn.classList.remove("btn-outline-light"); btn.setAttribute("aria-pressed", "true"); };
|
||||
const off = (btn) => { btn.classList.remove("btn-primary"); btn.classList.add("btn-outline-light"); btn.setAttribute("aria-pressed", "false"); };
|
||||
if (active === "monthly") { on(toggleMonthlySplit); off(toggleDailySplit); }
|
||||
else { on(toggleDailySplit); off(toggleMonthlySplit); }
|
||||
}
|
||||
function isDailyActive() { return toggleDailySplit?.classList.contains("btn-primary"); }
|
||||
|
||||
// ——— KLUCZOWE: jedno miejsce, które przeładowuje wykres zgodnie z aktualnym trybem ———
|
||||
function reloadRespectingSplit(preferredRange = null) {
|
||||
// preferredRange używamy dla przycisków typu monthly/quarterly/halfyearly/yearly
|
||||
const sd = startDateInput?.value || null;
|
||||
const ed = endDateInput?.value || null;
|
||||
|
||||
if (isDailyActive()) {
|
||||
// Dzienny ZAWSZE z datami (fallback: ostatnie 30 dni), bo inaczej backend spadnie na monthly
|
||||
const _sd = sd && ed ? sd : iso(daysAgo(30));
|
||||
const _ed = sd && ed ? ed : iso(today());
|
||||
window.loadExpenses("daily", _sd, _ed);
|
||||
return;
|
||||
}
|
||||
|
||||
// Miesięczny
|
||||
if (sd && ed) {
|
||||
window.loadExpenses("monthly", sd, ed);
|
||||
} else if (preferredRange) {
|
||||
window.loadExpenses(preferredRange);
|
||||
} else {
|
||||
window.loadExpenses("monthly");
|
||||
}
|
||||
}
|
||||
|
||||
// ——— Przełączniki czasu ———
|
||||
toggleMonthlySplit?.addEventListener("click", () => {
|
||||
setActiveTimeSplit("monthly");
|
||||
reloadRespectingSplit("monthly");
|
||||
});
|
||||
|
||||
toggleDailySplit?.addEventListener("click", () => {
|
||||
setActiveTimeSplit("daily");
|
||||
reloadRespectingSplit();
|
||||
});
|
||||
|
||||
// ——— Podział na kategorie ———
|
||||
toggleCategory?.addEventListener("click", function () {
|
||||
const active = this.classList.contains("btn-primary");
|
||||
if (active) {
|
||||
this.classList.remove("btn-primary");
|
||||
this.classList.add("btn-outline-light");
|
||||
this.setAttribute("aria-pressed", "false");
|
||||
this.textContent = "Pokaż podział na kategorie";
|
||||
window.setCategorySplit(false);
|
||||
} else {
|
||||
this.classList.add("btn-primary");
|
||||
this.classList.remove("btn-outline-light");
|
||||
this.setAttribute("aria-pressed", "true");
|
||||
this.textContent = "Pokaż całościowo";
|
||||
window.setCategorySplit(true);
|
||||
}
|
||||
reloadRespectingSplit();
|
||||
});
|
||||
|
||||
// ——— Własny zakres ———
|
||||
customRangeBtn?.addEventListener("click", function () {
|
||||
const sd = startDateInput?.value;
|
||||
const ed = endDateInput?.value;
|
||||
if (!(sd && ed)) return alert("Proszę wybrać obie daty!");
|
||||
reloadRespectingSplit();
|
||||
});
|
||||
|
||||
// ——— Predefiniowane zakresy pod wykresem ———
|
||||
document.querySelectorAll("#chartTab .range-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", function () {
|
||||
document.querySelectorAll("#chartTab .range-btn").forEach((b) => b.classList.remove("active"));
|
||||
this.classList.add("active");
|
||||
const r = this.getAttribute("data-range"); // last30days/currentmonth/monthly/quarterly/halfyearly/yearly
|
||||
|
||||
if (r === "currentmonth") {
|
||||
const t = today();
|
||||
const first = new Date(t.getFullYear(), t.getMonth(), 1);
|
||||
if (isDailyActive()) {
|
||||
window.loadExpenses("daily", iso(first), iso(t));
|
||||
} else {
|
||||
window.loadExpenses("monthly", iso(first), iso(t));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (r === "last30days") {
|
||||
if (isDailyActive()) {
|
||||
window.loadExpenses("daily", iso(daysAgo(30)), iso(today()));
|
||||
} else {
|
||||
window.loadExpenses("last30days");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// monthly/quarterly/halfyearly/yearly – kubełkowanie po stronie backendu
|
||||
reloadRespectingSplit(r);
|
||||
});
|
||||
});
|
||||
|
||||
// ——— KATEGORIE (🌐 Wszystkie + pojedyncze) ———
|
||||
document.querySelectorAll(".category-filter").forEach((btn) => {
|
||||
btn.addEventListener("click", function () {
|
||||
// UI: podmień podświetlenie
|
||||
document.querySelectorAll(".category-filter").forEach(b => {
|
||||
b.classList.remove("btn-success");
|
||||
b.classList.add("btn-outline-light");
|
||||
});
|
||||
this.classList.add("btn-success");
|
||||
this.classList.remove("btn-outline-light");
|
||||
|
||||
// Zapisz filtr kategorii do globalnej zmiennej, którą odczytuje expense_chart.js
|
||||
const cid = this.getAttribute("data-category-id") || "";
|
||||
window.selectedCategoryId = cid;
|
||||
|
||||
// I ważne: przeładuj zgodnie z aktualnym trybem (to naprawia Twój przypadek #1)
|
||||
reloadRespectingSplit();
|
||||
});
|
||||
});
|
||||
|
||||
// ——— SHOW ALL (Uwzględnij listy udostępnione/publiczne) ———
|
||||
showAllCheckbox?.addEventListener("change", () => {
|
||||
reloadRespectingSplit();
|
||||
});
|
||||
|
||||
// ——— Inicjalizacja ———
|
||||
// Podpowiedź dat do inputów
|
||||
if (startDateInput && endDateInput) {
|
||||
startDateInput.value = iso(daysAgo(7));
|
||||
endDateInput.value = iso(today());
|
||||
}
|
||||
setActiveTimeSplit("daily");
|
||||
reloadRespectingSplit();
|
||||
});
|
@@ -1,214 +1,143 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// expense_chart.js
|
||||
// Czyste generowanie wykresu + publiczne API: window.loadExpenses, window.setCategorySplit
|
||||
// Współpracuje z backendem /expenses_data (range_type, start/end, by_category) – patrz app.py :contentReference[oaicite:3]{index=3}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
let expensesChart = null;
|
||||
let categorySplit = true;
|
||||
let categorySplit = false; // domyślnie wykres całościowy; przycisk w HTML startuje z aria-pressed="false"
|
||||
|
||||
const rangeLabel = document.getElementById("chartRangeLabel");
|
||||
const showAllCheckbox = document.getElementById("showAllLists");
|
||||
const ctx = document.getElementById("expensesChart")?.getContext("2d");
|
||||
|
||||
// Pomocnicze
|
||||
const iso = (d) => d.toISOString().split("T")[0];
|
||||
const today = () => new Date();
|
||||
const daysAgo = (n) => { const d = new Date(); d.setDate(d.getDate() - n); return d; };
|
||||
|
||||
// Jeśli ktoś nie wstrzyknął globalnie selectedCategoryId (np. przez inny widok),
|
||||
// zapewniamy istnienie zmiennej:
|
||||
if (typeof window.selectedCategoryId === "undefined") {
|
||||
window.selectedCategoryId = "";
|
||||
}
|
||||
|
||||
function loadExpenses(range = "currentmonth", startDate = null, endDate = null) {
|
||||
let url = '/expenses_data?range=' + range;
|
||||
// Ustawia tryb podziału na kategorie, bez odświeżania (kontroler zadzwoni potem w loadExpenses)
|
||||
function setCategorySplit(on) {
|
||||
categorySplit = !!on;
|
||||
}
|
||||
|
||||
const showAllCheckbox = document.getElementById("showAllLists");
|
||||
// Budowa URL dla /expenses_data zgodnie z backendem (range/start/end/show_all/category_id/by_category) :contentReference[oaicite:4]{index=4}
|
||||
function buildUrl(range, startDate, endDate) {
|
||||
let url = `/expenses_data?range=${encodeURIComponent(range)}`;
|
||||
|
||||
// show_all
|
||||
if (showAllCheckbox) {
|
||||
url += showAllCheckbox.checked ? '&show_all=true' : '&show_all=false';
|
||||
url += showAllCheckbox.checked ? "&show_all=true" : "&show_all=false";
|
||||
} else {
|
||||
url += '&show_all=true';
|
||||
url += "&show_all=true";
|
||||
}
|
||||
|
||||
// daty (dodaj tylko, gdy kompletne)
|
||||
if (startDate && endDate) {
|
||||
url += `&start_date=${startDate}&end_date=${endDate}`;
|
||||
url += `&start_date=${encodeURIComponent(startDate)}&end_date=${encodeURIComponent(endDate)}`;
|
||||
}
|
||||
|
||||
// filtr kategorii list (z listy, nie "podziału na kategorie" na wykresie)
|
||||
if (window.selectedCategoryId) {
|
||||
url += `&category_id=${window.selectedCategoryId}`;
|
||||
url += `&category_id=${encodeURIComponent(window.selectedCategoryId)}`;
|
||||
}
|
||||
|
||||
// podział na kategorie na wykresie
|
||||
if (categorySplit) {
|
||||
url += '&by_category=true';
|
||||
url += "&by_category=true";
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// Label dla UI
|
||||
function applyRangeLabel(range, startDate, endDate) {
|
||||
if (startDate && endDate) {
|
||||
rangeLabel.textContent = `Widok: własny zakres (${startDate} → ${endDate})`;
|
||||
return;
|
||||
}
|
||||
const map = {
|
||||
last30days: "Widok: ostatnie 30 dni",
|
||||
currentmonth: "Widok: bieżący miesiąc",
|
||||
monthly: "Widok: miesięczne",
|
||||
quarterly: "Widok: kwartalne",
|
||||
halfyearly: "Widok: półroczne",
|
||||
yearly: "Widok: roczne",
|
||||
daily: "Widok: dzienne",
|
||||
};
|
||||
rangeLabel.textContent = map[range] || "Widok: miesięczne";
|
||||
}
|
||||
|
||||
// Publiczne API – kontroler zawsze woła nas z odpowiednim 'range' i (dla daily) z datami.
|
||||
// Dla odporności: jeśli przyjdzie 'daily' BEZ dat, wymusimy ostatnie 30 dni (to była usterka źródłowa) :contentReference[oaicite:5]{index=5}
|
||||
function loadExpenses(range = "monthly", startDate = null, endDate = null) {
|
||||
// Naprawa: daily bez dat => ostatnie 30 dni
|
||||
if (range === "daily" && !(startDate && endDate)) {
|
||||
startDate = iso(daysAgo(30));
|
||||
endDate = iso(today());
|
||||
}
|
||||
|
||||
const url = buildUrl(range, startDate, endDate);
|
||||
|
||||
fetch(url, { cache: "no-store" })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const ctx = document.getElementById('expensesChart').getContext('2d');
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
if (!ctx) return;
|
||||
|
||||
if (expensesChart) {
|
||||
expensesChart.destroy();
|
||||
}
|
||||
if (expensesChart) expensesChart.destroy();
|
||||
|
||||
const tooltipOptions = {
|
||||
mode: 'index',
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
if (context.parsed.y === 0) {
|
||||
return ''; // pomija kategorie o wartości 0
|
||||
}
|
||||
return context.dataset.label + ': ' + context.parsed.y;
|
||||
}
|
||||
}
|
||||
if (context.parsed.y === 0) return "";
|
||||
return (context.dataset.label || "Suma") + ": " + context.parsed.y;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (categorySplit) {
|
||||
// Stacked per-kategoria – backend zwraca datasets z labelami kategorii :contentReference[oaicite:6]{index=6}
|
||||
expensesChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: { labels: data.labels, datasets: data.datasets },
|
||||
type: "bar",
|
||||
data: { labels: data.labels || [], datasets: data.datasets || [] },
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
tooltip: tooltipOptions,
|
||||
legend: { position: 'top' }
|
||||
},
|
||||
scales: {
|
||||
x: { stacked: true },
|
||||
y: { stacked: true, beginAtZero: true }
|
||||
}
|
||||
}
|
||||
plugins: { tooltip: tooltipOptions, legend: { position: "top" } },
|
||||
scales: { x: { stacked: true }, y: { stacked: true, beginAtZero: true } },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Całościowo – backend zwraca labels + expenses (sumy) :contentReference[oaicite:7]{index=7}
|
||||
expensesChart = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: data.labels,
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Suma wydatków [PLN]',
|
||||
data: data.expenses,
|
||||
backgroundColor: '#0d6efd'
|
||||
}]
|
||||
label: "Suma wydatków [PLN]",
|
||||
data: data.expenses || [],
|
||||
}],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
tooltip: tooltipOptions
|
||||
},
|
||||
scales: { y: { beginAtZero: true } }
|
||||
}
|
||||
plugins: { tooltip: tooltipOptions },
|
||||
scales: { y: { beginAtZero: true } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (startDate && endDate) {
|
||||
rangeLabel.textContent = `Widok: własny zakres (${startDate} → ${endDate})`;
|
||||
} else {
|
||||
let labelText = "";
|
||||
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;
|
||||
}
|
||||
applyRangeLabel(range, startDate, endDate);
|
||||
})
|
||||
.catch(error => console.error("Błąd pobierania danych:", error));
|
||||
.catch((e) => console.error("Błąd pobierania danych:", e));
|
||||
}
|
||||
|
||||
// Udostępnienie globalne, żeby inne skrypty mogły wywołać reload
|
||||
// Eksport publiczny dla kontrolerów
|
||||
window.loadExpenses = loadExpenses;
|
||||
|
||||
const toggleBtn = document.getElementById("toggleCategorySplit");
|
||||
|
||||
toggleBtn.addEventListener("click", function () {
|
||||
categorySplit = !categorySplit;
|
||||
|
||||
const startDateInput = document.getElementById("startDate");
|
||||
const endDateInput = document.getElementById("endDate");
|
||||
const startDate = startDateInput ? startDateInput.value : null;
|
||||
const endDate = endDateInput ? endDateInput.value : null;
|
||||
|
||||
// Wybierz zakres - przy daily ustawiamy range = 'custom', by respektować daty
|
||||
let activeRange = 'currentmonth'; // default
|
||||
|
||||
if (document.getElementById('toggleDailySplit').classList.contains('btn-primary')) {
|
||||
activeRange = (startDate && endDate) ? 'custom' : 'daily';
|
||||
} else if (document.getElementById('toggleMonthlySplit').classList.contains('btn-primary')) {
|
||||
activeRange = 'monthly';
|
||||
}
|
||||
|
||||
if (categorySplit) {
|
||||
this.textContent = "🔵 Pokaż całościowo";
|
||||
this.classList.remove("btn-outline-warning");
|
||||
this.classList.add("btn-outline-info");
|
||||
} else {
|
||||
this.textContent = "🎨 Pokaż podział na kategorie";
|
||||
this.classList.remove("btn-outline-info");
|
||||
this.classList.add("btn-outline-warning");
|
||||
}
|
||||
|
||||
if (startDate && endDate) {
|
||||
loadExpenses(activeRange, startDate, endDate);
|
||||
} else {
|
||||
loadExpenses(activeRange);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
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");
|
||||
|
||||
const today = new Date();
|
||||
const lastWeek = new Date(today);
|
||||
lastWeek.setDate(today.getDate() - 7);
|
||||
const formatDate = (d) => d.toISOString().split('T')[0];
|
||||
|
||||
startDateInput.value = formatDate(lastWeek);
|
||||
endDateInput.value = formatDate(today);
|
||||
|
||||
document.getElementById('customRangeBtn').addEventListener('click', function () {
|
||||
const startDate = startDateInput.value;
|
||||
const endDate = endDateInput.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.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');
|
||||
if (range === "currentmonth") {
|
||||
const today = new Date();
|
||||
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
loadExpenses('custom', formatDate(firstDay), formatDate(today));
|
||||
} else {
|
||||
loadExpenses(range);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Automatyczne ładowanie danych po przełączeniu na zakładkę Wykres
|
||||
document.getElementById('chart-tab').addEventListener('shown.bs.tab', function () {
|
||||
// Sprawdź jaki tryb jest aktywny (domyślnie dzienny lub miesięczny)
|
||||
if (document.getElementById('toggleDailySplit').classList.contains('btn-primary')) {
|
||||
const startDate = startDateInput.value || null;
|
||||
const endDate = endDateInput.value || null;
|
||||
|
||||
if (startDate && endDate) {
|
||||
loadExpenses('daily', startDate, endDate);
|
||||
} else {
|
||||
loadExpenses('daily');
|
||||
}
|
||||
} else if (document.getElementById('toggleMonthlySplit').classList.contains('btn-primary')) {
|
||||
loadExpenses('monthly');
|
||||
} else {
|
||||
loadExpenses('currentmonth'); // Albo inna domyślna wartość
|
||||
}
|
||||
});
|
||||
|
||||
// Jeśli jesteśmy od razu na zakładce Wykres
|
||||
//if (document.getElementById('chart-tab').classList.contains('active')) {
|
||||
// loadExpenses("currentmonth");
|
||||
//}
|
||||
window.setCategorySplit = setCategorySplit;
|
||||
});
|
||||
|
@@ -133,19 +133,22 @@
|
||||
<div class="card bg-dark text-white mb-4">
|
||||
<div class="card-body">
|
||||
|
||||
<div class="d-flex gap-3 mb-3">
|
||||
<div>
|
||||
<h6 class="text-white">Podział według czasu</h6>
|
||||
<div class="btn-group" role="group" aria-label="Podział czasu">
|
||||
<button type="button" class="btn btn-outline-light" id="toggleMonthlySplit" aria-pressed="true">Pokaż podział miesięczny</button>
|
||||
<button type="button" class="btn btn-outline-light" id="toggleDailySplit" aria-pressed="false">Pokaż podział dzienny</button>
|
||||
<div class="d-flex gap-3 mb-3">
|
||||
<div>
|
||||
<h6 class="text-white">Podział według czasu</h6>
|
||||
<div class="btn-group" role="group" aria-label="Podział czasu">
|
||||
<button type="button" class="btn btn-outline-light" id="toggleMonthlySplit" aria-pressed="true">Pokaż
|
||||
podział miesięczny</button>
|
||||
<button type="button" class="btn btn-outline-light" id="toggleDailySplit" aria-pressed="false">Pokaż
|
||||
podział dzienny</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-white">Podział według kategorii</h6>
|
||||
<button class="btn btn-outline-light" id="toggleCategorySplit" aria-pressed="false">Pokaż podział na
|
||||
kategorie</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-white">Podział według kategorii</h6>
|
||||
<button class="btn btn-outline-light" id="toggleCategorySplit" aria-pressed="false">Pokaż podział na kategorie</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p id="chartRangeLabel" class="fw-bold mb-3">Widok: miesięczne</p>
|
||||
@@ -188,5 +191,5 @@
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expense_table.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='expense_tab.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select_all_table.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='chart_split_controls.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='chart_controls.js') }}"></script>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user