3 Commits

Author SHA1 Message Date
Mateusz Gruszczyński 6a89f20384 visual fixes 2026-05-26 10:40:23 +02:00
Mateusz Gruszczyński 172b46ad07 zmiany dla michała 2026-04-10 12:00:46 +02:00
Mateusz Gruszczyński 4c9d665ae2 zmiana waluty w .env 2026-04-02 08:25:07 +02:00
25 changed files with 393 additions and 52 deletions
+7 -1
View File
@@ -195,4 +195,10 @@ UPLOADS_CACHE_CONTROL="max-age=3600, immutable"
# UWAGA: wielkość liter w nazwach jest zachowywana, ale porównywanie odbywa się
# bez rozróżniania wielkości liter (case-insensitive).
# Domyślnie: poniższa lista
DEFAULT_CATEGORIES="Spożywcze,Budowlane,Zabawki,Chemia,Inne,Elektronika,Odzież i obuwie,Artykuły biurowe,Kosmetyki i higiena,Motoryzacja,Ogród i rośliny,Zwierzęta,Sprzęt sportowy,Książki i prasa,Narzędzia i majsterkowanie,RTV / AGD,Apteka i suplementy,Artykuły dekoracyjne,Gry i hobby,Usługi,Pieczywo"
DEFAULT_CATEGORIES="Spożywcze,Budowlane,Zabawki,Chemia,Inne,Elektronika,Odzież i obuwie,Artykuły biurowe,Kosmetyki i higiena,Motoryzacja,Ogród i rośliny,Zwierzęta,Sprzęt sportowy,Książki i prasa,Narzędzia i majsterkowanie,RTV / AGD,Apteka i suplementy,Artykuły dekoracyjne,Gry i hobby,Usługi,Pieczywo"
# Waluta używana w całej aplikacji (kwoty, paragony, analizy)
# Użyj kodu ISO 4217 (np. PLN, EUR, USD, GBP)
# Domyślnie: PLN (jeśli zmienna nie jest ustawiona)
CURRENCY_CODE=PLN
+2 -1
View File
@@ -10,4 +10,5 @@ db/pgsql/*
db/shopping.db
*.swp
version.txt
deploy/varnish/default.vcl
deploy/varnish/default.vcl
*.zip
+2
View File
@@ -91,6 +91,8 @@ class Config:
DEBUG_MODE = env_bool("DEBUG_MODE", True)
DISABLE_ROBOTS = env_bool("DISABLE_ROBOTS", False)
CURRENCY_CODE = env_str("CURRENCY_CODE", "PLN").strip().upper() or "PLN"
JS_CACHE_CONTROL = env_str("JS_CACHE_CONTROL", "no-cache")
CSS_CACHE_CONTROL = env_str("CSS_CACHE_CONTROL", "no-cache")
LIB_JS_CACHE_CONTROL = env_str("LIB_JS_CACHE_CONTROL", "max-age=604800")
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import os
import sys
import zipfile
import subprocess
from pathlib import Path
def run_git_command(args, repo_path: Path) -> bytes:
result = subprocess.run(
["git", *args],
cwd=repo_path,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
return result.stdout
def get_files_to_archive(repo_path: Path) -> list[str]:
output = run_git_command(
["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
repo_path,
)
files = output.decode("utf-8", errors="surrogateescape").split("\0")
return [f for f in files if f]
def make_zip(repo_path: Path, output_zip: Path) -> None:
files = get_files_to_archive(repo_path)
output_zip = output_zip.resolve()
if output_zip.exists():
output_zip.unlink()
with zipfile.ZipFile(output_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for rel_path in files:
abs_path = repo_path / rel_path
if not abs_path.exists():
continue
if abs_path.resolve() == output_zip:
continue
zf.write(abs_path, arcname=rel_path)
print(f"Utworzono archiwum: {output_zip}")
print(f"Added files: {len(files)}")
def main():
repo_path = Path.cwd()
if len(sys.argv) > 1:
output_zip = Path(sys.argv[1])
else:
output_zip = repo_path / f"{repo_path.name}.zip"
try:
run_git_command(["rev-parse", "--show-toplevel"], repo_path)
except subprocess.CalledProcessError:
print("Error: this directory is not a Git repository.", file=sys.stderr)
sys.exit(1)
make_zip(repo_path, output_zip)
if __name__ == "__main__":
main()
+19
View File
@@ -2,6 +2,25 @@ from .deps import *
from .app_setup import *
from .models import *
def get_currency_code() -> str:
code = str(app.config.get("CURRENCY_CODE", "PLN") or "PLN").strip().upper()
return code or "PLN"
def format_currency(amount, include_code: bool = True) -> str:
try:
normalized = float(amount or 0)
except (TypeError, ValueError):
normalized = 0.0
formatted = f"{normalized:.2f}"
return f"{formatted} {get_currency_code()}" if include_code else formatted
def currency_placeholder(prefix: str = "Kwota") -> str:
return f"{prefix} ({get_currency_code()})"
def get_setting(key: str, default: str | None = None) -> str | None:
s = db.session.get(AppSetting, key)
return s.value if s else default
+1 -1
View File
@@ -453,7 +453,7 @@ def handle_add_expense(data):
)
db.session.add(new_expense)
log_list_activity(list_id, 'expense_added', item_name=None, actor=current_user if current_user.is_authenticated else None, actor_name=current_user.username if current_user.is_authenticated else 'Gość', details=f'kwota: {float(amount):.2f} PLN')
log_list_activity(list_id, 'expense_added', item_name=None, actor=current_user if current_user.is_authenticated else None, actor_name=current_user.username if current_user.is_authenticated else 'Gość', details=f'kwota: {format_currency(amount)}')
db.session.commit()
total = (
+40
View File
@@ -1382,6 +1382,14 @@ input[type="checkbox"].form-check-input,
min-width: 0;
overflow-wrap: break-word;
word-break: normal;
appearance: none;
background: transparent;
border: 0;
padding: 0;
margin: 0;
text-align: left;
font: inherit;
color: inherit;
}
.shopping-item-text .info-line {
@@ -2284,3 +2292,35 @@ body:not(.sorting-active) .drag-handle {
color: rgba(255,255,255,.66);
line-height: 1.35;
}
.endpoint-view_list .shopping-item-name[data-item-menu-trigger],
.endpoint-list .shopping-item-name[data-item-menu-trigger] {
cursor: pointer;
}
.endpoint-view_list .shopping-item-name[data-item-menu-trigger]:hover,
.endpoint-view_list .shopping-item-name[data-item-menu-trigger]:focus-visible,
.endpoint-list .shopping-item-name[data-item-menu-trigger]:hover,
.endpoint-list .shopping-item-name[data-item-menu-trigger]:focus-visible {
text-decoration: none;
}
#desktopItemMenu {
position: fixed;
z-index: 1200;
min-width: 10rem;
display: grid;
gap: .35rem;
padding: .45rem;
border: 1px solid rgba(255, 255, 255, .12);
border-radius: .9rem;
background: rgba(18, 20, 24, .96);
box-shadow: 0 16px 38px rgba(0, 0, 0, .34);
backdrop-filter: blur(10px);
}
#desktopItemMenu[hidden] {
display: none !important;
}
+126
View File
@@ -0,0 +1,126 @@
(function () {
const DESKTOP_QUERY = '(min-width: 992px) and (pointer: fine)';
function isDesktopOwnerList() {
return window.matchMedia(DESKTOP_QUERY).matches
&& !window.IS_SHARE
&& (
document.body.classList.contains('endpoint-view_list')
|| document.body.classList.contains('endpoint-list')
);
}
function getMenu() {
return document.getElementById('desktopItemMenu');
}
function hideDesktopItemMenu() {
const menu = getMenu();
if (!menu) return;
menu.hidden = true;
delete menu.dataset.itemId;
delete menu.dataset.itemName;
delete menu.dataset.itemQuantity;
}
function positionMenu(menu, clickX, clickY) {
const gap = 14;
menu.style.left = '0px';
menu.style.top = '0px';
menu.hidden = false;
const rect = menu.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
let left = clickX - (rect.width / 2);
let top = clickY - rect.height - gap;
if (top < 12) {
top = Math.min(viewportHeight - rect.height - 12, clickY + gap);
}
left = Math.max(12, Math.min(left, viewportWidth - rect.width - 12));
top = Math.max(12, Math.min(top, viewportHeight - rect.height - 12));
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
}
function showDesktopItemMenu(trigger, event) {
const menu = getMenu();
if (!menu) return;
menu.dataset.itemId = trigger.dataset.itemId || '';
menu.dataset.itemName = trigger.dataset.itemName || '';
menu.dataset.itemQuantity = trigger.dataset.itemQuantity || '1';
let clickX = event.clientX || 0;
let clickY = event.clientY || 0;
if (!clickX && !clickY) {
const rect = trigger.getBoundingClientRect();
clickX = rect.left + (rect.width / 2);
clickY = rect.top;
}
positionMenu(menu, clickX, clickY);
}
document.addEventListener('click', function (event) {
const menu = getMenu();
const trigger = event.target.closest('[data-item-menu-trigger="true"]');
if (trigger && isDesktopOwnerList() && !trigger.disabled) {
event.preventDefault();
event.stopPropagation();
showDesktopItemMenu(trigger, event);
return;
}
if (menu && !menu.hidden && !event.target.closest('#desktopItemMenu')) {
hideDesktopItemMenu();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') {
hideDesktopItemMenu();
}
});
['scroll', 'resize'].forEach(function (eventName) {
window.addEventListener(eventName, hideDesktopItemMenu, true);
});
document.addEventListener('DOMContentLoaded', function () {
const menu = getMenu();
if (!menu) return;
menu.addEventListener('click', function (event) {
const actionButton = event.target.closest('[data-menu-action]');
if (!actionButton) return;
const itemId = parseInt(menu.dataset.itemId || '', 10);
const itemName = menu.dataset.itemName || '';
const itemQuantity = parseInt(menu.dataset.itemQuantity || '1', 10) || 1;
if (!itemId) {
hideDesktopItemMenu();
return;
}
if (actionButton.dataset.menuAction === 'edit') {
openEditItemModal(event, itemId, itemName, itemQuantity);
}
if (actionButton.dataset.menuAction === 'delete') {
deleteItem(itemId);
}
hideDesktopItemMenu();
});
});
window.hideDesktopItemMenu = hideDesktopItemMenu;
})();
+1 -1
View File
@@ -123,7 +123,7 @@ document.addEventListener("DOMContentLoaded", function () {
data: {
labels: data.labels || [],
datasets: [{
label: "Suma wydatków [PLN]",
label: `Suma wydatków [${getCurrencyCode()}]`,
data: data.expenses || [],
}],
},
+1 -1
View File
@@ -28,7 +28,7 @@ document.addEventListener('DOMContentLoaded', () => {
total += parseFloat(cb.dataset.amount);
}
});
totalEl.textContent = total.toFixed(2) + ' PLN';
totalEl.textContent = formatCurrencyAmount(total);
}
function getISOWeek(date) {
+29 -3
View File
@@ -1,3 +1,19 @@
function getCurrencyCode() {
return window.CURRENCY_CODE || 'PLN';
}
function formatCurrencyAmount(amount, options = {}) {
const includeCode = options.includeCode !== false;
const numeric = Number(amount || 0);
const safe = Number.isFinite(numeric) ? numeric : 0;
const formatted = safe.toFixed(2);
return includeCode ? `${formatted} ${getCurrencyCode()}` : formatted;
}
function currencyLabel(prefix = 'Kwota') {
return `${prefix} (${getCurrencyCode()})`;
}
function updateItemState(itemId, isChecked) {
const checkbox = document.querySelector(`#item-${itemId} input[type='checkbox']`);
if (checkbox) {
@@ -369,8 +385,9 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
const isOwner = window.IS_OWNER === true || window.IS_OWNER === 'true';
const isArchived = window.IS_ARCHIVED === true || window.IS_ARCHIVED === 'true';
const safeName = escapeHtml(item.name || '');
const nameForEdit = JSON.stringify(String(item.name || ''));
const rawName = String(item.name || '');
const safeName = escapeHtml(rawName);
const nameForEdit = JSON.stringify(rawName);
const quantity = Number.isInteger(item.quantity) ? item.quantity : parseInt(item.quantity, 10) || 1;
const quantityBadge = quantity > 1
? `<span class="badge rounded-pill bg-secondary">x${quantity}</span>`
@@ -401,6 +418,15 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
const iconBtn = 'btn btn-outline-light btn-sm shopping-action-btn';
const wideBtn = 'btn btn-outline-light btn-sm shopping-action-btn shopping-action-btn--wide';
const itemNameHtml = canEditListItem
? `<button type="button"
id="name-${item.id}"
class="shopping-item-name text-white"
data-item-id="${item.id}"
data-item-name=${JSON.stringify(rawName)}
data-item-quantity="${quantity}"
${isArchived ? 'disabled aria-disabled="true"' : 'data-item-menu-trigger="true"'}>${safeName}</button>`
: `<span id="name-${item.id}" class="shopping-item-name text-white">${safeName}</span>`;
let actionButtons = '';
if (canEditListItem) {
@@ -438,7 +464,7 @@ function renderItem(item, isShare = window.IS_SHARE, optionsOrShowEditOnly = fal
<div class="shopping-item-content">
<div class="shopping-item-head">
<div class="shopping-item-text">
<span id="name-${item.id}" class="shopping-item-name text-white">${safeName}</span>
${itemNameHtml}
${quantityBadge}
${infoHtml}
</div>
+37 -12
View File
@@ -113,7 +113,7 @@ function setupList(listId, username) {
socket.on('expense_added', data => {
const badgeEl = document.getElementById('total-expense1');
if (badgeEl) {
badgeEl.innerHTML = `💸 ${data.total.toFixed(2)} PLN`;
badgeEl.innerHTML = `💸 ${formatCurrencyAmount(data.total)}`;
badgeEl.classList.remove('bg-secondary');
badgeEl.classList.add('bg-success');
badgeEl.style.display = '';
@@ -121,10 +121,10 @@ function setupList(listId, username) {
const summaryEl = document.getElementById('total-expense2');
if (summaryEl) {
summaryEl.innerHTML = `<b>💸 Łącznie wydano:</b> ${data.total.toFixed(2)} PLN`;
summaryEl.innerHTML = `<b>💸 Łącznie wydano:</b> ${formatCurrencyAmount(data.total)}`;
}
showToast(`Dodano wydatek: ${data.amount.toFixed(2)} PLN`, 'info');
showToast(`Dodano wydatek: ${formatCurrencyAmount(data.amount)}`, 'info');
});
@@ -139,6 +139,13 @@ function setupList(listId, username) {
note: ''
};
// Note: store newly added items locally so later edits do not depend on a full page refresh.
if (Array.isArray(window.currentItems)) {
window.currentItems.push(item);
} else {
window.currentItems = [item];
}
const isOwnFreshShareItem = Boolean(
window.IS_SHARE &&
data.added_by &&
@@ -216,22 +223,40 @@ function setupList(listId, username) {
});
socket.on('item_edited', data => {
const idx = window.currentItems.findIndex(i => i.id === data.item_id);
if (idx !== -1) {
window.currentItems[idx].name = data.new_name;
window.currentItems[idx].quantity = data.new_quantity;
const itemId = Number(data.item_id);
const oldItem = document.getElementById(`item-${itemId}`);
const currentItems = Array.isArray(window.currentItems) ? window.currentItems : [];
const idx = currentItems.findIndex(item => Number(item.id) === itemId);
const cachedItem = idx !== -1 ? currentItems[idx] : {};
const newItem = renderItem(window.currentItems[idx], window.IS_SHARE);
const oldItem = document.getElementById(`item-${data.item_id}`);
if (oldItem && newItem) {
oldItem.replaceWith(newItem);
}
// Note: keep the edited item visible immediately, even when the local list cache is stale.
const updatedItem = {
...cachedItem,
id: itemId,
name: data.new_name,
quantity: data.new_quantity,
purchased: oldItem ? oldItem.classList.contains('bg-success') : !!cachedItem.purchased,
not_purchased: oldItem ? oldItem.classList.contains('bg-warning') : !!cachedItem.not_purchased,
not_purchased_reason: cachedItem.not_purchased_reason || '',
note: cachedItem.note || ''
};
if (idx !== -1) {
currentItems[idx] = updatedItem;
window.currentItems = currentItems;
}
if (oldItem) {
oldItem.replaceWith(renderItem(updatedItem, window.IS_SHARE));
} else if (window.LIST_ID) {
socket.emit('request_full_list', { list_id: window.LIST_ID });
}
showToast(`Zaktualizowano produkt: ${data.new_name} (x${data.new_quantity})`, 'success');
updateProgressBar();
toggleEmptyPlaceholder();
applyHidePurchased();
});
// --- WAŻNE: zapisz dane do reconnect ---
+1 -1
View File
@@ -99,7 +99,7 @@ document.addEventListener("DOMContentLoaded", function () {
summary.innerHTML = `
<p class="mb-1">📦 <strong>${totalCount}</strong> produktów</p>
<p class="mb-1"> Kupione: <strong>${purchasedCount}</strong> (${percent}%)</p>
<p class="mb-0">💸 Wydatek: <strong>${totalExpense.toFixed(2)} </strong></p>`;
<p class="mb-0">💸 Wydatek: <strong>${formatCurrencyAmount(totalExpense)}</strong></p>`;
productList.appendChild(summary);
const purchased = createSection("✔️ Kupione");
+1 -1
View File
@@ -22,7 +22,7 @@ async function analyzeReceipts(listId) {
let html = `<div class="card bg-dark text-white border-secondary p-3">`;
html += `<p class="text-secondary"><small>⏱ Czas analizy OCR: ${duration} sek.</small></p>`;
html += `<p><b>📊 Łącznie wykryto:</b> ${data.total.toFixed(2)} PLN</p>`;
html += `<p><b>📊 Łącznie wykryto:</b> ${formatCurrencyAmount(data.total)}</p>`;
data.results.forEach((r, i) => {
const disabled = r.already_added ? "disabled" : "";
+9 -2
View File
@@ -1,9 +1,16 @@
document.addEventListener("DOMContentLoaded", function () {
new TomSelect("#categories", {
const categoriesSelect = document.querySelector("#categories");
if (!categoriesSelect || typeof TomSelect === 'undefined') {
return;
}
new TomSelect(categoriesSelect, {
plugins: ['remove_button'],
maxItems: 1,
placeholder: 'Wybierz jedną kategorie...',
placeholder: 'Wybierz jedną kategorię...',
create: false,
dropdownParent: 'body',
sortField: {
field: "text",
direction: "asc"
+1 -1
View File
@@ -12,7 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
total += parseFloat(cb.dataset.amount);
}
});
totalEl.textContent = total.toFixed(2) + ' PLN';
totalEl.textContent = formatCurrencyAmount(total);
}
selectAllBtn.addEventListener('click', () => {
+1 -1
View File
@@ -27,7 +27,7 @@
<span class="progress-label main-list-progress__label small fw-bold {% if percent < 51 %}text-white{% else %}text-dark{% endif %}">
Produkty: {{ purchased_count }}/{{ total_count }} ({{ percent|round(0)|int }}%)
{% if total_expense > 0 %} — 💸 {{ '%.2f'|format(total_expense) }} PLN{% endif %}
{% if total_expense > 0 %} — 💸 {{ format_currency(total_expense) }}{% endif %}
</span>
</div>
</div>
+14 -14
View File
@@ -51,7 +51,7 @@
</tr>
<tr>
<td>💸 Średnia kwota na listę</td>
<td class="text-end fw-bold">{{ avg_list_expense }}</td>
<td class="text-end fw-bold">{{ format_currency(avg_list_expense) }}</td>
</tr>
</tbody>
</table>
@@ -115,30 +115,30 @@
<tbody>
<tr>
<td>Wszystkie</td>
<td>{{ '%.2f'|format(expense_summary.all.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.all.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.all.total) }} PLN</td>
<td>{{ format_currency(expense_summary.all.month) }}</td>
<td>{{ format_currency(expense_summary.all.year) }}</td>
<td>{{ format_currency(expense_summary.all.total) }}</td>
<!-- <td>{{ '%.2f'|format(expense_summary.all.avg) }} PLN</td> -->
</tr>
<tr>
<td>Aktywne</td>
<td>{{ '%.2f'|format(expense_summary.active.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.active.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.active.total) }} PLN</td>
<td>{{ format_currency(expense_summary.active.month) }}</td>
<td>{{ format_currency(expense_summary.active.year) }}</td>
<td>{{ format_currency(expense_summary.active.total) }}</td>
<!-- <td>{{ '%.2f'|format(expense_summary.active.avg) }} PLN</td> -->
</tr>
<tr>
<td>Archiwalne</td>
<td>{{ '%.2f'|format(expense_summary.archived.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.archived.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.archived.total) }} PLN</td>
<td>{{ format_currency(expense_summary.archived.month) }}</td>
<td>{{ format_currency(expense_summary.archived.year) }}</td>
<td>{{ format_currency(expense_summary.archived.total) }}</td>
<!-- <td>{{ '%.2f'|format(expense_summary.archived.avg) }} PLN</td> -->
</tr>
<tr>
<td>Wygasłe</td>
<td>{{ '%.2f'|format(expense_summary.expired.month) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.expired.year) }} PLN</td>
<td>{{ '%.2f'|format(expense_summary.expired.total) }} PLN</td>
<td>{{ format_currency(expense_summary.expired.month) }}</td>
<td>{{ format_currency(expense_summary.expired.year) }}</td>
<td>{{ format_currency(expense_summary.expired.total) }}</td>
<!-- <td>{{ '%.2f'|format(expense_summary.expired.avg) }} PLN</td> -->
</tr>
</tbody>
@@ -282,7 +282,7 @@
{% if e.total_expense >= 500 %}text-danger
{% elif e.total_expense > 0 %}text-success{% endif %}">
{% if e.total_expense > 0 %}
{{ '%.2f'|format(e.total_expense) }} PLN
{{ format_currency(e.total_expense) }}
{% else %}
-
{% endif %}
+1 -1
View File
@@ -25,7 +25,7 @@
<!-- Wydatek i właściciel -->
<div class="row mb-3">
<div class="col-md-6">
<label for="amount" class="form-label">💰 Całkowity wydatek (PLN)</label>
<label for="amount" class="form-label">💰 Całkowity wydatek ({{ CURRENCY_CODE }})</label>
<input type="number" step="0.01" min="0" class="form-control bg-dark text-white border-secondary ui-consistent-input"
id="amount" name="amount" value="{{ '%.2f'|format(total_expense) }}">
</div>
+4
View File
@@ -173,6 +173,10 @@
});
</script>
<script>
window.CURRENCY_CODE = {{ CURRENCY_CODE|tojson }};
</script>
{% if request.endpoint != 'system_auth' %}
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'glightbox.min.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js_lib', 'socket.io.min.js') }}"></script>
+2 -2
View File
@@ -88,7 +88,7 @@
<button id="deselectAllBtn" class="btn btn-sm btn-outline-light active" style="display: none;">Odznacz
wszystko</button>
</div>
<h5 class="text-success m-0">💰 Suma: <span id="listsTotal">0.00 PLN</span></h5>
<h5 class="text-success m-0">💰 Suma: <span id="listsTotal">{{ format_currency(0) }}</span></h5>
</div>
<!-- Tabela list z możliwością filtrowania -->
@@ -101,7 +101,7 @@
<th>Nazwa listy</th>
<th>Właściciel</th>
<th>Data</th>
<th>Wydatki (PLN)</th>
<th>Wydatki ({{ CURRENCY_CODE }})</th>
</tr>
</thead>
<tbody id="listsTableBody">
+15 -3
View File
@@ -96,11 +96,11 @@
<br>
{% if total_expense > 0 %}
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: {{ '%.2f'|format(total_expense) }} PLN
💸 Łącznie wydano: {{ format_currency(total_expense) }}
</div>
{% else %}
<div id="total-expense2" class="text-success fw-bold mb-3">
💸 Łącznie wydano: 0.00 PLN
💸 Łącznie wydano: {{ format_currency(0) }}
</div>
{% endif %}
@@ -125,7 +125,13 @@
<div class="shopping-item-content">
<div class="shopping-item-head">
<div class="shopping-item-text">
<span id="name-{{ item.id }}" class="shopping-item-name text-white">{{ item.name }}</span>
<button type="button"
id="name-{{ item.id }}"
class="shopping-item-name text-white"
data-item-id="{{ item.id }}"
data-item-name={{ item.name|tojson }}
data-item-quantity="{{ item.quantity or 1 }}"
{% if not list.is_archived %}data-item-menu-trigger="true"{% else %}disabled aria-disabled="true"{% endif %}>{{ item.name }}</button>
{% if item.quantity and item.quantity > 1 %}
<span class="badge rounded-pill bg-secondary">x{{ item.quantity }}</span>
{% endif %}
@@ -166,6 +172,11 @@
{% endfor %}
</ul>
<div id="desktopItemMenu" hidden>
<button type="button" class="btn btn-outline-light btn-sm w-100 text-start" data-menu-action="edit">✏️ Edytuj</button>
<button type="button" class="btn btn-outline-danger btn-sm w-100 text-start" data-menu-action="delete">🗑️ Usuń</button>
</div>
<div class="modal fade" id="editItemModal" tabindex="-1" aria-labelledby="editItemModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-white">
@@ -541,6 +552,7 @@
<script src="{{ static_asset_url('static_bp.serve_js', 'access_users.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'category_modal.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'notes.js') }}"></script>
<script src="{{ static_asset_url('static_bp.serve_js', 'desktop_item_menu.js') }}"></script>
<script>
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
+5 -5
View File
@@ -12,11 +12,11 @@
{% if total_expense > 0 %}
<span id="total-expense1" class="badge rounded-pill bg-success ms-2">
💸 {{ '%.2f'|format(total_expense) }} PLN
💸 {{ format_currency(total_expense) }}
</span>
{% else %}
<span id="total-expense" class="badge rounded-pill bg-secondary ms-2" style="display: none;">
💸 0.00 PLN
💸 {{ format_currency(0) }}
</span>
{% endif %}
@@ -114,7 +114,7 @@
<span>💰 Dodaj wydatek</span>
<span class="badge rounded-pill bg-success" id="total-expense2">
💸 Łączna suma: {{ '%.2f'|format(total_expense) }} PLN
💸 Łączna suma: {{ format_currency(total_expense) }}
</span>
</div>
@@ -123,7 +123,7 @@
<div class="input-group mb-0 shopping-compact-input-group shopping-expense-input-group">
<input id="expenseAmount" type="number" step="0.01" min="0"
class="form-control bg-dark text-white border-secondary shopping-expense-amount-input"
placeholder="Kwota (PLN)">
placeholder="{{ currency_placeholder() }}">
<button onclick="submitExpense({{ list.id }})"
class="btn btn-outline-primary share-submit-btn share-submit-btn--expense shopping-compact-submit">
@@ -135,7 +135,7 @@
{% endif %}
<p id="total-expense2" style="display: none;">
<b>💸 Łącznie wydano:</b> {{ '%.2f'|format(total_expense) }} PLN
<b>💸 Łącznie wydano:</b> {{ format_currency(total_expense) }}
</p>
<button id="toggleReceiptBtn" type="button" class="receipt-disclosure mb-3"
+1 -1
View File
@@ -101,7 +101,7 @@
</div>
<div class="main-summary-stat">
<span class="main-summary-stat__label">Wydatki</span>
<strong>{{ '%.2f'|format(summary.total_expense) }} PLN</strong>
<strong>{{ format_currency(summary.total_expense) }}</strong>
</div>
</div>
</div>
+3
View File
@@ -26,6 +26,9 @@ def inject_version():
return {
"APP_VERSION": app.config["APP_VERSION"],
"CURRENCY_CODE": get_currency_code(),
"format_currency": format_currency,
"currency_placeholder": currency_placeholder,
"static_asset_url": static_asset_url,
}