nowe funkcje i foxy

This commit is contained in:
Mateusz Gruszczyński
2025-07-25 18:27:58 +02:00
parent bb667a2cbd
commit e4322f2bc6
10 changed files with 267 additions and 87 deletions

View File

@@ -205,7 +205,6 @@ input.form-control {
box-shadow: 0 -1px 4px rgba(0, 0, 0, 0.25);
}
@media (max-width: 768px) {
.info-bar-fixed {
position: static;
@@ -310,4 +309,12 @@ input.form-control {
.only-mobile {
display: none !important;
}
}
.sorting-active {
border: 2px dashed #ffc107;
border-radius: 0.5rem;
background-color: rgba(255, 193, 7, 0.05);
padding: 0.5rem;
transition: border 0.3s, background-color 0.3s;
}

View File

@@ -272,7 +272,6 @@ function isListDifferent(oldItems, newItems) {
return false;
}
function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
const li = document.createElement('li');
li.id = `item-${item.id}`;
@@ -297,9 +296,11 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
let reasonHTML = item.not_purchased_reason
? `<small class="text-dark ms-4">[ <b>Powód: ${item.not_purchased_reason}</b> ]</small>` : '';
let dragHandle = window.isSorting ? `<span class="drag-handle me-2 text-danger" style="cursor: grab;">☰</span>` : '';
let left = `
<div class="d-flex align-items-center gap-2 flex-grow-1">
${window.isSorting ? `<span class="drag-handle me-2 text-danger" style="cursor: grab;">☰</span>` : ''}
${dragHandle}
${checkboxOrIcon}
<span id="name-${item.id}" class="text-white">${item.name} ${quantityBadge}</span>
${noteHTML}
@@ -361,8 +362,6 @@ function renderItem(item, isShare = window.IS_SHARE, showEditOnly = false) {
return li;
}
function updateListSmoothly(newItems) {
const itemsContainer = document.getElementById('items');
const existingItemsMap = new Map();

View File

@@ -4,22 +4,22 @@ let currentReceiptId;
document.addEventListener("DOMContentLoaded", function () {
const cropModal = document.getElementById("cropModal");
const cropImage = document.getElementById("cropImage");
const spinner = document.getElementById("cropLoading");
cropModal.addEventListener("shown.bs.modal", function (event) {
const button = event.relatedTarget;
const imgSrc = button.getAttribute("data-img-src");
currentReceiptId = button.getAttribute("data-receipt-id");
const image = document.getElementById("cropImage");
image.src = imgSrc;
cropImage.src = imgSrc;
if (cropper) {
cropper.destroy();
cropper = null;
}
image.onload = () => {
cropper = new Cropper(image, {
cropImage.onload = () => {
cropper = new Cropper(cropImage, {
viewMode: 1,
autoCropArea: 1,
responsive: true,
@@ -36,7 +36,51 @@ document.addEventListener("DOMContentLoaded", function () {
document.getElementById("saveCrop").addEventListener("click", function () {
if (!cropper) return;
cropper.getCroppedCanvas().toBlob(function (blob) {
spinner.classList.remove("d-none");
const cropData = cropper.getData();
const imageData = cropper.getImageData();
const scaleX = imageData.naturalWidth / imageData.width;
const scaleY = imageData.naturalHeight / imageData.height;
const width = cropData.width * scaleX;
const height = cropData.height * scaleY;
if (width < 1 || height < 1) {
spinner.classList.add("d-none");
showToast("Obszar przycięcia jest zbyt mały lub pusty", "danger");
return;
}
// Ogranicz do 2000x2000 w proporcji
const maxDim = 2000;
const scale = Math.min(1, maxDim / Math.max(width, height));
const finalWidth = Math.round(width * scale);
const finalHeight = Math.round(height * scale);
const croppedCanvas = cropper.getCroppedCanvas({
width: finalWidth,
height: finalHeight,
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
});
if (!croppedCanvas) {
spinner.classList.add("d-none");
showToast("Nie można uzyskać obrazu przycięcia", "danger");
return;
}
croppedCanvas.toBlob(function (blob) {
if (!blob) {
spinner.classList.add("d-none");
showToast("Nie udało się zapisać obrazu", "danger");
return;
}
const formData = new FormData();
formData.append("receipt_id", currentReceiptId);
formData.append("cropped_image", blob);
@@ -47,6 +91,7 @@ document.addEventListener("DOMContentLoaded", function () {
})
.then((res) => res.json())
.then((data) => {
spinner.classList.add("d-none");
if (data.success) {
showToast("Zapisano przycięty paragon", "success");
setTimeout(() => location.reload(), 1500);
@@ -55,9 +100,10 @@ document.addEventListener("DOMContentLoaded", function () {
}
})
.catch((err) => {
spinner.classList.add("d-none");
showToast("Błąd sieci", "danger");
console.error(err);
});
}, "image/webp");
}, "image/webp", 1.0);
});
});

14
static/js/select_month.js Normal file
View File

@@ -0,0 +1,14 @@
document.addEventListener("DOMContentLoaded", () => {
const select = document.getElementById("monthSelect");
if (!select) return;
select.addEventListener("change", () => {
const month = select.value;
const url = new URL(window.location.href);
if (month) {
url.searchParams.set("month", month);
} else {
url.searchParams.delete("month");
}
window.location.href = url.toString();
});
});

View File

@@ -2,53 +2,54 @@ let sortable = null;
let isSorting = false;
function enableSortMode() {
if (sortable || isSorting) return;
if (isSorting) return;
isSorting = true;
window.isSorting = true;
localStorage.setItem('sortModeEnabled', 'true');
const itemsContainer = document.getElementById('items');
const listId = window.LIST_ID;
if (!itemsContainer || !listId) return;
sortable = Sortable.create(itemsContainer, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'drag-ghost',
filter: 'input, button',
preventOnFilter: false,
onEnd: function () {
const order = Array.from(itemsContainer.children)
.map(li => parseInt(li.id.replace('item-', '')))
.filter(id => !isNaN(id));
fetch('/reorder_items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ list_id: listId, order })
}).then(() => {
showToast('Zapisano nową kolejność', 'success');
if (window.currentItems) {
window.currentItems = order.map(id =>
window.currentItems.find(item => item.id === id)
);
updateListSmoothly(window.currentItems);
}
});
}
});
const btn = document.getElementById('sort-toggle-btn');
if (btn) {
btn.textContent = '✔️ Zakończ sortowanie';
btn.classList.remove('btn-outline-warning');
btn.classList.add('btn-outline-success');
}
// Odśwież widok listy z uchwytami (☰)
if (window.currentItems) {
updateListSmoothly(window.currentItems);
}
// Poczekaj na DOM po odświeżeniu listy
setTimeout(() => {
if (sortable) sortable.destroy();
sortable = Sortable.create(itemsContainer, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'drag-ghost',
filter: 'input, button',
preventOnFilter: false,
onEnd: () => {
const order = Array.from(itemsContainer.children)
.map(li => parseInt(li.id.replace('item-', '')))
.filter(id => !isNaN(id));
fetch('/reorder_items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ list_id: listId, order })
}).then(() => {
showToast('Zapisano nową kolejność', 'success');
if (window.currentItems) {
window.currentItems = order.map(id =>
window.currentItems.find(item => item.id === id)
);
updateListSmoothly(window.currentItems);
}
});
}
});
updateSortButtonUI(true);
}, 50);
}
function disableSortMode() {
@@ -56,28 +57,40 @@ function disableSortMode() {
sortable.destroy();
sortable = null;
}
isSorting = false;
localStorage.removeItem('sortModeEnabled');
const btn = document.getElementById('sort-toggle-btn');
if (btn) {
btn.textContent = '✳️ Zmień kolejność';
btn.classList.remove('btn-outline-success');
btn.classList.add('btn-outline-warning');
}
window.isSorting = false;
if (window.currentItems) {
updateListSmoothly(window.currentItems);
}
updateSortButtonUI(false);
}
function toggleSortMode() {
isSorting ? disableSortMode() : enableSortMode();
}
function updateSortButtonUI(active) {
const btn = document.getElementById('sort-toggle-btn');
if (!btn) return;
if (active) {
btn.textContent = '✔️ Zakończ sortowanie';
btn.classList.remove('btn-outline-warning');
btn.classList.add('btn-outline-success');
} else {
btn.textContent = '✳️ Zmień kolejność';
btn.classList.remove('btn-outline-success');
btn.classList.add('btn-outline-warning');
}
}
document.addEventListener('DOMContentLoaded', () => {
const wasSorting = localStorage.getItem('sortModeEnabled') === 'true';
if (wasSorting) {
enableSortMode();
}
});
});

View File

@@ -3,7 +3,11 @@ document.addEventListener("DOMContentLoaded", function () {
const rangeLabel = document.getElementById("chartRangeLabel");
function loadExpenses(range = "monthly", startDate = null, endDate = null) {
let url = '/user/expenses_data?range=' + range;
let url = '/user_expenses_data?range=' + range;
const showAllCheckbox = document.getElementById("showAllLists");
if (showAllCheckbox && showAllCheckbox.checked) {
url += '&show_all=true';
}
if (startDate && endDate) {
url += `&start_date=${startDate}&end_date=${endDate}`;
}