New layout for absences

This commit is contained in:
Zan1456
2025-08-06 22:56:12 +02:00
parent 1218d3b905
commit d722740bdf
4 changed files with 291 additions and 107 deletions

View File

@@ -239,66 +239,200 @@ body {
}
.absences-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.absence-group {
background: var(--card-card);
border-radius: 24px;
overflow: hidden;
animation: fadeIn 0.3s ease;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
}
.absence-date {
padding: 16px;
color: var(--text-primary);
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.absence-count {
margin-left: auto;
background: var(--accent-accent);
color: var(--button-secondaryFill);
padding: 4px 8px;
border-radius: 12px;
font-size: 14px;
}
.absence-list {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.absence-item {
.stats-overview {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
padding: 12px;
background: var(--accent-15);
border-radius: 12px;
margin-bottom: 24px;
}
.stat-card {
background: var(--card-card);
border-radius: 16px;
padding: 20px;
text-align: center;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
transition: transform 0.2s ease;
}
.absence-item:hover {
transform: translateX(4px);
.stat-card:hover {
transform: translateY(-2px);
}
.absence-time {
display: flex;
align-items: center;
gap: 4px;
.stat-number {
font-size: 32px;
font-weight: 700;
color: var(--accent-accent);
margin-bottom: 8px;
}
.stat-label {
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
}
.absences-container {
background: var(--card-card);
border-radius: 16px;
overflow: hidden;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
}
.absences-table {
width: 100%;
border-collapse: collapse;
}
.table-header {
background: var(--accent-15);
border-bottom: 1px solid var(--accent-30);
}
.table-header th {
padding: 16px;
text-align: left;
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.table-row {
border-bottom: 1px solid var(--accent-15);
transition: background-color 0.2s ease;
}
.table-row:hover {
background: var(--accent-10);
}
.table-row:last-child {
border-bottom: none;
}
.table-cell {
padding: 16px;
color: var(--text-primary);
vertical-align: middle;
}
.date-cell {
font-weight: 600;
color: var(--accent-accent);
}
.lesson-cell {
text-align: center;
font-weight: 500;
}
.subject-cell {
font-weight: 600;
}
.topic-cell {
color: var(--text-secondary);
font-size: 14px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.status-cell {
text-align: center;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-badge.justified {
background: var(--grades-4-bg);
color: var(--grades-4);
}
.status-badge.unjustified {
background: var(--grades-1-bg);
color: var(--grades-1);
}
.status-badge.pending {
background: var(--grades-3-bg);
color: var(--grades-3);
}
@media (max-width: 768px) {
.absences-table {
font-size: 14px;
}
.table-header th,
.table-cell {
padding: 12px 8px;
}
.topic-cell {
max-width: 120px;
}
.stats-overview {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.absences-table,
.table-header,
.table-row {
display: block;
}
.table-header {
display: none;
}
.table-row {
margin-bottom: 16px;
border: 1px solid var(--accent-15);
border-radius: 12px;
padding: 16px;
background: var(--card-card);
}
.table-cell {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--accent-15);
}
.table-cell:last-child {
border-bottom: none;
}
.table-cell::before {
content: attr(data-label);
font-weight: 600;
color: var(--text-secondary);
font-size: 12px;
text-transform: uppercase;
}
.stats-overview {
grid-template-columns: 1fr;
}
}
.absence-details {

View File

@@ -116,39 +116,67 @@ async function transformAbsencesPage() {
</div>
</div>
<div class="stats-overview">
<div class="stat-card">
<div class="stat-number">${absences.length}</div>
<div class="stat-label">${LanguageManager.t('absences.total_absences')}</div>
</div>
<div class="stat-card">
<div class="stat-number">${absences.filter(a => a.justificationStatus === 'justified').length}</div>
<div class="stat-label">${LanguageManager.t('absences.justified')}</div>
</div>
<div class="stat-card">
<div class="stat-number">${absences.filter(a => a.justificationStatus === 'unjustified').length}</div>
<div class="stat-label">${LanguageManager.t('absences.unjustified')}</div>
</div>
<div class="stat-card">
<div class="stat-number">${absences.filter(a => a.justificationStatus === 'pending').length}</div>
<div class="stat-label">${LanguageManager.t('absences.pending')}</div>
</div>
</div>
<div class="absences-container">
${Object.entries(groupedAbsences).map(([date, dayAbsences]) => `
<div class="absence-group" data-date="${date}">
<div class="absence-date">
<span class="material-icons-round">event</span>
${date}
<span class="absence-count">${dayAbsences.length} ${LanguageManager.t('absences.hours')}</span>
</div>
<div class="absence-list">
${dayAbsences.map(absence => `
<div class="absence-item"
data-subject="${absence.subject}"
data-justified="${absence.justified}">
<div class="absence-time">
<span class="material-icons-round">schedule</span>
${absence.lesson}. ${LanguageManager.t('absences.lesson').toLowerCase()}
</div>
<div class="absence-details">
<div class="absence-subject">${absence.subject}</div>
<div class="absence-topic">${absence.topic}</div>
</div>
<div class="absence-status ${absence.justificationStatus}">
<table class="absences-table">
<thead class="table-header">
<tr>
<th>${LanguageManager.t('absences.date')}</th>
<th>${LanguageManager.t('absences.lesson')}</th>
<th>${LanguageManager.t('absences.subject')}</th>
<th>${LanguageManager.t('absences.topic')}</th>
<th>${LanguageManager.t('absences.status')}</th>
</tr>
</thead>
<tbody>
${absences.map(absence => `
<tr class="table-row"
data-subject="${absence.subject}"
data-justified="${absence.justified}"
data-date="${absence.date}">
<td class="table-cell date-cell" data-label="${LanguageManager.t('absences.date')}">
${absence.date}
</td>
<td class="table-cell lesson-cell" data-label="${LanguageManager.t('absences.lesson')}">
${absence.lesson}.
</td>
<td class="table-cell subject-cell" data-label="${LanguageManager.t('absences.subject')}">
${absence.subject}
</td>
<td class="table-cell topic-cell" data-label="${LanguageManager.t('absences.topic')}" title="${absence.topic}">
${absence.topic}
</td>
<td class="table-cell status-cell" data-label="${LanguageManager.t('absences.status')}">
<span class="status-badge ${absence.justificationStatus}">
${absence.justificationStatus === 'justified' ?
`${LanguageManager.t('absences.justified')} <span class="material-icons-round">check_circle</span>` :
`<span class="material-icons-round">check_circle</span> ${LanguageManager.t('absences.justified')}` :
absence.justificationStatus === 'unjustified' ?
`${LanguageManager.t('absences.unjustified')} <span class="material-icons-round">cancel</span>` :
`${LanguageManager.t('absences.pending')} <span class="material-icons-round">pending</span>`}
</div>
</div>
`).join('')}
</div>
</div>
`).join('')}
`<span class="material-icons-round">cancel</span> ${LanguageManager.t('absences.unjustified')}` :
`<span class="material-icons-round">pending</span> ${LanguageManager.t('absences.pending')}`}
</span>
</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</main>
</div>
@@ -188,8 +216,8 @@ function setupFilters() {
const justified = filters.justified.value;
const selectedDate = dateFilterValue ? new Date(dateFilterValue) : null;
document.querySelectorAll('.absence-group').forEach(group => {
const dateStr = group.dataset.date;
document.querySelectorAll('.table-row').forEach(row => {
const dateStr = row.dataset.date;
const dateParts = dateStr.split('.');
if (dateParts.length < 3) {
@@ -206,37 +234,32 @@ function setupFilters() {
return;
}
const groupDate = new Date(parsedYear, parsedMonth, parsedDay);
const rowDate = new Date(parsedYear, parsedMonth, parsedDay);
let showGroup = true;
let showRow = true;
if (selectedDate) {
if (groupDate.getFullYear() !== selectedDate.getFullYear() ||
groupDate.getMonth() !== selectedDate.getMonth() ||
groupDate.getDate() !== selectedDate.getDate()) {
showGroup = false;
if (rowDate.getFullYear() !== selectedDate.getFullYear() ||
rowDate.getMonth() !== selectedDate.getMonth() ||
rowDate.getDate() !== selectedDate.getDate()) {
showRow = false;
}
}
const absenceItems = group.querySelectorAll('.absence-item');
let visibleItems = 0;
if (subject && row.dataset.subject !== subject) {
showRow = false;
}
absenceItems.forEach(item => {
let showItem = true;
if (subject && item.dataset.subject !== subject) showItem = false;
if (justified) {
const statusElement = item.querySelector('.absence-status');
const hasStatus = statusElement.classList.contains(justified);
if (!hasStatus) showItem = false;
}
if (justified) {
const statusElement = row.querySelector('.status-badge');
const hasStatus = statusElement.classList.contains(justified);
if (!hasStatus) showRow = false;
}
item.style.display = showItem ? '' : 'none';
if (showItem) visibleItems++;
});
group.style.display = (showGroup && visibleItems > 0) ? '' : 'none';
row.style.display = showRow ? '' : 'none';
});
updateStatistics();
} catch (err) {
console.error('Error during filtering absences:', err);
@@ -270,6 +293,27 @@ function setupFilters() {
}
}
function updateStatistics() {
try {
const visibleRows = document.querySelectorAll('.table-row:not([style*="display: none"])');
const totalVisible = visibleRows.length;
const justifiedVisible = Array.from(visibleRows).filter(row =>
row.querySelector('.status-badge.justified')).length;
const unjustifiedVisible = Array.from(visibleRows).filter(row =>
row.querySelector('.status-badge.unjustified')).length;
const pendingVisible = Array.from(visibleRows).filter(row =>
row.querySelector('.status-badge.pending')).length;
const statCards = document.querySelectorAll('.stat-card');
if (statCards[0]) statCards[0].querySelector('.stat-number').textContent = totalVisible;
if (statCards[1]) statCards[1].querySelector('.stat-number').textContent = justifiedVisible;
if (statCards[2]) statCards[2].querySelector('.stat-number').textContent = unjustifiedVisible;
if (statCards[3]) statCards[3].querySelector('.stat-number').textContent = pendingVisible;
} catch (err) {
console.error('Error updating statistics:', err);
}
}
if (window.location.href.includes('/Hianyzas/Hianyzasok')) {
transformAbsencesPage().catch(error => {

View File

@@ -155,7 +155,10 @@
"current_month": "Current month",
"last_month": "Last month",
"current_semester": "Current semester",
"last_30_days": "Last 30 days"
"last_30_days": "Last 30 days",
"total_absences": "Total absences",
"topic": "Topic",
"status": "Status"
},
"profile": {
"title": "Profile",

View File

@@ -155,7 +155,10 @@
"current_month": "Aktuális hónap",
"last_month": "Előző hónap",
"current_semester": "Aktuális félév",
"last_30_days": "Utolsó 30 nap"
"last_30_days": "Utolsó 30 nap",
"total_absences": "Összes hiányzás",
"topic": "Téma",
"status": "Állapot"
},
"profile": {
"title": "Profil",