push
This commit is contained in:
135
app/templates/dashboard/index.html
Normal file
135
app/templates/dashboard/index.html
Normal file
@@ -0,0 +1,135 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}<i class="fa-solid fa-chart-pie me-2 text-primary"></i>Dashboard{% endblock %}
|
||||
{% block content %}
|
||||
{% if not company %}
|
||||
{% set eyebrow='Pulpit firmy' %}{% set heading='Dashboard operacyjny' %}{% set description='Podsumowanie pracy na aktywnej firmie.' %}
|
||||
{% include 'partials/page_header.html' with context %}
|
||||
<div class="card"><div class="card-body py-5"><h4 class="mb-2">Brak wybranej firmy</h4><p class="text-secondary mb-3">Najpierw wybierz firmę z przełącznika w górnym pasku albo dodaj ją w panelu administracyjnym.</p><a class="btn btn-primary" href="{{ url_for('admin.company_form') if current_user.role == 'admin' else url_for('settings.index') }}">{{ 'Dodaj firmę' if current_user.role == 'admin' else 'Przejdź do ustawień' }}</a></div></div>
|
||||
{% else %}
|
||||
{% set eyebrow='Pulpit firmy' %}{% set heading='Dashboard operacyjny' %}{% set description='Podsumowanie bieżącego miesiąca, synchronizacji i ostatnich dokumentów.' %}
|
||||
{% include 'partials/page_header.html' with context %}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-3"><div class="card stat-card stat-blue text-white"><div class="card-body"><div class="small opacity-75"><i class="fa-solid fa-file-invoice me-1"></i>Faktury w miesiącu</div><div class="display-6">{{ month_invoices|length }}</div><div class="small">{{ company.name }}</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card stat-card stat-green text-white"><div class="card-body"><div class="small opacity-75"><i class="fa-solid fa-envelope-open-text me-1"></i>Nowe</div><div class="display-6">{{ unread }}</div></div></div></div>
|
||||
<div class="col-md-2"><div class="card stat-card stat-dark text-white"><div class="card-body"><div class="small opacity-75"><i class="fa-solid fa-wallet me-1"></i>Netto</div><div>{{ totals.net|pln }}</div></div></div></div>
|
||||
<div class="col-md-2"><div class="card stat-card stat-purple text-white"><div class="card-body"><div class="small opacity-75"><i class="fa-solid fa-percent me-1"></i>VAT</div><div>{{ totals.vat|pln }}</div></div></div></div>
|
||||
<div class="col-md-2"><div class="card stat-card stat-orange text-white"><div class="card-body"><div class="small opacity-75"><i class="fa-solid fa-sack-dollar me-1"></i>Brutto</div><div>{{ totals.gross|pln }}</div></div></div></div>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-3"><div class="card-header d-flex justify-content-between align-items-center"><span><i class="fa-solid fa-rotate me-2"></i>Synchronizacja KSeF</span><button id="syncBtn" class="btn btn-sm btn-primary" data-sync-url="{{ url_for("dashboard.sync_start") }}" data-csrf-token="{{ csrf_token() }}"><i class="fa-solid fa-download me-1"></i>Pobierz ręcznie</button></div><div class="card-body"><div class="d-flex justify-content-between align-items-center flex-wrap gap-2 small mb-2"><span>Status: <span id="syncStatusText" class="badge text-bg-info">{{ sync_status }}</span></span><span class="d-inline-flex align-items-center gap-2"><span class="text-secondary">Ostatni sync:</span><span class="badge rounded-pill text-bg-light border">{{ last_sync_display }}</span></span></div><div class="progress" style="height: 22px;"><div id="syncProgressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%">0%</div></div><div id="syncMessage" class="small text-secondary mt-2">Kliknij "Pobierz ręcznie" aby pobrać faktury z KSeF.</div></div></div>
|
||||
<div class="card"><div class="card-header"><i class="fa-solid fa-clock-rotate-left me-2"></i>Ostatnie faktury</div><div class="table-responsive"><table class="table table-sm mb-0"><thead><tr><th>Numer</th><th>Kontrahent</th><th>Brutto</th><th></th></tr></thead><tbody>{% for invoice in recent_invoices %}<tr><td>{{ invoice.invoice_number }}</td><td>{{ invoice.contractor_name }}</td><td>{{ invoice.gross_amount|pln }}</td><td class="text-end"><div class="d-inline-flex gap-2 flex-wrap justify-content-end"><a href="{{ url_for('invoices.detail', invoice_id=invoice.id) }}" class="btn btn-sm btn-outline-primary invoice-action-btn"><i class="fa-solid fa-folder-open me-1"></i>Otwórz</a><button type="button" class="btn btn-sm btn-success invoice-action-btn" data-bs-toggle="modal" data-bs-target="#payModalDashboard{{ invoice.id }}"><i class="fa-solid fa-wallet me-1"></i>Opłać</button></div>{% set payment_details = payment_details_map.get(invoice.id, {}) %}{% set modal_id = 'payModalDashboard' ~ invoice.id %}{% include 'partials/payment_modal.html' %}</td></tr>{% else %}<tr><td colspan="4" class="text-center text-secondary py-4">Brak danych.</td></tr>{% endfor %}</tbody></table></div><div class="card-body border-top py-2"><nav><ul class="pagination pagination-sm justify-content-end mb-0">{% if recent_pagination and recent_pagination.has_prev %}<li class="page-item"><a class="page-link" href="{{ url_for('dashboard.index', dashboard_page=recent_pagination.prev_num) }}">Poprz.</a></li>{% endif %}{% if recent_pagination and recent_pagination.pages > 1 %}{% for pg in range(1, recent_pagination.pages + 1) %}<li class="page-item {{ 'active' if pg == recent_pagination.page else '' }}"><a class="page-link" href="{{ url_for('dashboard.index', dashboard_page=pg) }}">{{ pg }}</a></li>{% endfor %}{% endif %}{% if recent_pagination and recent_pagination.has_next %}<li class="page-item"><a class="page-link" href="{{ url_for('dashboard.index', dashboard_page=recent_pagination.next_num) }}">Dalej</a></li>{% endif %}</ul></nav></div></div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
{% if health.critical %}
|
||||
<div class="card mb-3 border-danger">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<i class="fa-solid fa-triangle-exclamation me-2"></i>Raport krytyczny
|
||||
</div>
|
||||
<div class="card-body small">
|
||||
{% if health.ksef != 'ok' %}
|
||||
<div>API KSeF: {{ health.ksef }}</div>
|
||||
{% if health.ksef_message %}
|
||||
<div class="text-muted">{{ health.ksef_message }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if health.ceidg != 'ok' %}
|
||||
<div>API CEIDG: {{ health.ceidg }}</div>
|
||||
{% if health.ceidg_message %}
|
||||
<div class="text-muted">{{ health.ceidg_message }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="fa-solid fa-building-shield me-2"></i>Harmonogram
|
||||
</div>
|
||||
|
||||
<div class="card-body small">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Automatyczna synchronizacja</span>
|
||||
<span class="badge {{ 'bg-success' if company.sync_enabled else 'bg-secondary' }}">
|
||||
{{ 'włączona' if company.sync_enabled else 'wyłączona' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span>Interwał</span>
|
||||
<span class="badge bg-info text-dark">
|
||||
{{ company.sync_interval_minutes }} min
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span>Tryb pracy</span>
|
||||
{% if read_only %}
|
||||
<span class="badge bg-warning text-dark">tylko pobieranie</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary">pełna synchronizacja</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
let syncTimer = null;
|
||||
const syncBtn = document.getElementById('syncBtn');
|
||||
const statusMap = {queued: 'W kolejce', started: 'W toku', finished: 'Zakończona', error: 'Błąd'};
|
||||
|
||||
syncBtn?.addEventListener('click', async () => {
|
||||
syncBtn.disabled = true;
|
||||
syncBtn.classList.add('disabled');
|
||||
document.getElementById('syncMessage').textContent = 'Uruchamianie ręcznego pobierania...';
|
||||
|
||||
try {
|
||||
const res = await fetch(syncBtn.dataset.syncUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': syncBtn.dataset.csrfToken,
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !data.log_id) {
|
||||
const message = data.error || data.message || 'Nie udało się uruchomić ręcznego pobierania.';
|
||||
document.getElementById('syncMessage').textContent = message;
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('syncMessage').textContent = 'Rozpoczęto pobieranie...';
|
||||
syncTimer = setInterval(async () => {
|
||||
const r = await fetch(`/sync/status/${data.log_id}`, {credentials: 'same-origin'});
|
||||
const s = await r.json();
|
||||
const bar = document.getElementById('syncProgressBar');
|
||||
bar.style.width = `${s.progress}%`;
|
||||
bar.textContent = `${s.progress}%`;
|
||||
document.getElementById('syncStatusText').textContent = statusMap[s.status] || s.status || '—';
|
||||
document.getElementById('syncMessage').textContent = s.message || '';
|
||||
if (s.status === 'finished' || s.status === 'error') {
|
||||
clearInterval(syncTimer);
|
||||
syncBtn.disabled = false;
|
||||
syncBtn.classList.remove('disabled');
|
||||
if (s.status === 'finished') {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
}, 1200);
|
||||
} catch (err) {
|
||||
document.getElementById('syncMessage').textContent = 'Nie udało się połączyć z usługą synchronizacji.';
|
||||
syncBtn.disabled = false;
|
||||
syncBtn.classList.remove('disabled');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user