Stundenansicht verbessert: #25

Merged
samuelzielke merged 1 commits from 26-7-Stundeneinträge into main 3 days ago

@ -173,6 +173,69 @@
height: 15vh; height: 15vh;
} }
/* Neu hinzugefügte Icons (Filter-Toggle, Maximieren) in Grau statt Schwarz/Weiß.
invert(50%) bildet jede Ausgangsfarbe (schwarz, weiß, hell-grau) auf denselben
neutralen Grauton ab - unabhängig davon, welches der beiden Icons gerade angezeigt wird. */
#toggleSortOptionsIcon,
#openViewOfEntryIcon {
filter: invert(50%);
}
/* Tabelle maximieren: Timer-Karte klappt weg, Aktivitäten-Tabelle wächst.
overflow bleibt im normalen Zustand "visible", damit Rand/Schatten der Timer-Karte
nicht abgeschnitten werden - nur beim Einklappen selbst wird geclippt. */
#timerCard {
max-height: 20vh;
opacity: 1;
overflow: visible;
transition: max-height 0.4s ease-in-out, opacity 0.3s ease-in-out, padding 0.4s ease-in-out;
}
#timerCard.timerCollapsed {
max-height: 0;
opacity: 0;
overflow: hidden;
padding-top: 0 !important;
padding-bottom: 0 !important;
pointer-events: none;
}
#ActivitysTable {
transition: height 0.4s ease-in-out;
}
#ActivitysTable.ActivitysTableMaximized {
height: 65vh !important;
}
/* Filter/Sortierung ein- und ausblenden.
overflow bleibt im geöffneten Zustand "visible", damit das Dropdown-Menü (sortFieldMenu)
unterhalb der Zeile nicht abgeschnitten wird - nur beim Einklappen selbst wird geclippt. */
#sortOptionsRow {
max-height: 3rem;
opacity: 1;
overflow: visible;
transition: max-height 0.3s ease-in-out, opacity 0.25s ease-in-out, margin-top 0.3s ease-in-out;
}
#sortOptionsRow.sortOptionsCollapsed {
max-height: 0;
opacity: 0;
overflow: hidden;
margin-top: 0 !important;
pointer-events: none;
}
.sortFieldOption {
cursor: pointer;
}
.sortFieldOption:hover,
.sortFieldOption:active {
background-color: rgb(120,170,86);
color: white;
}
.header-bg-block { .header-bg-block {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;

@ -293,16 +293,24 @@ function filterEntriesByServiceYear(serviceYearLabel) {
} }
// Tabelle auf dem Dashboard mit Infos füllen // Tabelle auf dem Dashboard mit Infos füllen
function fillTableWithEntries(entries) { // sortField: 'date' | 'duration' | 'type' - sortDirection: 'asc' | 'desc'
function fillTableWithEntries(entries, sortField = 'date', sortDirection = 'asc') {
const tableBody = document.getElementById('entriesTableBody'); const tableBody = document.getElementById('entriesTableBody');
entries.sort((a, b) => new Date(a.date) - new Date(b.date)); const sortedEntries = entries.slice().sort((a, b) => {
const valA = (sortField === 'date') ? new Date(a.date) : a[sortField];
const valB = (sortField === 'date') ? new Date(b.date) : b[sortField];
if (valA < valB) return (sortDirection === 'asc') ? -1 : 1;
if (valA > valB) return (sortDirection === 'asc') ? 1 : -1;
return 0;
});
// Zuerst den Inhalt der Tabelle löschen (falls schon Daten da sind) // Zuerst den Inhalt der Tabelle löschen (falls schon Daten da sind)
tableBody.innerHTML = ''; tableBody.innerHTML = '';
// Iteriere durch die Einträge und füge Zeilen hinzu // Iteriere durch die Einträge und füge Zeilen hinzu
entries.forEach(entry => { sortedEntries.forEach(entry => {
const row = document.createElement('tr'); const row = document.createElement('tr');
//EVENTUELL MAL IN DER ZUKUNFT - UM EINTRÄGE ZU SEITE WISCHEN //EVENTUELL MAL IN DER ZUKUNFT - UM EINTRÄGE ZU SEITE WISCHEN

@ -5,6 +5,18 @@ const NewEntryButton = document.getElementById('newEntrySubmitButton')
const SETStudySaveButton = document.getElementById('SETStudySaveButton') const SETStudySaveButton = document.getElementById('SETStudySaveButton')
const SETMonthGoalSaveButton = document.getElementById('SETMonthGoalSaveButton') const SETMonthGoalSaveButton = document.getElementById('SETMonthGoalSaveButton')
const rule55hCheck = document.getElementById('rule55hCheck') const rule55hCheck = document.getElementById('rule55hCheck')
const openViewOfEntry = document.getElementById('openViewOfEntry')
const openViewOfEntryIcon = document.getElementById('openViewOfEntryIcon')
const ActivitysTable = document.getElementById('ActivitysTable')
const timerCard = document.getElementById('timerCard')
const toggleSortOptions = document.getElementById('toggleSortOptions')
const sortOptionsRow = document.getElementById('sortOptionsRow')
const sortFieldButton = document.getElementById('sortFieldButton')
const sortFieldLabel = document.getElementById('sortFieldLabel')
const sortFieldMenu = document.getElementById('sortFieldMenu')
const sortFieldOptions = document.querySelectorAll('.sortFieldOption')
const sortDirectionToggle = document.getElementById('sortDirectionToggle')
const sortDirectionIcon = document.getElementById('sortDirectionIcon')
var allowToSend = false; var allowToSend = false;
const monate = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]; const monate = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
const currentDate = new Date(); const currentDate = new Date();
@ -13,6 +25,16 @@ const currentDate = new Date();
// live aktualisiert, damit getActualDataAndFillView() sie synchron verwenden kann. // live aktualisiert, damit getActualDataAndFillView() sie synchron verwenden kann.
var rule55Active = false; var rule55Active = false;
// Sortierung der Aktivitäten-Tabelle: wird aus dem Cache (localStorage) geladen/gespeichert.
var sortField = 'date';
var sortDirection = 'asc';
// Ob die Filter/Sortier-Optionen gerade sichtbar sind (wird ebenfalls im Cache gespeichert).
var isFilterVisible = true;
// Ob die Aktivitäten-Tabelle gerade maximiert ist (Timer-Karte ist dann ausgeblendet).
var isTableMaximized = false;
// //
// Trigger // Trigger
@ -27,6 +49,12 @@ document.addEventListener('DOMContentLoaded', (event) => {
// 55h-Regel Einstellung laden, bevor die erste Jahresansicht berechnet wird // 55h-Regel Einstellung laden, bevor die erste Jahresansicht berechnet wird
await loadRule55Setting(); await loadRule55Setting();
// Gespeicherte Sortierung der Aktivitäten-Tabelle aus dem Cache laden
loadSortPreference();
// Gespeicherte Sichtbarkeit der Filter/Sortier-Optionen aus dem Cache laden
loadFilterVisibility();
// Bildschirm mit dem Inhalt für den aktuellen Tag füllen // Bildschirm mit dem Inhalt für den aktuellen Tag füllen
getActualDataAndFillView(); getActualDataAndFillView();
@ -76,6 +104,112 @@ rule55hCheck.addEventListener('change', () => {
}) })
}) })
//
// TABELLE MAXIMIEREN
//
// Aktivitäten-Tabelle vergrößern/verkleinern: Timer-Karte klappt beim Maximieren
// animiert weg, um Platz zu schaffen; die Tabelle wächst gleichzeitig.
openViewOfEntry.addEventListener('click', () => {
isTableMaximized = !isTableMaximized
ActivitysTable.classList.toggle('ActivitysTableMaximized', isTableMaximized)
timerCard.classList.toggle('timerCollapsed', isTableMaximized)
openViewOfEntryIcon.src = isTableMaximized
? openViewOfEntryIcon.src.replace('open.png', 'close.png')
: openViewOfEntryIcon.src.replace('close.png', 'open.png')
openViewOfEntry.title = isTableMaximized ? 'Tabelle minimieren' : 'Tabelle maximieren'
})
//
// SORTIERUNG DER AKTIVITÄTEN-TABELLE
//
// Lädt die gespeicherte Sortierung aus dem Cache (localStorage) und synchronisiert die Steuerelemente
function loadSortPreference() {
try {
const saved = JSON.parse(localStorage.getItem('piomintTableSort'))
if (saved && saved.field) {
sortField = saved.field
sortDirection = (saved.direction === 'desc') ? 'desc' : 'asc'
}
} catch (error) {
console.error('Sortierung konnte nicht aus dem Cache geladen werden:', error)
}
const selectedOption = Array.from(sortFieldOptions).find(option => option.dataset.value === sortField)
sortFieldLabel.innerText = selectedOption ? selectedOption.dataset.label : 'Datum'
sortDirectionIcon.innerText = (sortDirection === 'asc') ? 'A → Z' : 'Z → A'
}
// Speichert die aktuelle Sortierung im Cache (localStorage)
function saveSortPreference() {
try {
localStorage.setItem('piomintTableSort', JSON.stringify({ field: sortField, direction: sortDirection }))
} catch (error) {
console.error('Sortierung konnte nicht im Cache gespeichert werden:', error)
}
}
// Eigener Dropdown für die Sortier-Eigenschaft (statt <select>) öffnen/schließen
sortFieldButton.addEventListener('click', (event) => {
event.stopPropagation()
sortFieldMenu.classList.toggle('d-none')
})
// Außerhalb des Menüs klicken schließt es wieder
document.addEventListener('click', (event) => {
if (!sortFieldMenu.classList.contains('d-none') && !sortFieldMenu.contains(event.target) && event.target !== sortFieldButton) {
sortFieldMenu.classList.add('d-none')
}
})
sortFieldOptions.forEach(option => {
option.addEventListener('click', () => {
sortField = option.dataset.value
sortFieldLabel.innerText = option.dataset.label
sortFieldMenu.classList.add('d-none')
saveSortPreference()
getActualDataAndFillView(cursorMonth, cursorYear)
})
})
sortDirectionToggle.addEventListener('click', () => {
sortDirection = (sortDirection === 'asc') ? 'desc' : 'asc'
sortDirectionIcon.innerText = (sortDirection === 'asc') ? 'A → Z' : 'Z → A'
saveSortPreference()
getActualDataAndFillView(cursorMonth, cursorYear)
})
//
// FILTER/SORTIER-OPTIONEN EIN-/AUSBLENDEN
//
// Lädt die gespeicherte Sichtbarkeit der Filter-Optionen aus dem Cache (localStorage)
function loadFilterVisibility() {
try {
const saved = localStorage.getItem('piomintFilterVisible')
if (saved !== null) {
isFilterVisible = (saved === 'true')
}
} catch (error) {
console.error('Filter-Sichtbarkeit konnte nicht aus dem Cache geladen werden:', error)
}
sortOptionsRow.classList.toggle('sortOptionsCollapsed', !isFilterVisible)
}
toggleSortOptions.addEventListener('click', () => {
isFilterVisible = !isFilterVisible
sortOptionsRow.classList.toggle('sortOptionsCollapsed', !isFilterVisible)
// Offenes Dropdown-Menü beim Einklappen der Filterzeile mit schließen
if (!isFilterVisible) {
sortFieldMenu.classList.add('d-none')
}
try {
localStorage.setItem('piomintFilterVisible', isFilterVisible)
} catch (error) {
console.error('Filter-Sichtbarkeit konnte nicht im Cache gespeichert werden:', error)
}
})
// Änderungen überwachen damit die das Formular nicht zu früh abgesendet wird // Änderungen überwachen damit die das Formular nicht zu früh abgesendet wird
@ -237,7 +371,7 @@ async function getActualDataAndFillView(monat = currentDate.getMonth(), jahr = c
console.log(jahr) console.log(jahr)
// Tabelle mit den aktuellen informationen füllen (funktion triggern und Daten übergeben) // Tabelle mit den aktuellen informationen füllen (funktion triggern und Daten übergeben)
fillTableWithEntries(filteredEntries) fillTableWithEntries(filteredEntries, sortField, sortDirection)
// Ausgabe der Daten in den Log für DEV möglich // Ausgabe der Daten in den Log für DEV möglich
// console.log('Gefilterte Einträge für September 2024:', filteredEntries); // console.log('Gefilterte Einträge für September 2024:', filteredEntries);

@ -78,7 +78,7 @@
</div> </div>
</div> </div>
<div class="row justify-content-around" style="width: 100vw; color: black; padding-top: 2rem;"> <div class="row justify-content-around" id="timerCard" style="width: 100vw; color: black; padding-top: 2rem;">
<div class="col-11" style="height: 15vh; padding: 1rem; border-radius:1rem; box-shadow: 0 0 0.5rem gray"> <div class="col-11" style="height: 15vh; padding: 1rem; border-radius:1rem; box-shadow: 0 0 0.5rem gray">
<p style="font-size: 1.2rem;"><b>Timer</b></p> <p style="font-size: 1.2rem;"><b>Timer</b></p>
<div class="row justify-content-between" style="margin-top: -1rem;"> <div class="row justify-content-between" style="margin-top: -1rem;">
@ -93,11 +93,32 @@
</div> </div>
<div class="row justify-content-around" style="width: 95vw; color: black; padding-top: 2rem;"></div> <div class="row justify-content-around" style="width: 95vw; color: black; padding-top: 2rem;"></div>
<div class="col-12" id="ActivitysTable" style="height: 40vh; padding: 0rem 1rem 1rem 1rem; border-radius:1rem; box-shadow: 0 0 0.5rem gray; overflow-y: auto;"> <div class="col-12" id="ActivitysTable" style="height: 40vh; padding: 0rem 1rem 1rem 1rem; border-radius:1rem; box-shadow: 0 0 0.5rem gray; overflow-y: auto;">
<div class="row justify-content-between align-items-center m-0" style="width: 100%;position: sticky; top: 0; background-color: white; padding-top: 1rem; z-index: 100;"> <div style="position: sticky; top: 0; background-color: white; padding-top: 1rem; z-index: 100; width: 100%;">
<p class="col-5" style="font-size: 1.2rem; margin: 0 1rem 0 -1rem;" ><b>Aktivitäten</b></p> <div class="d-flex justify-content-between align-items-center" style="width: 100%;">
<!-- <span class="col-2" style="margin-left: 32%; padding: 0;"><img id="resetTimer" src="{% static 'app/images/addcircle.png' %}" alt="add" class="img-fluid" style="height: 35px"></span> --> <p style="font-size: 1.2rem; margin: 0;" ><b>Aktivitäten</b></p>
<button type="button" id="openNewEntryForm" class="offset-2 col-2 btn btn-light" data-bs-toggle="modal" data-bs-target="#newEntryModal"><img id="openNewEntryFormIcon" src="{% static 'app/images/addcircle.png' %}" alt="add" class="img-fluid"></button> <div class="d-flex align-items-center" style="gap: 0.5rem;">
<button type="button" id="openViewOfEntry" class="offset-1 col-2 btn btn-light d-none"><img id="openViewOfEntryIcon" src="{% static 'app/images/open.png' %}" alt="open" class="img-fluid"></button> <button type="button" id="openNewEntryForm" class="btn btn-light" data-bs-toggle="modal" data-bs-target="#newEntryModal" style="width: 45px; height: 45px; padding: 0;"><img id="openNewEntryFormIcon" src="{% static 'app/images/addcircle.png' %}" alt="add" class="img-fluid"></button>
<button type="button" id="toggleSortOptions" class="btn btn-light" title="Filter/Sortierung ein-/ausblenden" style="width: 45px; height: 45px; padding: 0;"><img id="toggleSortOptionsIcon" src="{% static 'app/images/more.png' %}" alt="filter" class="img-fluid"></button>
<button type="button" id="openViewOfEntry" class="btn btn-light" title="Tabelle maximieren" style="width: 45px; height: 45px; padding: 0;"><img id="openViewOfEntryIcon" src="{% static 'app/images/open.png' %}" alt="open" class="img-fluid"></button>
</div>
</div>
<div class="d-flex align-items-center" id="sortOptionsRow" style="width: 100%; margin-top: 0.5rem; gap: 0.5rem;">
<!-- Eigener Dropdown statt <select>, damit das Auswahlmenü nicht als winziges natives
Popup irgendwo auf dem Bildschirm erscheint, sondern direkt unter dem Button. -->
<div style="position: relative; flex: 1;">
<button type="button" id="sortFieldButton" class="btn btn-light btn-sm" style="font-size: 0.85rem; width: 100%; text-align: left;">
<span id="sortFieldLabel">Datum</span>
</button>
<div id="sortFieldMenu" class="d-none" style="position: absolute; top: 100%; left: 0; right: 0; margin-top: 0.25rem; background-color: white; border-radius: 0.5rem; box-shadow: 0 0 0.5rem gray; z-index: 200; overflow: hidden;">
<div class="sortFieldOption" data-value="date" data-label="Datum" style="padding: 0.5rem 0.75rem; font-size: 0.85rem;">Datum</div>
<div class="sortFieldOption" data-value="duration" data-label="Dauer" style="padding: 0.5rem 0.75rem; font-size: 0.85rem;">Dauer</div>
<div class="sortFieldOption" data-value="type" data-label="Typ" style="padding: 0.5rem 0.75rem; font-size: 0.85rem;">Typ</div>
</div>
</div>
<button type="button" id="sortDirectionToggle" class="btn btn-light btn-sm" style="font-size: 0.85rem; flex: 1;" title="Sortierrichtung umkehren">
<span id="sortDirectionIcon">A → Z</span>
</button>
</div>
</div> </div>
<table class="table table-striped" style="z-index: 1;"> <table class="table table-striped" style="z-index: 1;">
<tbody id="entriesTableBody" style="overflow-y: auto; max-height: 70%;"></tbody> <tbody id="entriesTableBody" style="overflow-y: auto; max-height: 70%;"></tbody>

Loading…
Cancel
Save

Powered by TurnKey Linux.