You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
piomint/app/static/app/js/dbcontrol.js

458 lines
17 KiB

// IndexedDB Funktionen
const dbName = 'myDB';
const dbVersion = 2;
let db;
//
// GRUND FUNKTIONEN FÜR DIE DATENBANK
//
// IndexedDB initialisieren
function initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, dbVersion);
request.onupgradeneeded = function (event) {
db = event.target.result;
if (!db.objectStoreNames.contains('hours')) {
const store = db.createObjectStore('hours', {
keyPath: 'id',
autoIncrement: true,
});
// Spalten definieren
store.createIndex('createdate', 'createdate', { unique: false });
store.createIndex('date', 'date', { unique: false });
store.createIndex('duration', 'duration', { unique: false });
store.createIndex('type', 'type', { unique: false });
store.createIndex('status', 'status', { unique: false });
store.createIndex('sondertext', 'sondertext', { unique: false });
}
if (!db.objectStoreNames.contains('settings')) {
const settingsT = db.createObjectStore('settings', {
keyPath: 'id',
autoIncrement: true,
})
settingsT.createIndex('SettingsID', 'SettingsID', { unique: false });
settingsT.createIndex('SettingsValue', 'SettingsValue', { unique: false });
}
};
request.onsuccess = function (event) {
db = event.target.result;
resolve(db);
};
request.onerror = function (event) {
console.error('IndexedDB Fehler:', event.target.errorCode);
reject(event.target.errorCode);
};
});
}
// Neuer Eintrag in die IndexedDB
function addEntry(data, table) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([table], 'readwrite');
const store = transaction.objectStore(table);
data.createdate = new Date();
const request = store.add(data);
request.onsuccess = function () {
resolve(request.result);
};
request.onerror = function (event) {
reject('Fehler beim Hinzufügen des Eintrags:', event.target.error);
};
});
}
// Eintrag aktualisieren
function updateEntry(id, table, updatedData) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([table], 'readwrite');
const store = transaction.objectStore(table);
const getRequest = store.get(id);
getRequest.onsuccess = function (event) {
const data = event.target.result;
Object.assign(data, updatedData);
data.createdate = new Date(); // Aktualisiere das Änderungsdatum
const updateRequest = store.put(data);
updateRequest.onsuccess = function () {
resolve(data);
};
updateRequest.onerror = function (event) {
reject('Fehler beim Aktualisieren des Eintrags:', event.target.error);
};
};
getRequest.onerror = function (event) {
reject('Fehler beim Abrufen des Eintrags:', event.target.error);
};
});
}
// Eintrag löschen
function deleteEntry(id) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['hours'], 'readwrite');
const store = transaction.objectStore('hours');
const request = store.delete(id);
request.onsuccess = function () {
resolve(`Eintrag mit ID ${id} gelöscht.`);
};
request.onerror = function (event) {
reject('Fehler beim Löschen des Eintrags:', event.target.error);
};
});
}
// Einträge abrufen
function getAllEntries(table) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([table], 'readonly');
const store = transaction.objectStore(table);
const request = store.getAll();
request.onsuccess = function () {
resolve(request.result);
};
request.onerror = function (event) {
reject('Fehler beim Abrufen der Einträge:', event.target.error);
};
});
}
// Neuen Eintrag verschlüsselt speichern
async function addEncryptedEntry(data) {
const key = await generateKey();
const { iv, encryptedData } = await encryptData(key, JSON.stringify(data));
return new Promise((resolve, reject) => {
const transaction = db.transaction(['hours'], 'readwrite');
const store = transaction.objectStore('hours');
const request = store.add({
iv: Array.from(iv), // IV muss auch gespeichert werden
encryptedData: Array.from(new Uint8Array(encryptedData)), // Encrypted data als Uint8Array speichern
});
request.onsuccess = function () {
resolve(request.result);
};
request.onerror = function (event) {
reject('Fehler beim Hinzufügen des verschlüsselten Eintrags:', event.target.error);
};
});
}
// Verschlüsselten Eintrag entschlüsseln
async function getDecryptedEntry(id) {
const key = await generateKey();
return new Promise((resolve, reject) => {
const transaction = db.transaction(['hours'], 'readonly');
const store = transaction.objectStore('hours');
const request = store.get(id);
request.onsuccess = async function () {
const entry = request.result;
if (entry) {
const iv = new Uint8Array(entry.iv);
const encryptedData = new Uint8Array(entry.encryptedData);
const decryptedData = await decryptData(key, iv, encryptedData);
resolve(JSON.parse(decryptedData));
} else {
reject('Eintrag nicht gefunden');
}
};
request.onerror = function (event) {
reject('Fehler beim Abrufen des verschlüsselten Eintrags:', event.target.error);
};
});
}
function durationDBtoForm(durationInMin) {
const hours = Math.floor(durationInMin / 60); // Ganze Stunden berechnen
const mins = durationInMin % 60; // Verbleibende Minuten berechnen
return [hours, mins]
}
//
// KOMBI FUNKTIONEN
// SIND VOR ALLEM HIER DAMIT SIE ZEITGLEICH GERENDERT WERDEN
//
// Die abgefragte Dauer in Minuten umrechnen und in hh:mm formatieren
function formatDuration(minutes, shortOutput = false) {
const hours = Math.floor(minutes / 60); // Ganze Stunden berechnen
const mins = minutes % 60; // Verbleibende Minuten berechnen
if (!shortOutput){
return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`; // Stunden und Minuten formatieren
} else {
return `${String(hours)}:${String(mins)}`.replace(':30', ',5').replace(':0', ''); // Stunden und Minuten formatieren
}
}
// Datum für die gewünschte Ansicht formatieren
function formatDate(dateString) {
const date = new Date(dateString); // Konvertiere in ein Date-Objekt
const day = String(date.getDate()).padStart(2, '0'); // Tag
const month = String(date.getMonth() + 1).padStart(2, '0'); // Monat (getMonth() ist 0-basiert)
const year = String(date.getFullYear()).slice(-2); // Die letzten 2 Ziffern des Jahres
return `${day}.${month}.${year}`; // Format tt.mm.yy
}
// Dienstjahr-Label für ein Datum ermitteln.
// Dienstjahr läuft September(Jahr X) bis August(Jahr X+1) und wird mit dem Label X+1 bezeichnet.
// Beispiel: 15. September 2025 -> Dienstjahr-Label 2026; 15. Mai 2026 -> Dienstjahr-Label 2026.
function getServiceYear(date) {
const d = (date instanceof Date) ? date : new Date(date);
const calendarYear = d.getFullYear();
const calendarMonth = d.getMonth() + 1; // 1-basiert
return (calendarMonth >= 9) ? calendarYear + 1 : calendarYear;
}
// Einträge nach Monat und/oder Kalenderjahr filtern.
// WICHTIG: 'year' ist hier immer das ECHTE Kalenderjahr, KEIN Dienstjahr-Label.
// Für Dienstjahr-Filterung siehe filterEntriesByServiceYear().
function filterEntriesByMonthYear(month, year) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['hours'], 'readonly');
const store = transaction.objectStore('hours');
const request = store.getAll();
request.onsuccess = function () {
const allEntries = request.result;
// Filter nach Monat und Jahr
const filteredEntries = allEntries.filter(entry => {
const entryDate = new Date(entry.date); // Konvertiere das date-Feld in ein Date-Objekt
const matchesMonth = month ? entryDate.getMonth() + 1 === month : true; // +1 da getMonth() 0-basiert ist
const matchesYear = year ? entryDate.getFullYear() === year : true;
return matchesMonth && matchesYear;
});
resolve(filteredEntries);
};
request.onerror = function (event) {
reject('Fehler beim Abrufen der Einträge:', event.target.error);
};
});
}
// Einträge für ein komplettes Dienstjahr (Sept. Vorjahr - Aug. Label-Jahr) filtern.
// serviceYearLabel entspricht dem Rückgabewert von getServiceYear(), z.B. 2026 für Sept 2025 - Aug 2026.
function filterEntriesByServiceYear(serviceYearLabel) {
return new Promise((resolve, reject) => {
const transaction = db.transaction(['hours'], 'readonly');
const store = transaction.objectStore('hours');
const request = store.getAll();
request.onsuccess = function () {
const allEntries = request.result;
const filteredEntries = allEntries.filter(entry => {
return getServiceYear(new Date(entry.date)) === serviceYearLabel;
});
resolve(filteredEntries);
};
request.onerror = function (event) {
reject('Fehler beim Abrufen der Einträge:', event.target.error);
};
});
}
// Tabelle auf dem Dashboard mit Infos füllen
// sortField: 'date' | 'duration' | 'type' - sortDirection: 'asc' | 'desc'
function fillTableWithEntries(entries, sortField = 'date', sortDirection = 'asc', ldcLabel = 'LDC') {
const tableBody = document.getElementById('entriesTableBody');
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)
tableBody.innerHTML = '';
// Iteriere durch die Einträge und füge Zeilen hinzu
sortedEntries.forEach(entry => {
const row = document.createElement('tr');
//EVENTUELL MAL IN DER ZUKUNFT - UM EINTRÄGE ZU SEITE WISCHEN
// let startX = 0;
// let currentRow;
// row.addEventListener('touchstart', (e) => {
// startX = e.touches[0].clientX;
// currentRow = e.currentTarget;
// });
// row.addEventListener('touchmove', (e) => {
// const moveX = e.touches[0].clientX;
// if (startX - moveX > 50) {
// currentRow.classList.add('swipe-left');
// }
// });
// // Am Ende des Swipe, zeige den Löschen-Button
// row.addEventListener('touchend', () => {
// if (currentRow.classList.contains('swipe-left')) {
// currentRow.querySelector('.editCell').classList.remove('d-none')
// currentRow.querySelector('.delCell').classList.remove('d-none')
// currentRow.querySelector('.duration').classList.add('d-none')
// currentRow.querySelector('.date').classList.add('d-none')
// }
// });
// Spalte für die Dauer (duration)
const durationCell = document.createElement('td');
durationCell.classList.add('duration')
durationCell.textContent = formatDuration(entry.duration); // Dauer im hh:mm-Format
row.appendChild(durationCell);
// Spalte für das Datum (date)
const dateCell = document.createElement('td');
const entryDate = new Date(entry.date); // Konvertiere in ein Date-Objekt
dateCell.classList.add('date')
dateCell.textContent = formatDate(entry.date); // Datum im tt.mm.yy-Format
row.appendChild(dateCell);
// Spalte für den Typ (type) - wenn 3, dann zeige "Beschreibung" an
const typeCell = document.createElement('td');
if (entry.type === 3) {
typeCell.textContent = entry.sondertext; // Bei Type 3 zeige "Beschreibung" an
} else if (entry.type === 2) {
typeCell.textContent = ldcLabel; // Ansonsten zeige den Type-Wert (individuell umbenennbar, siehe Einstellungen)
} else if (entry.type === 1) {
typeCell.textContent = 'Dienst'; // Ansonsten zeige den Type-Wert
}
row.appendChild(typeCell);
const editCell = document.createElement('td');
// editCell.classList.add('editCell')
// editCell.style = 'background-color: rgb(120,170,86); color: white;'
editCell.addEventListener('click', ()=>{
document.getElementById('editEntryID').value = entry.id;
document.getElementById('editStunden').value = durationDBtoForm(entry.duration)[0];
document.getElementById('editMinuten').value = durationDBtoForm(entry.duration)[1];
document.getElementById('editDatum').value = entry.date;
if (entry.type === 2) {
document.getElementById('editldcCheck').checked = true;
} else {
document.getElementById('editldcCheck').checked = false;
}
if (entry.type === 3) {
document.getElementById('editsonstigesCheck').checked = true;
document.getElementById('editsonstigesInput').value = entry.sondertext;
document.getElementById('edit-sonstiges-text').style.display = 'block';
} else {
document.getElementById('editsonstigesCheck').checked = false;
document.getElementById('editsonstigesInput').value = null;
document.getElementById('edit-sonstiges-text').style.display = 'none';
}
})
editCell.innerHTML = '<span class="btn btn-sm btn-secondary" data-bs-toggle="modal" data-bs-target="#editEntryModel" style="width: 40px; border-radius: 1rem; transform: rotate(180deg);">✐</span>';
row.appendChild(editCell)
// const delCell = document.createElement('td');
// delCell.classList.add('d-none')
// delCell.classList.add('delCell')
// delCell.style = 'background-color: red; color: white;'
// delCell.innerHTML = '<p style="margin: 0; padding: 0;">Delete</p>'
// row.appendChild(delCell)
// Füge die erstellte Zeile dem Tabellenkörper hinzu
tableBody.appendChild(row);
});
}
// Abgefragte Daten Zeiten summieren und filtern können
function sumDurations(data, type = null, roundToHalfHour = false) {
// Optionaler Filter nach dem Typ, falls ein Typ angegeben wurde
const filteredData = type !== null ? data.filter(item => item.type === type) : data;
// Berechnung der Summe der "duration"-Werte
let totalDuration = filteredData.reduce((sum, item) => sum + item.duration, 0);
// Optionales Runden auf halbe Stunden
if (roundToHalfHour) {
totalDuration = Math.round(totalDuration / 30) * 30;
}
return totalDuration;
}
// Gesamtstunden (in Minuten) für ein Dienstjahr berechnen, optional mit "55h-Regel".
// Wenn rule55Active true ist: jeder Kalendermonat, der LDC- oder Sonstiges-Stunden (type 2/3)
// enthält, wird für die Summe auf maximal 55h gedeckelt. Reine Dienststunden (type 1) zählen
// in jedem Monat immer voll, auch wenn sie allein schon über 55h liegen - der Deckel greift
// nur, sobald LDC/Sonstiges dazukommt.
function calculateServiceYearTotal(entries, rule55Active = false) {
if (!rule55Active) {
return sumDurations(entries, null, true);
}
const CAP_MINUTES = 55 * 60;
// Einträge nach Kalendermonat gruppieren (Jahr+Monat, damit Dez/Jan nicht vermischt werden)
const monthBuckets = {};
entries.forEach(entry => {
const entryDate = new Date(entry.date);
const key = `${entryDate.getFullYear()}-${entryDate.getMonth()}`;
if (!monthBuckets[key]) {
monthBuckets[key] = [];
}
monthBuckets[key].push(entry);
});
let total = 0;
Object.values(monthBuckets).forEach(bucketEntries => {
const normalMinutes = sumDurations(bucketEntries, 1);
const capRuleMinutes = sumDurations(bucketEntries, 2) + sumDurations(bucketEntries, 3);
let monthTotal = normalMinutes + capRuleMinutes;
if (capRuleMinutes > 0) {
monthTotal = Math.min(monthTotal, CAP_MINUTES);
}
total += Math.round(monthTotal / 30) * 30;
});
return total;
}

Powered by TurnKey Linux.