nowe funkcje i foxy
This commit is contained in:
83
app.py
83
app.py
@@ -295,7 +295,9 @@ def save_resized_image(file, path):
|
||||
image.info.clear()
|
||||
|
||||
new_path = path.rsplit(".", 1)[0] + ".webp"
|
||||
image.save(new_path, format="WEBP", quality=100, method=0)
|
||||
# image.save(new_path, format="WEBP", quality=100, method=0)
|
||||
image.save(new_path, format="WEBP", lossless=True, method=6)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Błąd podczas przetwarzania obrazu: {e}")
|
||||
|
||||
@@ -662,13 +664,34 @@ def favicon():
|
||||
|
||||
@app.route("/")
|
||||
def main_page():
|
||||
# now = datetime.utcnow()
|
||||
now = datetime.now(timezone.utc)
|
||||
month_str = request.args.get("month")
|
||||
start = end = None
|
||||
|
||||
if month_str:
|
||||
try:
|
||||
year, month = map(int, month_str.split("-"))
|
||||
start = datetime(year, month, 1, tzinfo=timezone.utc)
|
||||
end = (start + timedelta(days=31)).replace(day=1)
|
||||
except:
|
||||
start = end = None
|
||||
|
||||
def date_filter(query):
|
||||
if start and end:
|
||||
query = query.filter(
|
||||
ShoppingList.created_at >= start, ShoppingList.created_at < end
|
||||
)
|
||||
return query
|
||||
|
||||
if current_user.is_authenticated:
|
||||
user_lists = (
|
||||
ShoppingList.query.filter_by(owner_id=current_user.id, is_archived=False)
|
||||
.filter((ShoppingList.expires_at == None) | (ShoppingList.expires_at > now))
|
||||
date_filter(
|
||||
ShoppingList.query.filter_by(
|
||||
owner_id=current_user.id, is_archived=False
|
||||
).filter(
|
||||
(ShoppingList.expires_at == None) | (ShoppingList.expires_at > now)
|
||||
)
|
||||
)
|
||||
.order_by(ShoppingList.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
@@ -680,11 +703,16 @@ def main_page():
|
||||
)
|
||||
|
||||
public_lists = (
|
||||
ShoppingList.query.filter(
|
||||
ShoppingList.is_public == True,
|
||||
ShoppingList.owner_id != current_user.id,
|
||||
((ShoppingList.expires_at == None) | (ShoppingList.expires_at > now)),
|
||||
ShoppingList.is_archived == False,
|
||||
date_filter(
|
||||
ShoppingList.query.filter(
|
||||
ShoppingList.is_public == True,
|
||||
ShoppingList.owner_id != current_user.id,
|
||||
(
|
||||
(ShoppingList.expires_at == None)
|
||||
| (ShoppingList.expires_at > now)
|
||||
),
|
||||
ShoppingList.is_archived == False,
|
||||
)
|
||||
)
|
||||
.order_by(ShoppingList.created_at.desc())
|
||||
.all()
|
||||
@@ -693,10 +721,15 @@ def main_page():
|
||||
user_lists = []
|
||||
archived_lists = []
|
||||
public_lists = (
|
||||
ShoppingList.query.filter(
|
||||
ShoppingList.is_public == True,
|
||||
((ShoppingList.expires_at == None) | (ShoppingList.expires_at > now)),
|
||||
ShoppingList.is_archived == False,
|
||||
date_filter(
|
||||
ShoppingList.query.filter(
|
||||
ShoppingList.is_public == True,
|
||||
(
|
||||
(ShoppingList.expires_at == None)
|
||||
| (ShoppingList.expires_at > now)
|
||||
),
|
||||
ShoppingList.is_archived == False,
|
||||
)
|
||||
)
|
||||
.order_by(ShoppingList.created_at.desc())
|
||||
.all()
|
||||
@@ -710,6 +743,8 @@ def main_page():
|
||||
user_lists=user_lists,
|
||||
public_lists=public_lists,
|
||||
archived_lists=archived_lists,
|
||||
now=now,
|
||||
timedelta=timedelta,
|
||||
)
|
||||
|
||||
|
||||
@@ -1028,16 +1063,24 @@ def user_expenses():
|
||||
)
|
||||
|
||||
|
||||
@app.route("/user/expenses_data")
|
||||
@app.route("/user_expenses_data")
|
||||
@login_required
|
||||
def user_expenses_data():
|
||||
range_type = request.args.get("range", "monthly")
|
||||
start_date = request.args.get("start_date")
|
||||
end_date = request.args.get("end_date")
|
||||
show_all = request.args.get("show_all", "false").lower() == "true"
|
||||
|
||||
query = Expense.query.join(ShoppingList, Expense.list_id == ShoppingList.id).filter(
|
||||
ShoppingList.owner_id == current_user.id
|
||||
)
|
||||
query = Expense.query.join(ShoppingList, Expense.list_id == ShoppingList.id)
|
||||
|
||||
if show_all:
|
||||
query = query.filter(
|
||||
or_(
|
||||
ShoppingList.owner_id == current_user.id, ShoppingList.is_public == True
|
||||
)
|
||||
)
|
||||
else:
|
||||
query = query.filter(ShoppingList.owner_id == current_user.id)
|
||||
|
||||
if start_date and end_date:
|
||||
try:
|
||||
@@ -2110,15 +2153,15 @@ def crop_receipt():
|
||||
old_path = os.path.join(app.config["UPLOAD_FOLDER"], receipt.filename)
|
||||
|
||||
try:
|
||||
image = Image.open(file).convert("RGB")
|
||||
new_filename = generate_new_receipt_filename(receipt.list_id)
|
||||
new_path = os.path.join(app.config["UPLOAD_FOLDER"], new_filename)
|
||||
image.save(new_path, format="WEBP", quality=100)
|
||||
|
||||
save_resized_image(file, new_path)
|
||||
|
||||
if os.path.exists(old_path):
|
||||
os.remove(old_path)
|
||||
|
||||
receipt.filename = new_filename
|
||||
receipt.filename = os.path.basename(new_path)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(success=True)
|
||||
|
@@ -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;
|
||||
}
|
@@ -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();
|
||||
|
@@ -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
14
static/js/select_month.js
Normal 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();
|
||||
});
|
||||
});
|
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -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}`;
|
||||
}
|
||||
|
@@ -78,6 +78,11 @@
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-bs-dismiss="modal">Anuluj</button>
|
||||
<button class="btn btn-success" id="saveCrop">Zapisz</button>
|
||||
<div id="cropLoading" class="position-absolute top-50 start-50 translate-middle text-center d-none"
|
||||
style="z-index: 1055;">
|
||||
<div class="spinner-border text-light" role="status"></div>
|
||||
<div class="mt-2 text-light">⏳ Pracuję...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -31,6 +31,33 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set month_names = ["styczeń", "luty", "marzec", "kwiecień", "maj", "czerwiec", "lipiec", "sierpień", "wrzesień",
|
||||
"październik", "listopad", "grudzień"] %}
|
||||
{% set selected_month = request.args.get('month') or now.strftime('%Y-%m') %}
|
||||
|
||||
<!-- Pulpit: zwykły <select> -->
|
||||
<div class="d-none d-md-flex justify-content-end align-items-center flex-wrap gap-2 mb-3">
|
||||
<label for="monthSelect" class="text-white small mb-0">📅 Wybierz miesiąc:</label>
|
||||
<select id="monthSelect" class="form-select form-select-sm bg-dark text-white border-secondary"
|
||||
style="min-width: 180px;">
|
||||
{% for offset in range(0, 6) %}
|
||||
{% set d = (now - timedelta(days=offset * 30)) %}
|
||||
{% set val = d.strftime('%Y-%m') %}
|
||||
<option value="{{ val }}" {% if selected_month==val %}selected{% endif %}>
|
||||
{{ month_names[d.month - 1] }} {{ d.year }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
<option value="">Wyświetl wszystko</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Telefon: przycisk otwierający modal -->
|
||||
<div class="d-md-none mb-3">
|
||||
<button class="btn btn-outline-light w-100" data-bs-toggle="modal" data-bs-target="#monthPickerModal">
|
||||
📅 Wybierz miesiąc
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<h3 class="mt-4 d-flex justify-content-between align-items-center flex-wrap">
|
||||
Twoje listy
|
||||
@@ -78,8 +105,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p><span class="badge rounded-pill bg-secondary opacity-75">Nie masz jeszcze żadnych list. Utwórz pierwszą, korzystając
|
||||
z formularza powyżej</span></p>
|
||||
<p><span class="badge rounded-pill bg-secondary opacity-75">Nie utworzono żadnej listy</span></p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@@ -114,7 +140,6 @@
|
||||
<p><span class="badge rounded-pill bg-secondary opacity-75">Brak dostępnych list publicznych do wyświetlenia</span></p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="modal fade" id="archivedModal" tabindex="-1" aria-labelledby="archivedModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
@@ -146,8 +171,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="monthPickerModal" tabindex="-1" aria-labelledby="monthPickerModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📅 Wybierz miesiąc</h5>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Zamknij"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-grid gap-2">
|
||||
{% for offset in range(0, 6) %}
|
||||
{% set d = (now - timedelta(days=offset * 30)) %}
|
||||
{% set val = d.strftime('%Y-%m') %}
|
||||
<a href="{{ url_for('main_page', month=val) }}" class="btn btn-outline-light">
|
||||
{{ month_names[d.month - 1] }} {{ d.year }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
<a href="{{ url_for('main_page') }}" class="btn btn-outline-secondary">📋 Wyświetl wszystkie</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='toggle_button.js') }}"></script>
|
||||
<script src="{{ url_for('static_bp.serve_js', filename='select_month.js') }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
@@ -6,7 +6,11 @@
|
||||
<h2 class="mb-2">Statystyki wydatków</h2>
|
||||
<a href="{{ url_for('main_page') }}" class="btn btn-outline-secondary">← Powrót</a>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="showAllLists" {% if show_all %}checked{% endif %}>
|
||||
<label class="form-check-label ms-2 text-white" for="showAllLists">Pokaż wszystkie publiczne listy
|
||||
innych</label>
|
||||
</div>
|
||||
<div class="card bg-dark text-white mb-5">
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-tabs mb-3" id="expenseTabs" role="tablist">
|
||||
@@ -47,11 +51,7 @@
|
||||
<label class="form-check-label ms-2 text-white" for="onlyWithExpenses">Pokaż tylko listy z
|
||||
wydatkami</label>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input class="form-check-input" type="checkbox" id="showAllLists" {% if show_all %}checked{% endif %}>
|
||||
<label class="form-check-label ms-2 text-white" for="showAllLists">Pokaż wszystkie publiczne listy
|
||||
innych</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="input-group input-group-sm mb-3 w-100" style="max-width: 570px;">
|
||||
<span class="input-group-text bg-secondary text-white border-secondary">Od</span>
|
||||
|
Reference in New Issue
Block a user