mirror of
https://github.com/QwIT-Development/firka-extension.git
synced 2026-06-12 03:41:39 +02:00
Absences rework
This commit is contained in:
@@ -29,131 +29,50 @@ body {
|
||||
}
|
||||
|
||||
.absences-page {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-areas:
|
||||
"sidebar content";
|
||||
gap: 24px;
|
||||
min-height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.absences-sidebar {
|
||||
grid-area: sidebar;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
position: sticky;
|
||||
top: 24px;
|
||||
height: fit-content;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
.stats-section {
|
||||
background: var(--card-card);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
|
||||
}
|
||||
|
||||
.filter-header {
|
||||
.stats-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
background-color: #00000000 !important;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-group label img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
padding: 12px 16px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 12px;
|
||||
background: var(--button-secondaryFill);
|
||||
color: var(--text-primary);
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter-input:hover {
|
||||
background: var(--accent-15);
|
||||
}
|
||||
|
||||
.filter-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-accent);
|
||||
background: var(--button-secondaryFill);
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
grid-area: stats;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--card-card);
|
||||
background: var(--button-secondaryFill);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
padding: 20px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
min-height: 100px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-bottom: 8px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
@@ -166,18 +85,46 @@ body {
|
||||
color: var(--accent-accent);
|
||||
}
|
||||
|
||||
.stat-card.total {
|
||||
border-color: var(--accent-15);
|
||||
}
|
||||
|
||||
.stat-card.absence .stat-number {
|
||||
color: var(--accent-accent);
|
||||
}
|
||||
|
||||
.stat-card.late .stat-number {
|
||||
color: var(--grades-2);
|
||||
}
|
||||
|
||||
.stat-card.late {
|
||||
border-color: var(--grades-background-2);
|
||||
}
|
||||
|
||||
.stat-card.justified .stat-number {
|
||||
color: var(--grades-4);
|
||||
}
|
||||
|
||||
.stat-card.justified {
|
||||
border-color: var(--grades-background-4);
|
||||
}
|
||||
|
||||
.stat-card.unjustified .stat-number {
|
||||
color: var(--grades-1);
|
||||
}
|
||||
|
||||
.stat-card.unjustified {
|
||||
border-color: var(--grades-background-1);
|
||||
}
|
||||
|
||||
.stat-card.pending .stat-number {
|
||||
color: var(--grades-3);
|
||||
}
|
||||
|
||||
.stat-card.pending {
|
||||
border-color: var(--grades-background-3);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
@@ -186,163 +133,369 @@ body {
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.absences-content {
|
||||
grid-area: content;
|
||||
.calendar-legend {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding: 16px 24px;
|
||||
background: var(--card-card);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
|
||||
}
|
||||
|
||||
.day-group {
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.legend-color.status-justified {
|
||||
background: var(--grades-background-4);
|
||||
border: 2px solid var(--grades-4);
|
||||
}
|
||||
|
||||
.legend-color.status-pending {
|
||||
background: var(--grades-background-3);
|
||||
border: 2px solid var(--grades-3);
|
||||
}
|
||||
|
||||
.legend-color.status-unjustified {
|
||||
background: var(--grades-background-1);
|
||||
border: 2px solid var(--grades-1);
|
||||
}
|
||||
|
||||
.legend-color.type-late-legend {
|
||||
background: var(--grades-background-2);
|
||||
border: 2px dashed var(--grades-2);
|
||||
}
|
||||
|
||||
.legend-item span {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.calendar-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.month-container {
|
||||
background: var(--card-card);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
|
||||
}
|
||||
|
||||
.day-header {
|
||||
.month-header {
|
||||
background: var(--button-secondaryFill);
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.calendar-day-header {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
padding: 8px 0;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
background: var(--button-secondaryFill);
|
||||
position: relative;
|
||||
min-height: 40px;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.calendar-day.empty {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.calendar-day.future {
|
||||
background: var(--accent-15);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.calendar-day.future .day-number {
|
||||
color: var(--text-teritary);
|
||||
}
|
||||
|
||||
.calendar-day.today .day-number {
|
||||
color: var(--accent-accent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calendar-day .day-number {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.calendar-day.has-absence {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.calendar-day.has-absence:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 12px var(--accent-shadow);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.calendar-day.status-justified {
|
||||
background: var(--grades-background-4);
|
||||
border: 2px solid var(--grades-4);
|
||||
}
|
||||
|
||||
.calendar-day.status-justified .day-number {
|
||||
color: var(--grades-4);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calendar-day.status-pending {
|
||||
background: var(--grades-background-3);
|
||||
border: 2px solid var(--grades-3);
|
||||
}
|
||||
|
||||
.calendar-day.status-pending .day-number {
|
||||
color: var(--grades-3);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calendar-day.status-unjustified {
|
||||
background: var(--grades-background-1);
|
||||
border: 2px solid var(--grades-1);
|
||||
}
|
||||
|
||||
.calendar-day.status-unjustified .day-number {
|
||||
color: var(--grades-1);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.calendar-day.type-late {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.calendar-day.type-mixed {
|
||||
border-style: double;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.absence-count {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
background: var(--accent-accent);
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.calendar-day.status-justified .absence-count {
|
||||
background: var(--grades-4);
|
||||
}
|
||||
|
||||
.calendar-day.status-pending .absence-count {
|
||||
background: var(--grades-3);
|
||||
}
|
||||
|
||||
.calendar-day.status-unjustified .absence-count {
|
||||
background: var(--grades-1);
|
||||
}
|
||||
|
||||
.absence-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.absence-modal-overlay.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.absence-modal {
|
||||
background: var(--card-card);
|
||||
border-radius: 24px;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
transform: scale(0.95);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.absence-modal-overlay.visible .absence-modal {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
background: var(--button-secondaryFill);
|
||||
border-bottom: 1px solid #00000000 !important;
|
||||
color: white;
|
||||
padding: 14px 18px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.day-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
.modal-header h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.day-date img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
filter: brightness(0) invert(1);
|
||||
.modal-content {
|
||||
background-color: var(--card-card) !important;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.day-count {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.day-absences {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.absence-card {
|
||||
.modal-absence-card {
|
||||
background: var(--button-secondaryFill);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr minmax(auto, max-content);
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"lesson subject status"
|
||||
"lesson topic status";
|
||||
gap: 8px 12px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border: 1px solid var(--accent-15);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.absence-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px var(--accent-shadow);
|
||||
}
|
||||
|
||||
.absence-lesson {
|
||||
grid-area: lesson;
|
||||
.modal-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--accent-15);
|
||||
border-radius: 12px;
|
||||
font-size: 20px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.modal-lesson {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: var(--accent-accent);
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.type-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px 1px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.type-indicator.absence {
|
||||
background: var(--accent-15);
|
||||
color: var(--accent-accent);
|
||||
}
|
||||
|
||||
.absence-subject {
|
||||
grid-area: subject;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
align-self: end;
|
||||
.type-indicator.late {
|
||||
background: var(--grades-background-2);
|
||||
color: var(--grades-2);
|
||||
}
|
||||
|
||||
.absence-topic {
|
||||
grid-area: topic;
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
align-self: start;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.absence-status {
|
||||
grid-area: status;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
.modal-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 24px;
|
||||
font-size: 11px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.2px;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.status-badge img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.status-badge.justified {
|
||||
.modal-status-badge.justified {
|
||||
background: var(--grades-background-4);
|
||||
color: var(--grades-4);
|
||||
}
|
||||
|
||||
.status-badge.justified img {
|
||||
filter: brightness(0) saturate(100%) invert(76%) sepia(43%) saturate(609%) hue-rotate(53deg) brightness(101%) contrast(92%);
|
||||
}
|
||||
|
||||
.status-badge.unjustified {
|
||||
background: var(--grades-background-1);
|
||||
color: var(--grades-1);
|
||||
}
|
||||
|
||||
.status-badge.unjustified .material-icons-round {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.status-badge.pending {
|
||||
.modal-status-badge.pending {
|
||||
background: var(--grades-background-3);
|
||||
color: var(--grades-3);
|
||||
}
|
||||
|
||||
.status-badge.pending .material-icons-round {
|
||||
font-size: 16px;
|
||||
.modal-status-badge.unjustified {
|
||||
background: var(--grades-background-1);
|
||||
color: var(--grades-1);
|
||||
}
|
||||
|
||||
.modal-subject {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.modal-info-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.modal-info-item {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.modal-info-item:not(:last-child)::after {
|
||||
content: '•';
|
||||
margin-left: 6px;
|
||||
color: var(--text-teritary);
|
||||
}
|
||||
|
||||
.modal-minutes {
|
||||
color: var(--grades-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
@@ -375,14 +528,9 @@ body {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.absences-page {
|
||||
grid-template-columns: 260px 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.day-absences {
|
||||
grid-template-columns: 1fr;
|
||||
@media (max-width: 1200px) {
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,41 +543,18 @@ body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.absences-page {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas:
|
||||
"sidebar"
|
||||
"content";
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.absences-sidebar {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
.stats-section {
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
.stats-title {
|
||||
font-size: 18px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -447,33 +572,78 @@ body {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.day-group {
|
||||
.calendar-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.calendar-legend {
|
||||
gap: 12px;
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.legend-item span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.month-container {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
padding: 14px 18px;
|
||||
.month-header {
|
||||
padding: 14px 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
gap: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.calendar-day {
|
||||
min-height: 36px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.calendar-day .day-number {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.absence-count {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.absence-modal {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: 12px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.modal-absence-card {
|
||||
padding: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.modal-subject {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.day-absences {
|
||||
padding: 14px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.absence-card {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.absence-lesson {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.absence-subject {
|
||||
font-size: 15px;
|
||||
.modal-info-row {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,26 +653,13 @@ body {
|
||||
}
|
||||
|
||||
.absences-page {
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-card {
|
||||
.stats-section {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.filter-header {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter-header h2 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.filter-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
@@ -523,63 +680,35 @@ body {
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
padding: 12px 16px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.day-date {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.day-count {
|
||||
font-size: 12px;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
|
||||
.day-absences {
|
||||
padding: 12px;
|
||||
.calendar-legend {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.absence-card {
|
||||
padding: 14px;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-areas:
|
||||
"lesson subject"
|
||||
"lesson topic"
|
||||
"status status";
|
||||
gap: 6px 12px;
|
||||
.calendar-grid {
|
||||
gap: 2px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.absence-lesson {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
font-size: 16px;
|
||||
grid-row: span 2;
|
||||
.calendar-day {
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.absence-status {
|
||||
justify-content: flex-start;
|
||||
margin-top: 8px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--accent-15);
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 6px 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.absence-subject {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.absence-topic {
|
||||
.calendar-day .day-number {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.absence-modal-overlay {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.modal-absence-card {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.modal-subject {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
@@ -593,17 +722,15 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.day-group {
|
||||
.month-container {
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.day-group:nth-child(1) { animation-delay: 0.05s; }
|
||||
.day-group:nth-child(2) { animation-delay: 0.1s; }
|
||||
.day-group:nth-child(3) { animation-delay: 0.15s; }
|
||||
.day-group:nth-child(4) { animation-delay: 0.2s; }
|
||||
.day-group:nth-child(5) { animation-delay: 0.25s; }
|
||||
|
||||
|
||||
.month-container:nth-child(1) { animation-delay: 0.05s; }
|
||||
.month-container:nth-child(2) { animation-delay: 0.1s; }
|
||||
.month-container:nth-child(3) { animation-delay: 0.15s; }
|
||||
.month-container:nth-child(4) { animation-delay: 0.2s; }
|
||||
.month-container:nth-child(5) { animation-delay: 0.25s; }
|
||||
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
|
||||
@@ -15,7 +15,7 @@ async function collectAbsencesData() {
|
||||
try {
|
||||
const currentDomain = window.location.hostname;
|
||||
const response = await fetch(
|
||||
`https://${currentDomain}/api/HianyzasokApi/GetHianyzasGrid?sort=MulasztasDatum-desc&page=1&pageSize=100&group=&filter=&data=%7B%7D&_=${Date.now()}`,
|
||||
`https://${currentDomain}/api/HianyzasokApi/GetHianyzasGrid?sort=MulasztasDatum-desc&page=1&pageSize=500&group=&filter=&data=%7B%7D&_=${Date.now()}`,
|
||||
{
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
@@ -45,178 +45,77 @@ async function collectAbsencesData() {
|
||||
justificationStatus = "unjustified";
|
||||
}
|
||||
|
||||
const absenceType = item.MulasztasTipus === 1500 ? 'absence' : item.MulasztasTipus === 1499 ? 'late' : 'other';
|
||||
|
||||
absences.push({
|
||||
date: formattedDate,
|
||||
rawDate: date,
|
||||
dateKey: `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`,
|
||||
lesson: item.Oraszam?.toString() || "",
|
||||
subject: item.Targy || "",
|
||||
topic: item.Tema || "",
|
||||
type: item.MulasztasTipus_DNAME || "",
|
||||
absenceType: absenceType,
|
||||
absenceTypeId: item.MulasztasTipus,
|
||||
justified: item.Igazolt_BOOL === true,
|
||||
justificationStatus: justificationStatus,
|
||||
purposeful: item.TanoraiCeluMulasztas_BNAME || "",
|
||||
justificationType: item.IgazolasTipus_DNAME || "",
|
||||
minutes: item.KesesPercben || 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const groupedAbsences = absences.reduce((groups, absence) => {
|
||||
const date = absence.date;
|
||||
if (!groups[date]) {
|
||||
groups[date] = [];
|
||||
const groupedByDate = absences.reduce((groups, absence) => {
|
||||
const key = absence.dateKey;
|
||||
if (!groups[key]) {
|
||||
groups[key] = [];
|
||||
}
|
||||
groups[date].push(absence);
|
||||
groups[key].push(absence);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
|
||||
Object.keys(groupedAbsences).forEach(date => {
|
||||
groupedAbsences[date].sort((a, b) => parseInt(a.lesson) - parseInt(b.lesson));
|
||||
Object.keys(groupedByDate).forEach(date => {
|
||||
groupedByDate[date].sort((a, b) => parseInt(a.lesson) - parseInt(b.lesson));
|
||||
});
|
||||
|
||||
return { basicData, absences, groupedAbsences };
|
||||
return { basicData, absences, groupedByDate };
|
||||
} catch (error) {
|
||||
console.error("Hiba az API hívás során:", error);
|
||||
return { basicData, absences: [], groupedAbsences: {} };
|
||||
return { basicData, absences: [], groupedByDate: {} };
|
||||
}
|
||||
}
|
||||
|
||||
function createFilterCard(absences) {
|
||||
const filterCard = document.createElement('div');
|
||||
filterCard.className = 'filter-card';
|
||||
|
||||
const filterHeader = document.createElement('div');
|
||||
filterHeader.className = 'filter-header';
|
||||
const h2 = document.createElement('h2');
|
||||
h2.textContent = LanguageManager.t('absences.filter_title');
|
||||
filterHeader.appendChild(h2);
|
||||
|
||||
const filterContent = document.createElement('div');
|
||||
filterContent.className = 'filter-content';
|
||||
|
||||
const dateGroup = document.createElement('div');
|
||||
dateGroup.className = 'filter-group';
|
||||
|
||||
const dateLabel = document.createElement('label');
|
||||
const dateImg = document.createElement('img');
|
||||
dateImg.src = chrome.runtime.getURL('icons/Calendar.svg');
|
||||
dateImg.alt = 'Dátum';
|
||||
dateLabel.appendChild(dateImg);
|
||||
dateLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.date')));
|
||||
|
||||
const dateInput = document.createElement('input');
|
||||
dateInput.type = 'date';
|
||||
dateInput.id = 'dateFilter';
|
||||
dateInput.className = 'filter-input';
|
||||
|
||||
dateGroup.appendChild(dateLabel);
|
||||
dateGroup.appendChild(dateInput);
|
||||
filterContent.appendChild(dateGroup);
|
||||
|
||||
const subjectGroup = document.createElement('div');
|
||||
subjectGroup.className = 'filter-group';
|
||||
|
||||
const subjectLabel = document.createElement('label');
|
||||
const subjectImg = document.createElement('img');
|
||||
subjectImg.src = chrome.runtime.getURL('icons/Subject.svg');
|
||||
subjectImg.alt = 'Tantárgy';
|
||||
subjectLabel.appendChild(subjectImg);
|
||||
subjectLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.subject')));
|
||||
|
||||
const subjectSelect = document.createElement('select');
|
||||
subjectSelect.id = 'subjectFilter';
|
||||
subjectSelect.className = 'filter-input';
|
||||
|
||||
const defaultSubjectOption = document.createElement('option');
|
||||
defaultSubjectOption.value = '';
|
||||
defaultSubjectOption.textContent = LanguageManager.t('absences.all_subjects');
|
||||
subjectSelect.appendChild(defaultSubjectOption);
|
||||
|
||||
const subjects = [...new Set(absences.map(a => a.subject))].sort();
|
||||
subjects.forEach(subject => {
|
||||
const option = document.createElement('option');
|
||||
option.value = subject;
|
||||
option.textContent = subject;
|
||||
subjectSelect.appendChild(option);
|
||||
});
|
||||
|
||||
subjectGroup.appendChild(subjectLabel);
|
||||
subjectGroup.appendChild(subjectSelect);
|
||||
filterContent.appendChild(subjectGroup);
|
||||
|
||||
const justificationGroup = document.createElement('div');
|
||||
justificationGroup.className = 'filter-group';
|
||||
|
||||
const justificationLabel = document.createElement('label');
|
||||
const justificationImg = document.createElement('img');
|
||||
justificationImg.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
|
||||
justificationImg.alt = 'Igazolás';
|
||||
justificationLabel.appendChild(justificationImg);
|
||||
justificationLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justification')));
|
||||
|
||||
const justificationSelect = document.createElement('select');
|
||||
justificationSelect.id = 'justificationFilter';
|
||||
justificationSelect.className = 'filter-input';
|
||||
|
||||
const justificationOptions = [
|
||||
{ value: '', text: LanguageManager.t('absences.all_types') },
|
||||
{ value: 'justified', text: LanguageManager.t('absences.justified') },
|
||||
{ value: 'unjustified', text: LanguageManager.t('absences.unjustified') },
|
||||
{ value: 'pending', text: LanguageManager.t('absences.pending') }
|
||||
];
|
||||
|
||||
justificationOptions.forEach(optionData => {
|
||||
const option = document.createElement('option');
|
||||
option.value = optionData.value;
|
||||
option.textContent = optionData.text;
|
||||
justificationSelect.appendChild(option);
|
||||
});
|
||||
|
||||
justificationGroup.appendChild(justificationLabel);
|
||||
justificationGroup.appendChild(justificationSelect);
|
||||
filterContent.appendChild(justificationGroup);
|
||||
|
||||
filterCard.appendChild(filterHeader);
|
||||
filterCard.appendChild(filterContent);
|
||||
|
||||
return filterCard;
|
||||
}
|
||||
|
||||
function createStatsSection(absences) {
|
||||
const statsSection = document.createElement('div');
|
||||
statsSection.className = 'stats-section';
|
||||
|
||||
const statsTitle = document.createElement('h2');
|
||||
statsTitle.className = 'stats-title';
|
||||
statsTitle.textContent = LanguageManager.t('absences.title');
|
||||
statsSection.appendChild(statsTitle);
|
||||
|
||||
const statsGrid = document.createElement('div');
|
||||
statsGrid.className = 'stats-grid';
|
||||
|
||||
const totalAbsences = absences.filter(a => a.absenceType === 'absence').length;
|
||||
const totalLates = absences.filter(a => a.absenceType === 'late').length;
|
||||
const justified = absences.filter(a => a.justificationStatus === 'justified').length;
|
||||
const unjustified = absences.filter(a => a.justificationStatus === 'unjustified').length;
|
||||
const pending = absences.filter(a => a.justificationStatus === 'pending').length;
|
||||
|
||||
const stats = [
|
||||
{
|
||||
type: 'total',
|
||||
number: absences.length,
|
||||
label: LanguageManager.t('absences.total_absences')
|
||||
},
|
||||
{
|
||||
type: 'justified',
|
||||
number: absences.filter(a => a.justificationStatus === 'justified').length,
|
||||
label: LanguageManager.t('absences.justified')
|
||||
},
|
||||
{
|
||||
type: 'unjustified',
|
||||
number: absences.filter(a => a.justificationStatus === 'unjustified').length,
|
||||
label: LanguageManager.t('absences.unjustified')
|
||||
},
|
||||
{
|
||||
type: 'pending',
|
||||
number: absences.filter(a => a.justificationStatus === 'pending').length,
|
||||
label: LanguageManager.t('absences.pending')
|
||||
}
|
||||
{ type: 'total', number: absences.length, label: LanguageManager.t('absences.total_absences') },
|
||||
{ type: 'absence', number: totalAbsences, label: LanguageManager.t('absences.absence_type') },
|
||||
{ type: 'late', number: totalLates, label: LanguageManager.t('absences.late_type') },
|
||||
{ type: 'justified', number: justified, label: LanguageManager.t('absences.justified') },
|
||||
{ type: 'unjustified', number: unjustified, label: LanguageManager.t('absences.unjustified') },
|
||||
{ type: 'pending', number: pending, label: LanguageManager.t('absences.pending') },
|
||||
];
|
||||
|
||||
stats.forEach(stat => {
|
||||
const statCard = document.createElement('div');
|
||||
statCard.className = `stat-card ${stat.type}`;
|
||||
statCard.dataset.type = stat.type;
|
||||
|
||||
const statNumber = document.createElement('div');
|
||||
statNumber.className = 'stat-number';
|
||||
@@ -235,166 +134,322 @@ function createStatsSection(absences) {
|
||||
return statsSection;
|
||||
}
|
||||
|
||||
function createDayGroup(date, dayAbsences) {
|
||||
const dayGroup = document.createElement('div');
|
||||
dayGroup.className = 'day-group';
|
||||
dayGroup.dataset.date = date;
|
||||
|
||||
|
||||
const dayHeader = document.createElement('div');
|
||||
dayHeader.className = 'day-header';
|
||||
|
||||
const dayDate = document.createElement('div');
|
||||
dayDate.className = 'day-date';
|
||||
|
||||
const calendarIcon = document.createElement('img');
|
||||
calendarIcon.src = chrome.runtime.getURL('icons/Calendar.svg');
|
||||
calendarIcon.alt = 'Dátum';
|
||||
|
||||
const dateText = document.createElement('span');
|
||||
dateText.textContent = formatDateWithDay(date);
|
||||
|
||||
dayDate.appendChild(calendarIcon);
|
||||
dayDate.appendChild(dateText);
|
||||
|
||||
const dayCount = document.createElement('div');
|
||||
dayCount.className = 'day-count';
|
||||
dayCount.textContent = `${dayAbsences.length} ${LanguageManager.t('absences.hours')}`;
|
||||
|
||||
dayHeader.appendChild(dayDate);
|
||||
dayHeader.appendChild(dayCount);
|
||||
|
||||
const dayAbsencesContainer = document.createElement('div');
|
||||
dayAbsencesContainer.className = 'day-absences';
|
||||
|
||||
dayAbsences.forEach(absence => {
|
||||
const absenceCard = createAbsenceCard(absence);
|
||||
dayAbsencesContainer.appendChild(absenceCard);
|
||||
});
|
||||
|
||||
dayGroup.appendChild(dayHeader);
|
||||
dayGroup.appendChild(dayAbsencesContainer);
|
||||
|
||||
return dayGroup;
|
||||
function getSchoolYearStart() {
|
||||
const now = new Date();
|
||||
const year = now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1;
|
||||
return new Date(year, 8, 1);
|
||||
}
|
||||
|
||||
function formatDateWithDay(dateStr) {
|
||||
const parts = dateStr.split('.');
|
||||
const year = parseInt(parts[0]);
|
||||
const month = parseInt(parts[1]) - 1;
|
||||
const day = parseInt(parts[2]);
|
||||
function createCalendarSection(groupedByDate, absences) {
|
||||
const calendarSection = document.createElement('div');
|
||||
calendarSection.className = 'calendar-section';
|
||||
|
||||
const date = new Date(year, month, day);
|
||||
const days = [
|
||||
LanguageManager.t('common.sunday'),
|
||||
LanguageManager.t('common.monday'),
|
||||
LanguageManager.t('common.tuesday'),
|
||||
LanguageManager.t('common.wednesday'),
|
||||
LanguageManager.t('common.thursday'),
|
||||
LanguageManager.t('common.friday'),
|
||||
LanguageManager.t('common.saturday')
|
||||
const schoolYearStart = getSchoolYearStart();
|
||||
const now = new Date();
|
||||
|
||||
const months = [];
|
||||
let currentDate = new Date(schoolYearStart);
|
||||
|
||||
while (currentDate <= now) {
|
||||
months.push({
|
||||
year: currentDate.getFullYear(),
|
||||
month: currentDate.getMonth()
|
||||
});
|
||||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||||
}
|
||||
|
||||
months.forEach(({ year, month }) => {
|
||||
const monthCalendar = createMonthCalendar(year, month, groupedByDate, absences);
|
||||
calendarSection.appendChild(monthCalendar);
|
||||
});
|
||||
|
||||
return calendarSection;
|
||||
}
|
||||
|
||||
function createMonthCalendar(year, month, groupedByDate, absences) {
|
||||
const monthContainer = document.createElement('div');
|
||||
monthContainer.className = 'month-container';
|
||||
|
||||
const monthHeader = document.createElement('div');
|
||||
monthHeader.className = 'month-header';
|
||||
|
||||
const monthNames = [
|
||||
LanguageManager.t('common.january') || 'Január',
|
||||
LanguageManager.t('common.february') || 'Február',
|
||||
LanguageManager.t('common.march') || 'Március',
|
||||
LanguageManager.t('common.april') || 'Április',
|
||||
LanguageManager.t('common.may') || 'Május',
|
||||
LanguageManager.t('common.june') || 'Június',
|
||||
LanguageManager.t('common.july') || 'Július',
|
||||
LanguageManager.t('common.august') || 'Augusztus',
|
||||
LanguageManager.t('common.september') || 'Szeptember',
|
||||
LanguageManager.t('common.october') || 'Október',
|
||||
LanguageManager.t('common.november') || 'November',
|
||||
LanguageManager.t('common.december') || 'December'
|
||||
];
|
||||
|
||||
const dayName = days[date.getDay()];
|
||||
return `${dateStr} - ${dayName.charAt(0).toUpperCase() + dayName.slice(1)}`;
|
||||
monthHeader.textContent = `${monthNames[month]} ${year}`;
|
||||
monthContainer.appendChild(monthHeader);
|
||||
|
||||
const calendarGrid = document.createElement('div');
|
||||
calendarGrid.className = 'calendar-grid';
|
||||
|
||||
const dayNames = [
|
||||
LanguageManager.t('common.mon') || 'H',
|
||||
LanguageManager.t('common.tue') || 'K',
|
||||
LanguageManager.t('common.wed') || 'Sze',
|
||||
LanguageManager.t('common.thu') || 'Cs',
|
||||
LanguageManager.t('common.fri') || 'P',
|
||||
LanguageManager.t('common.sat') || 'Szo',
|
||||
LanguageManager.t('common.sun') || 'V'
|
||||
];
|
||||
|
||||
dayNames.forEach(day => {
|
||||
const dayHeader = document.createElement('div');
|
||||
dayHeader.className = 'calendar-day-header';
|
||||
dayHeader.textContent = day;
|
||||
calendarGrid.appendChild(dayHeader);
|
||||
});
|
||||
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const daysInMonth = lastDay.getDate();
|
||||
|
||||
let startDay = firstDay.getDay() - 1;
|
||||
if (startDay < 0) startDay = 6;
|
||||
|
||||
for (let i = 0; i < startDay; i++) {
|
||||
const emptyCell = document.createElement('div');
|
||||
emptyCell.className = 'calendar-day empty';
|
||||
calendarGrid.appendChild(emptyCell);
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dateKey = `${year}-${(month + 1).toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}`;
|
||||
const dayAbsences = groupedByDate[dateKey] || [];
|
||||
const currentDayDate = new Date(year, month, day);
|
||||
currentDayDate.setHours(0, 0, 0, 0);
|
||||
|
||||
const dayCell = document.createElement('div');
|
||||
dayCell.className = 'calendar-day';
|
||||
|
||||
if (currentDayDate > today) {
|
||||
dayCell.classList.add('future');
|
||||
}
|
||||
|
||||
if (currentDayDate.getTime() === today.getTime()) {
|
||||
dayCell.classList.add('today');
|
||||
}
|
||||
|
||||
if (dayAbsences.length > 0) {
|
||||
dayCell.classList.add('has-absence');
|
||||
|
||||
const hasUnjustified = dayAbsences.some(a => a.justificationStatus === 'unjustified');
|
||||
const hasPending = dayAbsences.some(a => a.justificationStatus === 'pending');
|
||||
const hasJustified = dayAbsences.some(a => a.justificationStatus === 'justified');
|
||||
const hasAbsence = dayAbsences.some(a => a.absenceType === 'absence');
|
||||
const hasLate = dayAbsences.some(a => a.absenceType === 'late');
|
||||
|
||||
if (hasUnjustified) {
|
||||
dayCell.classList.add('status-unjustified');
|
||||
} else if (hasPending) {
|
||||
dayCell.classList.add('status-pending');
|
||||
} else if (hasJustified) {
|
||||
dayCell.classList.add('status-justified');
|
||||
}
|
||||
|
||||
if (hasAbsence && hasLate) {
|
||||
dayCell.classList.add('type-mixed');
|
||||
} else if (hasLate) {
|
||||
dayCell.classList.add('type-late');
|
||||
}
|
||||
|
||||
dayCell.addEventListener('click', () => {
|
||||
openAbsenceModal(dateKey, dayAbsences);
|
||||
});
|
||||
|
||||
const countBadge = document.createElement('span');
|
||||
countBadge.className = 'absence-count';
|
||||
countBadge.textContent = dayAbsences.length;
|
||||
dayCell.appendChild(countBadge);
|
||||
}
|
||||
|
||||
const dayNumber = document.createElement('span');
|
||||
dayNumber.className = 'day-number';
|
||||
dayNumber.textContent = day;
|
||||
dayCell.appendChild(dayNumber);
|
||||
|
||||
calendarGrid.appendChild(dayCell);
|
||||
}
|
||||
|
||||
monthContainer.appendChild(calendarGrid);
|
||||
return monthContainer;
|
||||
}
|
||||
|
||||
function createAbsenceCard(absence) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'absence-card';
|
||||
card.dataset.subject = absence.subject;
|
||||
card.dataset.status = absence.justificationStatus;
|
||||
card.dataset.date = absence.date;
|
||||
function openAbsenceModal(dateKey, dayAbsences) {
|
||||
const existingModal = document.querySelector('.absence-modal-overlay');
|
||||
if (existingModal) {
|
||||
existingModal.remove();
|
||||
}
|
||||
|
||||
const lessonDiv = document.createElement('div');
|
||||
lessonDiv.className = 'absence-lesson';
|
||||
lessonDiv.textContent = absence.lesson + '.';
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'absence-modal-overlay';
|
||||
|
||||
const subjectDiv = document.createElement('div');
|
||||
subjectDiv.className = 'absence-subject';
|
||||
subjectDiv.textContent = absence.subject;
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'absence-modal';
|
||||
|
||||
const topicDiv = document.createElement('div');
|
||||
topicDiv.className = 'absence-topic';
|
||||
topicDiv.textContent = absence.topic || '-';
|
||||
topicDiv.title = absence.topic;
|
||||
const modalHeader = document.createElement('div');
|
||||
modalHeader.className = 'modal-header';
|
||||
|
||||
const statusDiv = document.createElement('div');
|
||||
statusDiv.className = 'absence-status';
|
||||
const dateParts = dateKey.split('-');
|
||||
const displayDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2]));
|
||||
const dayNames = [
|
||||
LanguageManager.t('common.sunday') || 'Vasárnap',
|
||||
LanguageManager.t('common.monday') || 'Hétfő',
|
||||
LanguageManager.t('common.tuesday') || 'Kedd',
|
||||
LanguageManager.t('common.wednesday') || 'Szerda',
|
||||
LanguageManager.t('common.thursday') || 'Csütörtök',
|
||||
LanguageManager.t('common.friday') || 'Péntek',
|
||||
LanguageManager.t('common.saturday') || 'Szombat'
|
||||
];
|
||||
|
||||
const modalTitle = document.createElement('h2');
|
||||
modalTitle.textContent = `${dateParts[0]}.${dateParts[1]}.${dateParts[2]}. - ${dayNames[displayDate.getDay()]}`;
|
||||
|
||||
modalHeader.appendChild(modalTitle);
|
||||
|
||||
const modalContent = document.createElement('div');
|
||||
modalContent.className = 'modal-content';
|
||||
|
||||
dayAbsences.forEach(absence => {
|
||||
const absenceCard = document.createElement('div');
|
||||
absenceCard.className = `modal-absence-card ${absence.justificationStatus}`;
|
||||
|
||||
const headerRow = document.createElement('div');
|
||||
headerRow.className = 'modal-card-header';
|
||||
|
||||
const lessonSpan = document.createElement('span');
|
||||
lessonSpan.className = 'modal-lesson';
|
||||
lessonSpan.textContent = `${absence.lesson}.`;
|
||||
|
||||
const typeIndicator = document.createElement('span');
|
||||
typeIndicator.className = `type-indicator ${absence.absenceType}`;
|
||||
typeIndicator.textContent = absence.absenceType === 'late'
|
||||
? (LanguageManager.t('absences.late_type') || 'Késés')
|
||||
: (LanguageManager.t('absences.absence_type') || 'Hiányzás');
|
||||
|
||||
const statusBadge = document.createElement('span');
|
||||
statusBadge.className = `status-badge ${absence.justificationStatus}`;
|
||||
|
||||
statusBadge.className = `modal-status-badge ${absence.justificationStatus}`;
|
||||
let statusText = '';
|
||||
if (absence.justificationStatus === 'justified') {
|
||||
const img = document.createElement('img');
|
||||
img.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
|
||||
img.alt = 'Igazolt';
|
||||
statusBadge.appendChild(img);
|
||||
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justified')));
|
||||
statusText = LanguageManager.t('absences.justified') || 'Igazolt';
|
||||
} else if (absence.justificationStatus === 'unjustified') {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'material-icons-round';
|
||||
span.textContent = 'cancel';
|
||||
statusBadge.appendChild(span);
|
||||
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.unjustified')));
|
||||
statusText = LanguageManager.t('absences.unjustified') || 'Igazolatlan';
|
||||
} else {
|
||||
const img = document.createElement('img');
|
||||
img.src = chrome.runtime.getURL('icons/pending.svg');
|
||||
img.alt = 'Függőben';
|
||||
statusBadge.appendChild(img);
|
||||
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.pending')));
|
||||
statusText = LanguageManager.t('absences.pending') || 'Igazolásra vár';
|
||||
}
|
||||
statusBadge.textContent = statusText;
|
||||
|
||||
headerRow.appendChild(lessonSpan);
|
||||
headerRow.appendChild(typeIndicator);
|
||||
headerRow.appendChild(statusBadge);
|
||||
|
||||
const subjectDiv = document.createElement('div');
|
||||
subjectDiv.className = 'modal-subject';
|
||||
subjectDiv.textContent = absence.subject;
|
||||
|
||||
const infoRow = document.createElement('div');
|
||||
infoRow.className = 'modal-info-row';
|
||||
|
||||
if (absence.topic) {
|
||||
const topicSpan = document.createElement('span');
|
||||
topicSpan.className = 'modal-info-item';
|
||||
topicSpan.textContent = absence.topic;
|
||||
infoRow.appendChild(topicSpan);
|
||||
}
|
||||
|
||||
statusDiv.appendChild(statusBadge);
|
||||
if (absence.justificationType) {
|
||||
const justificationSpan = document.createElement('span');
|
||||
justificationSpan.className = 'modal-info-item';
|
||||
justificationSpan.textContent = absence.justificationType;
|
||||
infoRow.appendChild(justificationSpan);
|
||||
}
|
||||
|
||||
card.appendChild(lessonDiv);
|
||||
card.appendChild(subjectDiv);
|
||||
card.appendChild(topicDiv);
|
||||
card.appendChild(statusDiv);
|
||||
if (absence.absenceType === 'late' && absence.minutes > 0) {
|
||||
const minutesSpan = document.createElement('span');
|
||||
minutesSpan.className = 'modal-info-item modal-minutes';
|
||||
minutesSpan.textContent = `${absence.minutes} ${LanguageManager.t('absences.minutes') || 'perc'}`;
|
||||
infoRow.appendChild(minutesSpan);
|
||||
}
|
||||
|
||||
return card;
|
||||
absenceCard.appendChild(headerRow);
|
||||
absenceCard.appendChild(subjectDiv);
|
||||
if (infoRow.children.length > 0) {
|
||||
absenceCard.appendChild(infoRow);
|
||||
}
|
||||
|
||||
modalContent.appendChild(absenceCard);
|
||||
});
|
||||
|
||||
modal.appendChild(modalHeader);
|
||||
modal.appendChild(modalContent);
|
||||
overlay.appendChild(modal);
|
||||
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
});
|
||||
|
||||
const handleEscape = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
overlay.remove();
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
overlay.classList.add('visible');
|
||||
});
|
||||
}
|
||||
|
||||
function createAbsencesContent(groupedAbsences) {
|
||||
const content = document.createElement('div');
|
||||
content.className = 'absences-content';
|
||||
function createLegend() {
|
||||
const legend = document.createElement('div');
|
||||
legend.className = 'calendar-legend';
|
||||
|
||||
const sortedDates = Object.keys(groupedAbsences).sort((a, b) => {
|
||||
const dateA = new Date(a.replace(/\./g, '-').slice(0, -1));
|
||||
const dateB = new Date(b.replace(/\./g, '-').slice(0, -1));
|
||||
return dateB - dateA;
|
||||
const legendItems = [
|
||||
{ class: 'status-justified', label: LanguageManager.t('absences.justified') || 'Igazolt' },
|
||||
{ class: 'status-pending', label: LanguageManager.t('absences.pending') || 'Igazolásra vár' },
|
||||
{ class: 'status-unjustified', label: LanguageManager.t('absences.unjustified') || 'Igazolatlan' },
|
||||
{ class: 'type-late-legend', label: LanguageManager.t('absences.late_type') || 'Késés', isType: true },
|
||||
];
|
||||
|
||||
legendItems.forEach(item => {
|
||||
const legendItem = document.createElement('div');
|
||||
legendItem.className = 'legend-item';
|
||||
|
||||
const legendColor = document.createElement('div');
|
||||
legendColor.className = `legend-color ${item.class}`;
|
||||
|
||||
const legendLabel = document.createElement('span');
|
||||
legendLabel.textContent = item.label;
|
||||
|
||||
legendItem.appendChild(legendColor);
|
||||
legendItem.appendChild(legendLabel);
|
||||
legend.appendChild(legendItem);
|
||||
});
|
||||
|
||||
if (sortedDates.length === 0) {
|
||||
const emptyState = document.createElement('div');
|
||||
emptyState.className = 'empty-state';
|
||||
|
||||
const emptyIcon = document.createElement('img');
|
||||
emptyIcon.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
|
||||
emptyIcon.alt = 'Nincs hiányzás';
|
||||
|
||||
const emptyTitle = document.createElement('h3');
|
||||
emptyTitle.textContent = LanguageManager.t('absences.title');
|
||||
|
||||
const emptyText = document.createElement('p');
|
||||
emptyText.textContent = LanguageManager.t('dashboard.not_supported');
|
||||
|
||||
emptyState.appendChild(emptyIcon);
|
||||
emptyState.appendChild(emptyTitle);
|
||||
emptyState.appendChild(emptyText);
|
||||
content.appendChild(emptyState);
|
||||
} else {
|
||||
sortedDates.forEach(date => {
|
||||
const dayGroup = createDayGroup(date, groupedAbsences[date]);
|
||||
content.appendChild(dayGroup);
|
||||
});
|
||||
}
|
||||
|
||||
return content;
|
||||
return legend;
|
||||
}
|
||||
|
||||
async function transformAbsencesPage() {
|
||||
const { basicData, absences, groupedAbsences } = await collectAbsencesData();
|
||||
const { basicData, absences, groupedByDate } = await collectAbsencesData();
|
||||
|
||||
document.body.textContent = '';
|
||||
|
||||
@@ -413,162 +468,26 @@ async function transformAbsencesPage() {
|
||||
const main = document.createElement('main');
|
||||
main.className = 'kreta-main';
|
||||
|
||||
const pageGrid = document.createElement('div');
|
||||
pageGrid.className = 'absences-page';
|
||||
|
||||
const sidebar = document.createElement('div');
|
||||
sidebar.className = 'absences-sidebar';
|
||||
const filterCard = createFilterCard(absences);
|
||||
sidebar.appendChild(filterCard);
|
||||
const pageContent = document.createElement('div');
|
||||
pageContent.className = 'absences-page';
|
||||
|
||||
const statsSection = createStatsSection(absences);
|
||||
sidebar.appendChild(statsSection);
|
||||
pageContent.appendChild(statsSection);
|
||||
|
||||
const absencesContent = createAbsencesContent(groupedAbsences);
|
||||
const legend = createLegend();
|
||||
pageContent.appendChild(legend);
|
||||
|
||||
pageGrid.appendChild(sidebar);
|
||||
pageGrid.appendChild(absencesContent);
|
||||
const calendarSection = createCalendarSection(groupedByDate, absences);
|
||||
pageContent.appendChild(calendarSection);
|
||||
|
||||
main.appendChild(pageGrid);
|
||||
main.appendChild(pageContent);
|
||||
container.appendChild(main);
|
||||
document.body.appendChild(container);
|
||||
|
||||
setupUserDropdown();
|
||||
setupFilters(groupedAbsences);
|
||||
|
||||
loadingScreen.hide();
|
||||
}
|
||||
|
||||
function setupFilters(originalGroupedAbsences) {
|
||||
try {
|
||||
const dateFilter = document.getElementById("dateFilter");
|
||||
const subjectFilter = document.getElementById("subjectFilter");
|
||||
const justificationFilter = document.getElementById("justificationFilter");
|
||||
|
||||
if (!dateFilter || !subjectFilter || !justificationFilter) {
|
||||
console.warn("Some filter elements were not found in the DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
const filterAbsences = () => {
|
||||
try {
|
||||
const dateFilterValue = dateFilter.value;
|
||||
const subject = subjectFilter.value;
|
||||
const justified = justificationFilter.value;
|
||||
const selectedDate = dateFilterValue ? new Date(dateFilterValue) : null;
|
||||
|
||||
document.querySelectorAll(".absence-card").forEach((card) => {
|
||||
const dateStr = card.dataset.date;
|
||||
const dateParts = dateStr.split(".");
|
||||
|
||||
if (dateParts.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedYear = parseInt(dateParts[0].trim(), 10);
|
||||
const parsedMonth = parseInt(dateParts[1].trim(), 10) - 1;
|
||||
const parsedDay = parseInt(dateParts[2].trim(), 10);
|
||||
|
||||
const cardDate = new Date(parsedYear, parsedMonth, parsedDay);
|
||||
|
||||
let showCard = true;
|
||||
|
||||
if (selectedDate) {
|
||||
if (
|
||||
cardDate.getFullYear() !== selectedDate.getFullYear() ||
|
||||
cardDate.getMonth() !== selectedDate.getMonth() ||
|
||||
cardDate.getDate() !== selectedDate.getDate()
|
||||
) {
|
||||
showCard = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (subject && card.dataset.subject !== subject) {
|
||||
showCard = false;
|
||||
}
|
||||
|
||||
if (justified && card.dataset.status !== justified) {
|
||||
showCard = false;
|
||||
}
|
||||
|
||||
card.style.display = showCard ? "" : "none";
|
||||
});
|
||||
|
||||
updateDayGroupsVisibility();
|
||||
|
||||
updateStatistics();
|
||||
} catch (err) {
|
||||
console.error("Error during filtering absences:", err);
|
||||
}
|
||||
};
|
||||
|
||||
[dateFilter, subjectFilter, justificationFilter].forEach((filter) => {
|
||||
if (filter) {
|
||||
filter.addEventListener("change", filterAbsences);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error("Error setting up filters:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function updateDayGroupsVisibility() {
|
||||
document.querySelectorAll(".day-group").forEach((group) => {
|
||||
const visibleCards = group.querySelectorAll('.absence-card:not([style*="display: none"])');
|
||||
const dayCount = group.querySelector('.day-count');
|
||||
|
||||
if (visibleCards.length > 0) {
|
||||
group.style.display = "";
|
||||
if (dayCount) {
|
||||
dayCount.textContent = `${visibleCards.length} ${LanguageManager.t('absences.hours')}`;
|
||||
}
|
||||
} else {
|
||||
group.style.display = "none";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateStatistics() {
|
||||
try {
|
||||
const visibleCards = document.querySelectorAll('.absence-card:not([style*="display: none"])');
|
||||
const totalVisible = visibleCards.length;
|
||||
const justifiedVisible = Array.from(visibleCards).filter(
|
||||
card => card.dataset.status === 'justified'
|
||||
).length;
|
||||
const unjustifiedVisible = Array.from(visibleCards).filter(
|
||||
card => card.dataset.status === 'unjustified'
|
||||
).length;
|
||||
const pendingVisible = Array.from(visibleCards).filter(
|
||||
card => card.dataset.status === 'pending'
|
||||
).length;
|
||||
|
||||
const statCards = document.querySelectorAll(".stat-card");
|
||||
statCards.forEach(card => {
|
||||
const type = card.dataset.type;
|
||||
const numberEl = card.querySelector('.stat-number');
|
||||
if (numberEl) {
|
||||
switch(type) {
|
||||
case 'total':
|
||||
numberEl.textContent = totalVisible;
|
||||
break;
|
||||
case 'justified':
|
||||
numberEl.textContent = justifiedVisible;
|
||||
break;
|
||||
case 'unjustified':
|
||||
numberEl.textContent = unjustifiedVisible;
|
||||
break;
|
||||
case 'pending':
|
||||
numberEl.textContent = pendingVisible;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error updating statistics:", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.href.includes("/Hianyzas/Hianyzasok")) {
|
||||
transformAbsencesPage().catch((error) => {
|
||||
console.error(LanguageManager.t("absences.page_transform_error"), error);
|
||||
|
||||
26
i18n/de.json
26
i18n/de.json
@@ -266,7 +266,10 @@
|
||||
"last_30_days": "Letzte 30 Tage",
|
||||
"total_absences": "Alle Fehlzeiten",
|
||||
"topic": "Thema",
|
||||
"status": "Status"
|
||||
"status": "Status",
|
||||
"absence_type": "Abwesenheit",
|
||||
"late_type": "Verspätung",
|
||||
"minutes": "Minuten"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
@@ -418,8 +421,27 @@
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag",
|
||||
"mon": "M",
|
||||
"tue": "D",
|
||||
"wed": "M",
|
||||
"thu": "D",
|
||||
"fri": "F",
|
||||
"sat": "S",
|
||||
"sun": "S",
|
||||
"today": "Heute",
|
||||
"tomorrow": "Morgen"
|
||||
"tomorrow": "Morgen",
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
},
|
||||
"months": {
|
||||
"january": "Januar",
|
||||
|
||||
26
i18n/en.json
26
i18n/en.json
@@ -266,7 +266,10 @@
|
||||
"last_30_days": "Last 30 days",
|
||||
"total_absences": "Total absences",
|
||||
"topic": "Topic",
|
||||
"status": "Status"
|
||||
"status": "Status",
|
||||
"absence_type": "Absence",
|
||||
"late_type": "Late",
|
||||
"minutes": "minutes"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profile",
|
||||
@@ -418,8 +421,27 @@
|
||||
"friday": "Friday",
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday",
|
||||
"mon": "M",
|
||||
"tue": "T",
|
||||
"wed": "W",
|
||||
"thu": "T",
|
||||
"fri": "F",
|
||||
"sat": "S",
|
||||
"sun": "S",
|
||||
"today": "Today",
|
||||
"tomorrow": "Tomorrow"
|
||||
"tomorrow": "Tomorrow",
|
||||
"january": "January",
|
||||
"february": "February",
|
||||
"march": "March",
|
||||
"april": "April",
|
||||
"may": "May",
|
||||
"june": "June",
|
||||
"july": "July",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "October",
|
||||
"november": "November",
|
||||
"december": "December"
|
||||
},
|
||||
"months": {
|
||||
"january": "January",
|
||||
|
||||
28
i18n/hu.json
28
i18n/hu.json
@@ -266,9 +266,12 @@
|
||||
"last_month": "Előző hónap",
|
||||
"current_semester": "Aktuális félév",
|
||||
"last_30_days": "Utolsó 30 nap",
|
||||
"total_absences": "Összes hiányzás",
|
||||
"total_absences": "Összes mulasztás",
|
||||
"topic": "Téma",
|
||||
"status": "Állapot"
|
||||
"status": "Állapot",
|
||||
"absence_type": "Hiányzás",
|
||||
"late_type": "Késés",
|
||||
"minutes": "perc"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
@@ -420,8 +423,27 @@
|
||||
"friday": "péntek",
|
||||
"saturday": "szombat",
|
||||
"sunday": "vasárnap",
|
||||
"mon": "H",
|
||||
"tue": "K",
|
||||
"wed": "Sze",
|
||||
"thu": "Cs",
|
||||
"fri": "P",
|
||||
"sat": "Szo",
|
||||
"sun": "V",
|
||||
"today": "Ma",
|
||||
"tomorrow": "Holnap"
|
||||
"tomorrow": "Holnap",
|
||||
"january": "Január",
|
||||
"february": "Február",
|
||||
"march": "Március",
|
||||
"april": "Április",
|
||||
"may": "Május",
|
||||
"june": "Június",
|
||||
"july": "Július",
|
||||
"august": "Augusztus",
|
||||
"september": "Szeptember",
|
||||
"october": "Október",
|
||||
"november": "November",
|
||||
"december": "December"
|
||||
},
|
||||
"months": {
|
||||
"january": "január",
|
||||
|
||||
Reference in New Issue
Block a user