Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Piotrex paid 180 PLN instead of 200 PLN — status "partial" now shows as blue instead of unstyled black text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
650 lines
25 KiB
HTML
Executable File
650 lines
25 KiB
HTML
Executable File
{% extends "base.html" %}
|
||
|
||
{% block title %}Skladki Czlonkowskie - Norda Biznes Partner{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.admin-header {
|
||
margin-bottom: var(--spacing-xl);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.admin-header h1 {
|
||
font-size: var(--font-size-3xl);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: var(--spacing-lg);
|
||
margin-bottom: var(--spacing-2xl);
|
||
}
|
||
|
||
.stat-card {
|
||
background: var(--surface);
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
border-radius: var(--radius);
|
||
box-shadow: var(--shadow);
|
||
text-align: center;
|
||
}
|
||
|
||
.stat-card.success { border-left: 3px solid var(--success); }
|
||
.stat-card.warning { border-left: 3px solid var(--warning); }
|
||
.stat-card.primary { border-left: 3px solid var(--primary); }
|
||
|
||
.stat-value {
|
||
font-size: var(--font-size-xl);
|
||
font-weight: 700;
|
||
color: var(--primary);
|
||
}
|
||
|
||
.stat-label {
|
||
font-size: var(--font-size-xs);
|
||
}
|
||
|
||
.stat-label {
|
||
color: var(--text-secondary);
|
||
font-size: var(--font-size-sm);
|
||
margin-top: var(--spacing-xs);
|
||
}
|
||
|
||
.filters-bar {
|
||
background: var(--surface);
|
||
padding: var(--spacing-lg);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: var(--spacing-xl);
|
||
display: flex;
|
||
gap: var(--spacing-md);
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
.filters-bar select, .filters-bar input {
|
||
padding: var(--spacing-sm) var(--spacing-md);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
font-size: var(--font-size-sm);
|
||
}
|
||
|
||
.filters-bar .btn {
|
||
padding: var(--spacing-sm) var(--spacing-lg);
|
||
}
|
||
|
||
.section {
|
||
background: var(--surface);
|
||
padding: var(--spacing-xl);
|
||
border-radius: var(--radius-lg);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: var(--spacing-xl);
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--spacing-lg);
|
||
flex-wrap: wrap;
|
||
gap: var(--spacing-md);
|
||
}
|
||
|
||
.section h2 {
|
||
font-size: var(--font-size-xl);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
.fees-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.fees-table th,
|
||
.fees-table td {
|
||
padding: var(--spacing-md);
|
||
text-align: left;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
|
||
.fees-table th {
|
||
font-weight: 600;
|
||
color: var(--text-secondary);
|
||
font-size: var(--font-size-sm);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
background: var(--background);
|
||
}
|
||
|
||
.fees-table tr:hover {
|
||
background: var(--background);
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-block;
|
||
padding: var(--spacing-xs) var(--spacing-sm);
|
||
border-radius: var(--radius-full);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.status-paid { background: var(--success-bg); color: var(--success); }
|
||
.status-pending { background: var(--warning-bg); color: var(--warning); }
|
||
.status-overdue { background: var(--error-bg); color: var(--error); }
|
||
.status-partial { background: var(--info-bg); color: var(--info); }
|
||
.status-brak { background: var(--surface-secondary); color: var(--text-secondary); }
|
||
|
||
.btn-small {
|
||
padding: var(--spacing-xs) var(--spacing-sm);
|
||
font-size: var(--font-size-xs);
|
||
}
|
||
|
||
.actions-cell {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Modal */
|
||
.modal {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.5);
|
||
z-index: 1000;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.modal.active {
|
||
display: flex;
|
||
}
|
||
|
||
.modal-content {
|
||
background: var(--surface);
|
||
padding: var(--spacing-xl);
|
||
border-radius: var(--radius-lg);
|
||
max-width: 500px;
|
||
width: 90%;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: var(--spacing-lg);
|
||
}
|
||
|
||
.modal-header h3 {
|
||
font-size: var(--font-size-xl);
|
||
}
|
||
|
||
.modal-close {
|
||
background: none;
|
||
border: none;
|
||
font-size: var(--font-size-xl);
|
||
cursor: pointer;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
.form-group {
|
||
margin-bottom: var(--spacing-md);
|
||
}
|
||
|
||
.form-group label {
|
||
display: block;
|
||
margin-bottom: var(--spacing-xs);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-group input, .form-group select, .form-group textarea {
|
||
width: 100%;
|
||
padding: var(--spacing-sm);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-md);
|
||
}
|
||
|
||
.btn-group {
|
||
display: flex;
|
||
gap: var(--spacing-sm);
|
||
margin-top: var(--spacing-lg);
|
||
}
|
||
|
||
/* Month grid for year view */
|
||
.month-cell {
|
||
width: 30px;
|
||
height: 30px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: var(--radius-sm);
|
||
font-size: var(--font-size-xs);
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.month-cell.paid { background: var(--success); color: white; }
|
||
.month-cell.partial { background: #60a5fa; color: white; }
|
||
.month-cell.pending { background: var(--warning); color: white; }
|
||
.month-cell.overdue { background: var(--error); color: white; }
|
||
.month-cell.empty { background: var(--surface-secondary); color: var(--text-secondary); }
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<div class="admin-header">
|
||
<h1>Skladki Czlonkowskie</h1>
|
||
<div class="header-actions">
|
||
<a href="{{ url_for('admin.admin_fees_export', year=year, month=month) }}" class="btn btn-secondary">
|
||
Eksportuj CSV
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="stats-grid">
|
||
<div class="stat-card primary">
|
||
<div class="stat-value">{{ total_companies }}</div>
|
||
<div class="stat-label">Firm czlonkowskich</div>
|
||
</div>
|
||
<div class="stat-card success">
|
||
<div class="stat-value">{{ paid_count }}</div>
|
||
<div class="stat-label">Oplaconych</div>
|
||
</div>
|
||
<div class="stat-card warning">
|
||
<div class="stat-value">{{ pending_count }}</div>
|
||
<div class="stat-label">Oczekujacych</div>
|
||
</div>
|
||
<div class="stat-card primary">
|
||
<div class="stat-value">{{ total_paid|int }} zł</div>
|
||
<div class="stat-label">Zebrano</div>
|
||
</div>
|
||
<div class="stat-card warning">
|
||
<div class="stat-value">{{ (total_due - total_paid)|int }} zł</div>
|
||
<div class="stat-label">Do zebrania</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Legenda -->
|
||
<div style="display: flex; gap: var(--spacing-lg); flex-wrap: wrap; margin-bottom: var(--spacing-md); font-size: var(--font-size-sm); color: var(--text-secondary); align-items: center;">
|
||
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell paid" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Opłacone</span>
|
||
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell partial" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Niepełna wpłata</span>
|
||
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell pending" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Oczekujące</span>
|
||
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell overdue" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">1</span> Zaległe</span>
|
||
<span style="display: flex; align-items: center; gap: 4px;"><span class="month-cell empty" style="width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px;">-</span> Brak danych</span>
|
||
</div>
|
||
|
||
<!-- Filters -->
|
||
<div class="filters-bar">
|
||
<form id="feesFilterForm" method="GET" action="{{ url_for('admin.admin_fees') }}" style="display: flex; gap: var(--spacing-md); flex-wrap: wrap; align-items: center;">
|
||
<select name="year" onchange="this.form.submit()">
|
||
{% for y in years %}
|
||
<option value="{{ y }}" {% if y == year %}selected{% endif %}>{{ y }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<select name="month" onchange="this.form.submit()">
|
||
<option value="">-- Cały rok --</option>
|
||
{% for m, name in months %}
|
||
<option value="{{ m }}" {% if m == month %}selected{% endif %}>{{ name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
|
||
<select name="status" onchange="this.form.submit()">
|
||
<option value="">-- Wszystkie firmy --</option>
|
||
<option value="paid" {% if status_filter == 'paid' %}selected{% endif %}>{% if month %}Opłacone{% else %}Uregulowane cały rok{% endif %}</option>
|
||
<option value="pending" {% if status_filter == 'pending' %}selected{% endif %}>Oczekujące</option>
|
||
{% if month %}
|
||
<option value="overdue" {% if status_filter == 'overdue' %}selected{% endif %}>Zaległe</option>
|
||
{% else %}
|
||
<option value="partial" {% if status_filter == 'partial' %}selected{% endif %}>Częściowo opłacone</option>
|
||
{% endif %}
|
||
</select>
|
||
</form>
|
||
|
||
{% if month %}
|
||
<button class="btn btn-success" onclick="generateFees()">
|
||
Generuj skladki na {{ dict(months).get(month, month) }}
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Companies Table -->
|
||
<div class="section">
|
||
<div class="section-header">
|
||
<h2>Lista firm {% if month %}({{ dict(months).get(month, month) }} {{ year }}){% else %}({{ year }}){% endif %}</h2>
|
||
{% if month %}
|
||
<button class="btn btn-success btn-small" onclick="bulkMarkPaid()">
|
||
Oznacz zaznaczone jako oplacone
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<table class="fees-table">
|
||
<thead>
|
||
<tr>
|
||
{% if month %}<th><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th>{% endif %}
|
||
<th>Firma</th>
|
||
{% if month %}
|
||
<th>Status</th>
|
||
<th>Kwota</th>
|
||
<th>Zaplacono</th>
|
||
<th>Data platnosci</th>
|
||
<th>Akcje</th>
|
||
{% else %}
|
||
<th>Sty</th><th>Lut</th><th>Mar</th><th>Kwi</th><th>Maj</th><th>Cze</th>
|
||
<th>Lip</th><th>Sie</th><th>Wrz</th><th>Paz</th><th>Lis</th><th>Gru</th>
|
||
{% endif %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for cf in companies_fees %}
|
||
<tr>
|
||
{% if month %}
|
||
<td>
|
||
{% if cf.fee %}
|
||
<input type="checkbox" class="fee-checkbox" value="{{ cf.fee.id }}" {% if cf.status == 'paid' %}disabled{% endif %}>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<a href="{{ url_for('company_detail_by_slug', slug=cf.company.slug) }}" target="_blank">
|
||
{{ cf.company.name }}
|
||
</a>
|
||
</td>
|
||
<td>
|
||
<span class="status-badge status-{{ cf.status }}">
|
||
{% if cf.status == 'paid' %}Oplacone
|
||
{% elif cf.status == 'pending' %}Oczekuje
|
||
{% elif cf.status == 'overdue' %}Zalegle
|
||
{% elif cf.status == 'partial' %}Czesciowe
|
||
{% else %}Brak
|
||
{% endif %}
|
||
</span>
|
||
</td>
|
||
<td>{% if cf.fee %}{{ cf.fee.amount }} zl{% else %}-{% endif %}</td>
|
||
<td>{% if cf.fee and cf.fee.amount_paid %}{{ cf.fee.amount_paid }} zl{% else %}-{% endif %}</td>
|
||
<td>{% if cf.fee and cf.fee.payment_date %}{{ cf.fee.payment_date }}{% else %}-{% endif %}</td>
|
||
<td class="actions-cell">
|
||
{% if cf.fee and cf.status != 'paid' %}
|
||
<button class="btn btn-success btn-small" onclick="openPaymentModal({{ cf.fee.id }}, '{{ cf.company.name }}', {{ cf.fee.amount }})">
|
||
Oplac
|
||
</button>
|
||
{% elif not cf.fee %}
|
||
<span class="text-secondary">Brak rekordu</span>
|
||
{% endif %}
|
||
</td>
|
||
{% else %}
|
||
<td>
|
||
<a href="{{ url_for('company_detail_by_slug', slug=cf.company.slug) }}" target="_blank">
|
||
{{ cf.company.name }}
|
||
</a>
|
||
{% if cf.monthly_rate and cf.monthly_rate > 200 %}
|
||
<span style="display:inline-block;background:#dbeafe;color:#1e40af;font-size:10px;padding:1px 5px;border-radius:3px;font-weight:600;vertical-align:middle;margin-left:4px;">{{ cf.monthly_rate }} zł</span>
|
||
{% endif %}
|
||
</td>
|
||
{% for m in range(1, 13) %}
|
||
<td>
|
||
{% set fee = cf.months.get(m) %}
|
||
{% if fee %}
|
||
<span class="month-cell {{ fee.status }}" title="{{ fee.status }}: {{ fee.amount }} zl">
|
||
{{ m }}
|
||
</span>
|
||
{% else %}
|
||
<span class="month-cell empty" title="Brak rekordu">-</span>
|
||
{% endif %}
|
||
</td>
|
||
{% endfor %}
|
||
{% endif %}
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Payment Modal -->
|
||
<div class="modal" id="paymentModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h3>Rejestracja platnosci</h3>
|
||
<button class="modal-close" onclick="closePaymentModal()">×</button>
|
||
</div>
|
||
<form id="paymentForm">
|
||
<input type="hidden" name="fee_id" id="modalFeeId">
|
||
|
||
<div class="form-group">
|
||
<label>Firma</label>
|
||
<input type="text" id="modalCompanyName" disabled>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Kwota do zaplaty</label>
|
||
<input type="number" name="amount_paid" id="modalAmount" step="0.01" required>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Data platnosci</label>
|
||
<input type="date" name="payment_date" id="modalDate" value="{{ now.strftime('%Y-%m-%d') if now else '' }}">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Metoda platnosci</label>
|
||
<select name="payment_method">
|
||
<option value="transfer">Przelew bankowy</option>
|
||
<option value="cash">Gotowka</option>
|
||
<option value="card">Karta</option>
|
||
<option value="other">Inna</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Numer referencyjny</label>
|
||
<input type="text" name="payment_reference" placeholder="np. numer przelewu">
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Notatki</label>
|
||
<textarea name="notes" rows="2"></textarea>
|
||
</div>
|
||
|
||
<div class="btn-group">
|
||
<button type="button" class="btn btn-secondary" onclick="closePaymentModal()">Anuluj</button>
|
||
<button type="submit" class="btn btn-success">Zarejestruj platnosc</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Universal Confirm Modal -->
|
||
<div class="modal-overlay" id="confirmModal">
|
||
<div class="modal" style="max-width: 420px;">
|
||
<div style="text-align: center; margin-bottom: var(--spacing-lg);">
|
||
<div class="modal-icon" id="confirmModalIcon">❓</div>
|
||
<h3 id="confirmModalTitle" style="margin-bottom: var(--spacing-sm);">Potwierdzenie</h3>
|
||
<p class="modal-description" id="confirmModalMessage"></p>
|
||
</div>
|
||
<div class="modal-actions" style="justify-content: center;">
|
||
<button type="button" class="btn btn-secondary" id="confirmModalCancel">Anuluj</button>
|
||
<button type="button" class="btn btn-primary" id="confirmModalOk">OK</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="toastContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1100; display: flex; flex-direction: column; gap: 10px;"></div>
|
||
|
||
<style>
|
||
.modal-overlay#confirmModal { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 1050; align-items: center; justify-content: center; }
|
||
.modal-overlay#confirmModal.active { display: flex; }
|
||
#confirmModal .modal { background: var(--surface); border-radius: var(--radius-lg); padding: var(--spacing-xl); }
|
||
#confirmModal .modal-icon { font-size: 3em; margin-bottom: var(--spacing-md); }
|
||
#confirmModal .modal-actions { display: flex; gap: var(--spacing-sm); margin-top: var(--spacing-lg); }
|
||
.toast { padding: 12px 20px; border-radius: var(--radius); background: var(--surface); border-left: 4px solid var(--primary); box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px; animation: toastIn 0.3s ease; }
|
||
.toast.success { border-left-color: var(--success); }
|
||
.toast.error { border-left-color: var(--error); }
|
||
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
||
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
// Modal system
|
||
let confirmResolve = null;
|
||
function showConfirm(message, options = {}) {
|
||
return new Promise(resolve => {
|
||
confirmResolve = resolve;
|
||
document.getElementById('confirmModalIcon').textContent = options.icon || '❓';
|
||
document.getElementById('confirmModalTitle').textContent = options.title || 'Potwierdzenie';
|
||
document.getElementById('confirmModalMessage').innerHTML = message;
|
||
document.getElementById('confirmModalOk').textContent = options.okText || 'OK';
|
||
document.getElementById('confirmModalOk').className = 'btn ' + (options.okClass || 'btn-primary');
|
||
document.getElementById('confirmModal').classList.add('active');
|
||
});
|
||
}
|
||
function closeConfirm(result) {
|
||
document.getElementById('confirmModal').classList.remove('active');
|
||
if (confirmResolve) { confirmResolve(result); confirmResolve = null; }
|
||
}
|
||
document.getElementById('confirmModalOk').addEventListener('click', () => closeConfirm(true));
|
||
document.getElementById('confirmModalCancel').addEventListener('click', () => closeConfirm(false));
|
||
document.getElementById('confirmModal').addEventListener('click', e => { if (e.target.id === 'confirmModal') closeConfirm(false); });
|
||
|
||
function showToast(message, type = 'info', duration = 4000) {
|
||
const container = document.getElementById('toastContainer');
|
||
const icons = { success: '✓', error: '✕', warning: '⚠', info: 'ℹ' };
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.innerHTML = `<span style="font-size:1.2em">${icons[type]||'ℹ'}</span><span>${message}</span>`;
|
||
container.appendChild(toast);
|
||
setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration);
|
||
}
|
||
|
||
async function generateFees() {
|
||
const confirmed = await showConfirm('Czy na pewno chcesz wygenerować rekordy składek dla wszystkich firm na wybrany miesiąc?', {
|
||
icon: '📋',
|
||
title: 'Generowanie składek',
|
||
okText: 'Generuj',
|
||
okClass: 'btn-success'
|
||
});
|
||
if (!confirmed) return;
|
||
|
||
const formData = new FormData();
|
||
formData.append('year', {{ year }});
|
||
formData.append('month', {{ month or 'null' }});
|
||
|
||
try {
|
||
const response = await fetch('{{ url_for("admin.admin_fees_generate") }}', {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
}
|
||
});
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
showToast(data.message, 'success');
|
||
setTimeout(() => location.reload(), 1500);
|
||
} else {
|
||
showToast('Błąd: ' + data.error, 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('Błąd: ' + err, 'error');
|
||
}
|
||
}
|
||
|
||
function openPaymentModal(feeId, companyName, amount) {
|
||
document.getElementById('modalFeeId').value = feeId;
|
||
document.getElementById('modalCompanyName').value = companyName;
|
||
document.getElementById('modalAmount').value = amount;
|
||
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
|
||
document.getElementById('paymentModal').classList.add('active');
|
||
}
|
||
|
||
function closePaymentModal() {
|
||
document.getElementById('paymentModal').classList.remove('active');
|
||
}
|
||
|
||
document.getElementById('paymentForm').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
|
||
const feeId = document.getElementById('modalFeeId').value;
|
||
const formData = new FormData(this);
|
||
|
||
try {
|
||
const response = await fetch('/admin/fees/' + feeId + '/mark-paid', {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
}
|
||
});
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
closePaymentModal();
|
||
showToast(data.message, 'success');
|
||
setTimeout(() => location.reload(), 1500);
|
||
} else {
|
||
showToast('Błąd: ' + data.error, 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('Błąd: ' + err, 'error');
|
||
}
|
||
});
|
||
|
||
function toggleSelectAll() {
|
||
const selectAll = document.getElementById('selectAll');
|
||
const checkboxes = document.querySelectorAll('.fee-checkbox:not(:disabled)');
|
||
checkboxes.forEach(cb => cb.checked = selectAll.checked);
|
||
}
|
||
|
||
async function bulkMarkPaid() {
|
||
const checkboxes = document.querySelectorAll('.fee-checkbox:checked');
|
||
if (checkboxes.length === 0) {
|
||
showToast('Zaznacz przynajmniej jedną składkę', 'warning');
|
||
return;
|
||
}
|
||
|
||
const confirmed = await showConfirm(`Czy na pewno chcesz oznaczyć <strong>${checkboxes.length}</strong> składek jako opłacone?`, {
|
||
icon: '💰',
|
||
title: 'Oznaczanie płatności',
|
||
okText: 'Oznacz',
|
||
okClass: 'btn-success'
|
||
});
|
||
if (!confirmed) return;
|
||
|
||
const formData = new FormData();
|
||
checkboxes.forEach(cb => formData.append('fee_ids[]', cb.value));
|
||
|
||
try {
|
||
const response = await fetch('{{ url_for("admin.admin_fees_bulk_mark_paid") }}', {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'X-CSRFToken': '{{ csrf_token() }}'
|
||
}
|
||
});
|
||
const data = await response.json();
|
||
if (data.success) {
|
||
showToast(data.message, 'success');
|
||
setTimeout(() => location.reload(), 1500);
|
||
} else {
|
||
showToast('Błąd: ' + data.error, 'error');
|
||
}
|
||
} catch (err) {
|
||
showToast('Błąd: ' + err, 'error');
|
||
}
|
||
}
|
||
|
||
// Close modal on outside click
|
||
document.getElementById('paymentModal').addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
closePaymentModal();
|
||
}
|
||
});
|
||
{% endblock %}
|