2 Commits

Author SHA1 Message Date
Mateusz Gruszczyński
4988ad9a5f cofnięcie zmian z przesuwaniem listy 2025-08-15 13:23:34 +02:00
Mateusz Gruszczyński
d321521ef1 cofnięcie zmian z przesuwaniem listy 2025-08-15 13:22:47 +02:00
6 changed files with 82 additions and 227 deletions

29
app.py
View File

@@ -1527,20 +1527,18 @@ def edit_my_list(list_id):
next_page = request.args.get("next") or request.referrer
if request.method == "POST":
move_to_month = request.form.get("move_to_month", "")
move_to_month = request.form.get("move_to_month")
if move_to_month:
try:
year, month = map(int, move_to_month.split("-"))
new_created_at = datetime(year, month, 1, tzinfo=timezone.utc)
if l.created_at is None or l.created_at.year != year or l.created_at.month != month:
l.created_at = new_created_at
db.session.commit()
flash(
f"Zmieniono datę utworzenia listy na {new_created_at.strftime('%Y-%m-%d')}",
"success",
)
return redirect(next_page or url_for("main_page"))
l.created_at = new_created_at
db.session.commit()
flash(
f"Zmieniono datę utworzenia listy na {new_created_at.strftime('%Y-%m-%d')}",
"success",
)
return redirect(next_page or url_for("main_page"))
except ValueError:
flash("Nieprawidłowy format miesiąca", "danger")
return redirect(next_page or url_for("main_page"))
@@ -1577,26 +1575,15 @@ def edit_my_list(list_id):
flash("Zaktualizowano dane listy", "success")
return redirect(next_page or url_for("main_page"))
if l.created_at:
selected_year = l.created_at.year
selected_month = f"{l.created_at.month:02d}"
else:
now = datetime.now()
selected_year = now.year
selected_month = f"{now.month:02d}"
return render_template(
"edit_my_list.html",
list=l,
receipts=receipts,
categories=categories,
selected_categories=selected_categories_ids,
current_year=selected_year,
current_month=selected_month,
)
@app.route("/delete_user_list/<int:list_id>", methods=["POST"])
@login_required
def delete_user_list(list_id):

View File

@@ -1,109 +0,0 @@
let cropper;
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");
cropImage.src = imgSrc;
if (cropper) {
cropper.destroy();
cropper = null;
}
cropImage.onload = () => {
cropper = new Cropper(cropImage, {
viewMode: 1,
autoCropArea: 1,
responsive: true,
background: false,
zoomable: true,
movable: true,
dragMode: 'move',
minContainerHeight: 400,
minContainerWidth: 400,
});
};
});
document.getElementById("saveCrop").addEventListener("click", function () {
if (!cropper) return;
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);
fetch("/admin/crop_receipt", {
method: "POST",
body: formData,
})
.then((res) => res.json())
.then((data) => {
spinner.classList.add("d-none");
if (data.success) {
showToast("Zapisano przycięty paragon", "success");
setTimeout(() => location.reload(), 1500);
} else {
showToast("Błąd: " + (data.error || "Nieznany"), "danger");
}
})
.catch((err) => {
spinner.classList.add("d-none");
showToast("Błąd sieci", "danger");
console.error(err);
});
}, "image/webp", 1.0);
});
});

View File

@@ -1,11 +0,0 @@
function updateMoveToMonth() {
const year = document.getElementById('move_to_month_year').value;
const month = document.getElementById('move_to_month_month').value;
const hiddenInput = document.getElementById('move_to_month');
hiddenInput.value = `${year}-${month}`;
}
document.getElementById('move_to_month_year').addEventListener('change', updateMoveToMonth);
document.getElementById('move_to_month_month').addEventListener('change', updateMoveToMonth);
updateMoveToMonth();

View File

@@ -282,5 +282,4 @@
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static_bp.serve_js', filename='select.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='move_to_month.js') }}"></script>
{% endblock %}

View File

@@ -8,26 +8,26 @@
<link rel="icon" type="image/svg+xml" href="{{ url_for('favicon') }}">
{# --- Style CSS ładowane tylko dla niezablokowanych --- #}
{% if not is_blocked %}
{% if not is_blocked %}
<link href="{{ url_for('static_bp.serve_css', filename='style.css') }}" rel="stylesheet">
<link href="{{ url_for('static_bp.serve_css_lib', filename='glightbox.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static_bp.serve_css_lib', filename='sort_table.min.css') }}" rel="stylesheet">
{% endif %}
{% endif %}
{# --- Bootstrap zawsze --- #}
<link href="{{ url_for('static_bp.serve_css_lib', filename='bootstrap.min.css') }}" rel="stylesheet">
{# --- Bootstrap zawsze --- #}
<link href="{{ url_for('static_bp.serve_css_lib', filename='bootstrap.min.css') }}" rel="stylesheet">
{# --- Cropper CSS tylko dla wybranych podstron --- #}
{% set substrings_cropper = ['/admin/receipts', '/edit_my_list'] %}
{% if substrings_cropper | select("in", request.path) | list | length > 0 %}
{# --- Cropper CSS tylko dla wybranych podstron --- #}
{% set substrings_cropper = ['/admin/receipts', '/edit_my_list'] %}
{% if substrings_cropper | select("in", request.path) | list | length > 0 %}
<link href="{{ url_for('static_bp.serve_css_lib', filename='cropper.min.css') }}" rel="stylesheet">
{% endif %}
{% endif %}
{# --- Tom Select CSS tylko dla wybranych podstron --- #}
{% set substrings_tomselect = ['/edit_my_list', '/admin/edit_list', '/admin/mass_edit_categories'] %}
{% if substrings_tomselect | select("in", request.path) | list | length > 0 %}
{# --- Tom Select CSS tylko dla wybranych podstron --- #}
{% set substrings_tomselect = ['/edit_my_list', '/admin/edit_list', '/admin/mass_edit_categories'] %}
{% if substrings_tomselect | select("in", request.path) | list | length > 0 %}
<link href="{{ url_for('static_bp.serve_css_lib', filename='tom-select.bootstrap5.min.css') }}" rel="stylesheet">
{% endif %}
{% endif %}
</head>
<body class="bg-dark text-white">

View File

@@ -22,20 +22,20 @@
<label class="form-label">⚙️ Statusy listy</label>
<div class="d-flex flex-wrap gap-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="public" name="is_public"
{% if list.is_public %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="public" name="is_public" {% if list.is_public
%}checked{% endif %}>
<label class="form-check-label" for="public">🌐 Publiczna</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="temporary" name="is_temporary"
{% if list.is_temporary %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="temporary" name="is_temporary" {% if list.is_temporary
%}checked{% endif %}>
<label class="form-check-label" for="temporary">⏳ Tymczasowa</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="archived" name="is_archived"
{% if list.is_archived %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="archived" name="is_archived" {% if list.is_archived
%}checked{% endif %}>
<label class="form-check-label" for="archived">📦 Archiwalna</label>
</div>
</div>
@@ -55,35 +55,25 @@
</div>
</div>
<div class="row mb-4">
<!-- Przenoszenie -->
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">📆 Utworzono</label>
<span class="badge bg-success rounded-pill text-dark ms-1">
{{ list.created_at.strftime('%Y-%m-%d') }}
</span>
<label class="form-label">📆 Utworzono:</label>
<p class="form-control-plaintext text-white">
<span class="badge bg-success rounded-pill text-dark ms-1">
{{ list.created_at.strftime('%Y-%m-%d') }}
</span>
</p>
</div>
<div class="col-md-6">
<label class="form-label">📁 Przenieś do miesiąca</label>
<div class="d-flex gap-2">
<select id="move_to_month_year" class="form-select bg-dark text-white border-secondary rounded">
{% for y in range(2022, 2031) %}
<option value="{{ y }}" {% if y == current_year %}selected{% endif %}>{{ y }}</option>
{% endfor %}
</select>
<select id="move_to_month_month" class="form-select bg-dark text-white border-secondary rounded">
{% for m in range(1, 13) %}
<option value="{{ "%02d"|format(m) }}" {% if "%02d"|format(m) == current_month %}selected{% endif %}>
{{ "%02d"|format(m) }}
</option>
{% endfor %}
</select>
</div>
<!-- Ukryte pole, które jest wysyłane do backendu -->
<input type="hidden" id="move_to_month" name="move_to_month" value="{{ list.move_to_month or '' }}">
<label class="form-label">📁 Przenieś do miesiąca (format: rok-miesiąć np 2026-01)</label>
<input type="month" id="move_to_month" name="move_to_month"
class="form-control bg-dark text-white border-secondary rounded">
</div>
</div>
<!-- Kategorie -->
<div class="mb-4">
<label for="categories" class="form-label">🏷️ Kategorie</label>
@@ -108,55 +98,55 @@
</div>
</div>
{% if receipts %}
<hr class="my-4">
<h5>Paragony przypisane do tej listy</h5>
{% if receipts %}
<hr class="my-4">
<h5>Paragony przypisane do tej listy</h5>
<div class="row">
{% for r in receipts %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=r.filename) }}" class="glightbox" data-gallery="receipts"
data-title="{{ r.filename }}">
<img src="{{ url_for('uploaded_file', filename=r.filename) }}" class="card-img-top"
style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ r.filename }}</p>
<p class="small mb-1">Wgrano: {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</p>
{% if r.filesize and r.filesize >= 1024 * 1024 %}
<p class="small mb-1">Rozmiar: {{ (r.filesize / 1024 / 1024) | round(2) }} MB</p>
{% elif r.filesize %}
<p class="small mb-1">Rozmiar: {{ (r.filesize / 1024) | round(1) }} kB</p>
{% else %}
<p class="small mb-1 text-muted">Brak danych o rozmiarze</p>
{% endif %}
<div class="row">
{% for r in receipts %}
<div class="col-6 col-md-4 col-lg-3">
<div class="card bg-dark text-white h-100">
<a href="{{ url_for('uploaded_file', filename=r.filename) }}" class="glightbox" data-gallery="receipts"
data-title="{{ r.filename }}">
<img src="{{ url_for('uploaded_file', filename=r.filename) }}" class="card-img-top"
style="object-fit: cover; height: 200px;">
</a>
<div class="card-body text-center">
<p class="small text-truncate mb-1">{{ r.filename }}</p>
<p class="small mb-1">Wgrano: {{ r.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</p>
{% if r.filesize and r.filesize >= 1024 * 1024 %}
<p class="small mb-1">Rozmiar: {{ (r.filesize / 1024 / 1024) | round(2) }} MB</p>
{% elif r.filesize %}
<p class="small mb-1">Rozmiar: {{ (r.filesize / 1024) | round(1) }} kB</p>
{% else %}
<p class="small mb-1 text-muted">Brak danych o rozmiarze</p>
{% endif %}
<a href="{{ url_for('rotate_receipt_user', receipt_id=r.id) }}"
class="btn btn-sm btn-outline-warning w-100 mb-2">🔄 Obróć o 90°</a>
<a href="{{ url_for('rotate_receipt_user', receipt_id=r.id) }}"
class="btn btn-sm btn-outline-warning w-100 mb-2">🔄 Obróć o 90°</a>
<a href="#" class="btn btn-sm btn-outline-secondary w-100 mb-2" data-bs-toggle="modal"
data-bs-target="#userCropModal" data-img-src="{{ url_for('uploaded_file', filename=r.filename) }}"
data-receipt-id="{{ r.id }}" data-crop-endpoint="{{ url_for('crop_receipt_user') }}">
✂️ Przytnij
</a>
<a href="{{ url_for('delete_receipt_user', receipt_id=r.id) }}" class="btn btn-sm btn-outline-danger w-100"
onclick="return confirm('Na pewno usunąć ten paragon?')">🗑️ Usuń</a>
</div>
</div>
<a href="#" class="btn btn-sm btn-outline-secondary w-100 mb-2" data-bs-toggle="modal"
data-bs-target="#userCropModal" data-img-src="{{ url_for('uploaded_file', filename=r.filename) }}"
data-receipt-id="{{ r.id }}" data-crop-endpoint="{{ url_for('crop_receipt_user') }}">
✂️ Przytnij
</a>
<a href="{{ url_for('delete_receipt_user', receipt_id=r.id) }}" class="btn btn-sm btn-outline-danger w-100"
onclick="return confirm('Na pewno usunąć ten paragon?')">🗑️ Usuń</a>
</div>
{% endfor %}
</div>
{% endif %}
<hr class="my-3">
<!-- Trigger przycisk -->
<div class="btn-group mt-4" role="group">
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
🗑️ Usuń tę listę
</button>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<hr class="my-3">
<!-- Trigger przycisk -->
<div class="btn-group mt-4" role="group">
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
🗑️ Usuń tę listę
</button>
</div>
</div>
</div>
<!-- MODAL -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
@@ -212,5 +202,4 @@
<script src="{{ url_for('static_bp.serve_js', filename='user_receipt_crop.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='receipt_crop_logic.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='select.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js', filename='move_to_month.js') }}"></script>
{% endblock %}