Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
172b46ad07 | ||
|
|
4c9d665ae2 |
@@ -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
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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
shopping_app/static/js/desktop_item_menu.js
Normal file
126
shopping_app/static/js/desktop_item_menu.js
Normal 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;
|
||||
})();
|
||||
@@ -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 || [],
|
||||
}],
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -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)} zł</strong></p>`;
|
||||
<p class="mb-0">💸 Wydatek: <strong>${formatCurrencyAmount(totalExpense)}</strong></p>`;
|
||||
productList.appendChild(summary);
|
||||
|
||||
const purchased = createSection("✔️ Kupione");
|
||||
|
||||
@@ -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" : "";
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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>
|
||||
@@ -51,7 +51,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>💸 Średnia kwota na listę</td>
|
||||
<td class="text-end fw-bold">{{ avg_list_expense }} zł</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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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ść' }}');
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user