feat: Add sortable columns and PKD display in KRS Audit panel
- All columns now sortable (click header to sort asc/desc) - PKD codes displayed with primary code highlighted (★) - Show first 2 PKD codes, click '+N more' for tooltip with all - Backend returns full PKD codes list instead of just count Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bd23238c1a
commit
4e4b3f4ef0
12
app.py
12
app.py
@ -8619,10 +8619,11 @@ def admin_krs_audit():
|
||||
KRSAudit.company_id == company.id
|
||||
).order_by(KRSAudit.audit_date.desc()).first()
|
||||
|
||||
# Get PKD codes count
|
||||
pkd_count = db.query(CompanyPKD).filter(
|
||||
# Get PKD codes (all)
|
||||
pkd_codes = db.query(CompanyPKD).filter(
|
||||
CompanyPKD.company_id == company.id
|
||||
).count()
|
||||
).order_by(CompanyPKD.is_primary.desc(), CompanyPKD.pkd_code).all()
|
||||
pkd_count = len(pkd_codes)
|
||||
|
||||
# Get people count
|
||||
people_count = db.query(CompanyPerson).filter(
|
||||
@ -8640,6 +8641,11 @@ def admin_krs_audit():
|
||||
'krs_pdf_path': company.krs_pdf_path,
|
||||
'audit': latest_audit,
|
||||
'pkd_count': pkd_count,
|
||||
'pkd_codes': [{
|
||||
'code': pkd.pkd_code,
|
||||
'description': pkd.pkd_description,
|
||||
'is_primary': pkd.is_primary
|
||||
} for pkd in pkd_codes],
|
||||
'people_count': people_count,
|
||||
'capital_shares_count': company.capital_shares_count
|
||||
})
|
||||
|
||||
@ -217,6 +217,33 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.krs-table th.sortable {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.krs-table th.sortable:hover {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.krs-table th.sortable::after {
|
||||
content: '⇅';
|
||||
margin-left: 6px;
|
||||
opacity: 0.4;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.krs-table th.sortable.sort-asc::after {
|
||||
content: '↑';
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.krs-table th.sortable.sort-desc::after {
|
||||
content: '↓';
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.krs-table tbody tr:hover {
|
||||
background: var(--background);
|
||||
}
|
||||
@ -301,6 +328,93 @@
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* PKD display */
|
||||
.pkd-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.pkd-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pkd-badge.primary {
|
||||
background: var(--primary-light, #dbeafe);
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pkd-badge.secondary {
|
||||
background: var(--background);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.pkd-more {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pkd-more:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.pkd-tooltip {
|
||||
position: absolute;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--spacing-sm);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 100;
|
||||
max-width: 350px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pkd-tooltip.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pkd-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pkd-item {
|
||||
font-size: 12px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.pkd-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.pkd-item .pkd-code {
|
||||
font-family: monospace;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.pkd-item.primary .pkd-code {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.pkd-item .pkd-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
@ -529,13 +643,13 @@
|
||||
<table class="krs-table" id="krsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Firma</th>
|
||||
<th>KRS</th>
|
||||
<th class="hide-mobile">Kapital</th>
|
||||
<th class="hide-mobile">Zarzad</th>
|
||||
<th class="hide-mobile">PKD</th>
|
||||
<th>Status</th>
|
||||
<th>Ostatni audyt</th>
|
||||
<th class="sortable" data-sort="name">Firma</th>
|
||||
<th class="sortable" data-sort="krs">KRS</th>
|
||||
<th class="sortable hide-mobile" data-sort="capital">Kapital</th>
|
||||
<th class="sortable hide-mobile" data-sort="zarzad">Zarzad</th>
|
||||
<th class="sortable hide-mobile" data-sort="pkd">PKD</th>
|
||||
<th class="sortable" data-sort="status">Status</th>
|
||||
<th class="sortable" data-sort="date">Ostatni audyt</th>
|
||||
<th>Akcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -544,7 +658,11 @@
|
||||
<tr data-company-id="{{ company.id }}"
|
||||
data-name="{{ company.name|lower }}"
|
||||
data-krs="{{ company.krs }}"
|
||||
data-status="{{ 'audited' if company.krs_last_audit_at else 'pending' }}">
|
||||
data-capital="{{ company.capital_amount|default(0, true) }}"
|
||||
data-zarzad="{{ company.people_count|default(0, true) }}"
|
||||
data-pkd="{{ company.pkd_count|default(0, true) }}"
|
||||
data-status="{{ 'audited' if company.krs_last_audit_at else 'pending' }}"
|
||||
data-date="{{ company.krs_last_audit_at.strftime('%Y%m%d') if company.krs_last_audit_at else '00000000' }}">
|
||||
<td class="company-name-cell">
|
||||
<a href="{{ url_for('company_detail', company_id=company.id) }}">{{ company.name }}</a>
|
||||
</td>
|
||||
@ -566,8 +684,19 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="data-cell hide-mobile">
|
||||
{% if company.pkd_count > 0 %}
|
||||
<span class="data-value">{{ company.pkd_count }}</span>
|
||||
{% if company.pkd_codes %}
|
||||
<div class="pkd-container">
|
||||
{% for pkd in company.pkd_codes %}
|
||||
{% if pkd.is_primary %}
|
||||
<span class="pkd-badge primary" title="{{ pkd.description }}">★ {{ pkd.code }}</span>
|
||||
{% elif loop.index <= 2 %}
|
||||
<span class="pkd-badge secondary" title="{{ pkd.description }}">{{ pkd.code }}</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if company.pkd_count > 2 %}
|
||||
<span class="pkd-more" onclick="showPkdTooltip(this, {{ company.pkd_codes | tojson | safe }})">+{{ company.pkd_count - 2 }} więcej</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
@ -680,6 +809,138 @@
|
||||
const csrfToken = '{{ csrf_token() }}';
|
||||
let pendingModalAction = null;
|
||||
let auditInProgress = false;
|
||||
let currentSort = { column: null, direction: 'asc' };
|
||||
let activePkdTooltip = null;
|
||||
|
||||
// === TABLE SORTING ===
|
||||
function initTableSorting() {
|
||||
const headers = document.querySelectorAll('.krs-table th.sortable');
|
||||
headers.forEach(header => {
|
||||
header.addEventListener('click', () => {
|
||||
const sortKey = header.dataset.sort;
|
||||
sortTable(sortKey, header);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sortTable(sortKey, headerElement) {
|
||||
const tbody = document.getElementById('krsTableBody');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
|
||||
// Determine sort direction
|
||||
let direction = 'asc';
|
||||
if (currentSort.column === sortKey && currentSort.direction === 'asc') {
|
||||
direction = 'desc';
|
||||
}
|
||||
|
||||
// Update header classes
|
||||
document.querySelectorAll('.krs-table th.sortable').forEach(th => {
|
||||
th.classList.remove('sort-asc', 'sort-desc');
|
||||
});
|
||||
headerElement.classList.add(direction === 'asc' ? 'sort-asc' : 'sort-desc');
|
||||
|
||||
// Sort rows
|
||||
rows.sort((a, b) => {
|
||||
let valA, valB;
|
||||
|
||||
switch (sortKey) {
|
||||
case 'name':
|
||||
valA = a.dataset.name || '';
|
||||
valB = b.dataset.name || '';
|
||||
break;
|
||||
case 'krs':
|
||||
valA = a.dataset.krs || '';
|
||||
valB = b.dataset.krs || '';
|
||||
break;
|
||||
case 'capital':
|
||||
valA = parseFloat(a.dataset.capital) || 0;
|
||||
valB = parseFloat(b.dataset.capital) || 0;
|
||||
break;
|
||||
case 'zarzad':
|
||||
valA = parseInt(a.dataset.zarzad) || 0;
|
||||
valB = parseInt(b.dataset.zarzad) || 0;
|
||||
break;
|
||||
case 'pkd':
|
||||
valA = parseInt(a.dataset.pkd) || 0;
|
||||
valB = parseInt(b.dataset.pkd) || 0;
|
||||
break;
|
||||
case 'status':
|
||||
valA = a.dataset.status === 'audited' ? 1 : 0;
|
||||
valB = b.dataset.status === 'audited' ? 1 : 0;
|
||||
break;
|
||||
case 'date':
|
||||
valA = a.dataset.date || '00000000';
|
||||
valB = b.dataset.date || '00000000';
|
||||
break;
|
||||
default:
|
||||
valA = '';
|
||||
valB = '';
|
||||
}
|
||||
|
||||
// Compare
|
||||
if (typeof valA === 'number' && typeof valB === 'number') {
|
||||
return direction === 'asc' ? valA - valB : valB - valA;
|
||||
} else {
|
||||
const comparison = String(valA).localeCompare(String(valB));
|
||||
return direction === 'asc' ? comparison : -comparison;
|
||||
}
|
||||
});
|
||||
|
||||
// Re-append sorted rows
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
|
||||
// Update current sort state
|
||||
currentSort = { column: sortKey, direction };
|
||||
}
|
||||
|
||||
// === PKD TOOLTIP ===
|
||||
function showPkdTooltip(element, pkdCodes) {
|
||||
// Close existing tooltip
|
||||
if (activePkdTooltip) {
|
||||
activePkdTooltip.remove();
|
||||
activePkdTooltip = null;
|
||||
}
|
||||
|
||||
// Create tooltip
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'pkd-tooltip active';
|
||||
|
||||
let html = '<div class="pkd-list">';
|
||||
pkdCodes.forEach(pkd => {
|
||||
html += `<div class="pkd-item ${pkd.is_primary ? 'primary' : ''}">
|
||||
<span class="pkd-code">${pkd.is_primary ? '★ ' : ''}${pkd.code}</span>
|
||||
<div class="pkd-desc">${pkd.description || '-'}</div>
|
||||
</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
tooltip.innerHTML = html;
|
||||
|
||||
// Position tooltip
|
||||
const rect = element.getBoundingClientRect();
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.top = (rect.bottom + 5) + 'px';
|
||||
tooltip.style.left = rect.left + 'px';
|
||||
|
||||
// Add to document
|
||||
document.body.appendChild(tooltip);
|
||||
activePkdTooltip = tooltip;
|
||||
|
||||
// Close on click outside
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closePkdTooltipOnClickOutside);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function closePkdTooltipOnClickOutside(e) {
|
||||
if (activePkdTooltip && !activePkdTooltip.contains(e.target) && !e.target.classList.contains('pkd-more')) {
|
||||
activePkdTooltip.remove();
|
||||
activePkdTooltip = null;
|
||||
document.removeEventListener('click', closePkdTooltipOnClickOutside);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sorting on page load
|
||||
document.addEventListener('DOMContentLoaded', initTableSorting);
|
||||
|
||||
// Modal functions
|
||||
function showModal(title, body, onConfirm) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user