Compare commits
6 Commits
main
...
#2-Bericht
| Author | SHA1 | Date |
|---|---|---|
|
|
d6c590fdee | 2 months ago |
|
|
f0caaf7288 | 1 year ago |
|
|
0b3cb99431 | 1 year ago |
|
|
00bd28e0f0 | 1 year ago |
|
|
f2fbe0e8c1 | 1 year ago |
|
|
3c721a4dd8 | 2 years ago |
@ -1,12 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import UsageEvent
|
||||
|
||||
|
||||
@admin.register(UsageEvent)
|
||||
class UsageEventAdmin(admin.ModelAdmin):
|
||||
list_display = ("device_id", "created_at", "app_version", "platform", "browser", "is_standalone")
|
||||
list_filter = ("platform", "browser", "is_standalone", "app_version")
|
||||
search_fields = ("device_id",)
|
||||
date_hierarchy = "created_at"
|
||||
ordering = ("-created_at",)
|
||||
# Register your models here.
|
||||
|
||||
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 131 KiB |
@ -0,0 +1,500 @@
|
||||
// Absende Formular Verwalten
|
||||
const hours = document.getElementById('stunden');
|
||||
const mins = document.getElementById('minuten');
|
||||
const NewEntryButton = document.getElementById('newEntrySubmitButton')
|
||||
const SETStudySaveButton = document.getElementById('SETStudySaveButton')
|
||||
const SETMonthGoalSaveButton = document.getElementById('SETMonthGoalSaveButton')
|
||||
var allowToSend = false;
|
||||
const monate = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
|
||||
const currentDate = new Date();
|
||||
|
||||
|
||||
//
|
||||
// Trigger
|
||||
//
|
||||
|
||||
// Wenn das Dokument komplett geladen ist
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
initDB().then(() => {
|
||||
console.log('IndexedDB erfolgreich initialisiert.');
|
||||
// Hier kannst du die App weiter initialisieren, z.B. Daten laden oder die UI aufbauen
|
||||
|
||||
// Bildschirm mit dem Inhalt für den aktuellen Tag füllen
|
||||
getActualDataAndFillView();
|
||||
|
||||
//loadInitialData();
|
||||
}).catch((error) => {
|
||||
console.error('Fehler bei der Initialisierung der IndexedDB:', error);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Änderungen überwachen damit die das Formular nicht zu früh abgesendet wird
|
||||
hours.addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
mins.addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
document.getElementById('sonstigesInput').addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
|
||||
// Einstellungen wenn Neuer Eintrag erstellt wird
|
||||
NewEntryButton.addEventListener('click', () => {
|
||||
|
||||
if (allowToSend === true) {
|
||||
var type = 1
|
||||
|
||||
// Abfragen der Checkboxen
|
||||
const ldcCheck = document.getElementById('ldcCheck').checked
|
||||
if (ldcCheck) {
|
||||
type = 2
|
||||
document.getElementById('ldcCheck').checked = false
|
||||
} else if (document.getElementById('sonstigesCheck').checked) {
|
||||
type = 3;
|
||||
document.getElementById('sonstigesCheck').checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
}
|
||||
|
||||
// Berechnen der Duration
|
||||
const duration = (parseInt(hours.value)*60) + parseInt(mins.value)
|
||||
|
||||
const newEntryData = {
|
||||
date: document.getElementById('datum').value,
|
||||
duration: duration,
|
||||
type: type,
|
||||
status: 1,
|
||||
sondertext: document.getElementById('sonstigesInput').value,
|
||||
}
|
||||
|
||||
addEntry(newEntryData, 'hours').then((id) => {
|
||||
// console.log('Eintrag hinzugefügt mit ID:', id);
|
||||
location.reload()
|
||||
});
|
||||
|
||||
hours.value = null
|
||||
mins.value = null
|
||||
document.getElementById('sonstigesInput').value = null
|
||||
checkInput()
|
||||
}
|
||||
})
|
||||
|
||||
// SETTINGS ACTIONS
|
||||
|
||||
SETStudySaveButton.addEventListener('click', () => {
|
||||
|
||||
var actualSettings = 0
|
||||
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
const SETDBData = result.find(element => element.SettingsID === 1)
|
||||
actualSettingsEntryID = SETDBData ? SETDBData.id : undefined;
|
||||
actualSettings = SETDBData ? SETDBData.SettingsValue : undefined;
|
||||
|
||||
// AKTUELLE DATEN AUS FELD ÜBERNHEMEN
|
||||
var ActInput = document.getElementById('studysinput').value
|
||||
|
||||
const StudySettingsData = {
|
||||
SettingsID: 1,
|
||||
SettingsValue: parseInt(ActInput),
|
||||
}
|
||||
|
||||
// ÜBERPRÜFEN OB EIN EINTRAG MTI DER SETTINGS ID VORHANDEN IST - WENN JA DANN UPDATE WENN NEIN DANN NEUEN ERSTELLEN
|
||||
if ( actualSettings != undefined ) {
|
||||
updateEntry(actualSettingsEntryID, 'settings', StudySettingsData)
|
||||
} else {
|
||||
addEntry(StudySettingsData, 'settings')
|
||||
}
|
||||
})
|
||||
|
||||
SETStudySaveButton.classList.add('d-none')
|
||||
})
|
||||
|
||||
SETMonthGoalSaveButton.addEventListener('click', () => {
|
||||
getAllEntries('settings')
|
||||
.then(function(result){
|
||||
|
||||
const SETDBData = result.find(element => element.SettingsID === 2)
|
||||
actualSettingsEntryID = SETDBData ? SETDBData.id : undefined;
|
||||
actualSettings = SETDBData ? SETDBData.SettingsValue : undefined;
|
||||
|
||||
// AKTUELLE DATEN AUS FELD ÜBERNHEMEN
|
||||
var ActInput = document.getElementById('MonthGoalInput').value
|
||||
|
||||
const StudySettingsData = {
|
||||
SettingsID: 2,
|
||||
SettingsValue: parseInt(ActInput),
|
||||
}
|
||||
|
||||
// ÜBERPRÜFEN OB EIN EINTRAG MTI DER SETTINGS ID VORHANDEN IST - WENN JA DANN UPDATE WENN NEIN DANN NEUEN ERSTELLEN
|
||||
if ( actualSettings != undefined ) {
|
||||
updateEntry(actualSettingsEntryID, 'settings', StudySettingsData)
|
||||
} else {
|
||||
addEntry(StudySettingsData, 'settings')
|
||||
}
|
||||
|
||||
// UPDATE DER MONATSANSICHT
|
||||
|
||||
// Aktueller Monatsstand an Stunden
|
||||
const GesamtStundenMonat = parseInt(document.getElementById('MonthSumHours').value)
|
||||
document.getElementById('monthHours').innerText = GesamtStundenMonat + "/" + parseInt(ActInput)
|
||||
document.getElementById('prBarMonth').style.width = `${(100/parseInt(ActInput))*parseInt(GesamtStundenMonat)}%`
|
||||
|
||||
// Aktueller Rest an Stunden
|
||||
const rest = parseInt(ActInput) - parseInt(GesamtStundenMonat)
|
||||
if (rest > 0) {
|
||||
document.getElementById('monthRest').innerText = rest
|
||||
} else {
|
||||
document.getElementById('monthRest').innerText = 0
|
||||
}
|
||||
|
||||
})
|
||||
SETMonthGoalSaveButton.classList.add('d-none')
|
||||
})
|
||||
|
||||
|
||||
async function getActualDataAndFillView(monat = currentDate.getMonth(), jahr = currentDate.getFullYear()) {
|
||||
try {
|
||||
|
||||
// Einfüfen des aktuellen Monats
|
||||
document.getElementById('actualMonthName').innerHTML = monate[monat !== 12 ? monat : 0];
|
||||
console.log("AKTUELLER MONAT SYSTEM: ", monat)
|
||||
console.log("AKTUELLER MONAT REAL: ", currentDate.getMonth())
|
||||
|
||||
// Settings vor ausfüllen
|
||||
|
||||
// Monatswunsch-Stunden Abfragen
|
||||
var MonthGoal = 50
|
||||
|
||||
// DATEN VON SETTINGS ABFRAGEN
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
document.getElementById('studysinput').value = (result.find(element => element.SettingsID === 1) == undefined) ? 0 : parseInt(result.find(element => element.SettingsID === 1).SettingsValue)
|
||||
document.getElementById('MonthGoalInput').value = (result.find(element => element.SettingsID === 2) == undefined) ? 50 : parseInt(result.find(element => element.SettingsID === 2).SettingsValue)
|
||||
MonthGoal = (result.find(element => element.SettingsID === 2) == undefined) ? 50 : parseInt(result.find(element => element.SettingsID === 2).SettingsValue)
|
||||
});
|
||||
|
||||
// Filter funktion aufrufen und entsprechend mit dem aktuellen Datum füllen
|
||||
const filteredEntries = await filterEntriesByMonthYear(monat === 12 ? 1 : monat + 1, jahr);
|
||||
|
||||
// Tabelle mit den aktuellen informationen füllen (funktion triggern und Daten übergeben)
|
||||
fillTableWithEntries(filteredEntries)
|
||||
|
||||
// Ausgabe der Daten in den Log für DEV möglich
|
||||
// console.log('Gefilterte Einträge für September 2024:', filteredEntries);
|
||||
|
||||
// MonatsCard füllen
|
||||
|
||||
// Aktueller Monatsstand an Stunden
|
||||
const GesamtStundenMonat = formatDuration(sumDurations(filteredEntries,null,true), true)
|
||||
document.getElementById('monthHours').innerText = GesamtStundenMonat + "/" + MonthGoal
|
||||
document.getElementById('prBarMonth').style.width = `${(100/MonthGoal)*parseInt(GesamtStundenMonat)}%`
|
||||
document.getElementById('MonthSumHours').value = GesamtStundenMonat
|
||||
|
||||
// Aktueller Rest an Stunden
|
||||
const rest = MonthGoal - parseInt(GesamtStundenMonat)
|
||||
if (rest > 0) {
|
||||
document.getElementById('monthRest').innerText = rest
|
||||
} else {
|
||||
document.getElementById('monthRest').innerText = 0
|
||||
}
|
||||
|
||||
// Aufteilunge Stunden nach Dienst und LDC
|
||||
document.getElementById('monthHourService').innerText = formatDuration(sumDurations(filteredEntries,1,true), true)
|
||||
document.getElementById('monthHourLDC').innerText = formatDuration(sumDurations(filteredEntries,2,true), true)
|
||||
document.getElementById('monthHourOther').innerText = formatDuration(sumDurations(filteredEntries,3,true), true)
|
||||
|
||||
// Jahres Ansicht füllen
|
||||
var requestYear = currentDate.getFullYear()
|
||||
if (currentDate.getMonth() +1 >= 9) {
|
||||
requestYear += 1;
|
||||
}
|
||||
var yearData = await filterEntriesByMonthYear(null, requestYear)
|
||||
yearData = formatDuration(sumDurations(yearData, null, true), true)
|
||||
document.getElementById('yearHours').innerText = yearData
|
||||
document.getElementById('yearRest').innerText = 600 - parseInt(yearData)
|
||||
document.getElementById('prBarYear').style.width = `${Math.round((100/600)*parseInt(yearData))}%`
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Initialisierung oder Filterung:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// NEUES EINTRAG FORMULAR
|
||||
//
|
||||
|
||||
// Checkboxen-Verhalten: Nur eine Checkbox kann ausgewählt werden
|
||||
var ldcCheck = document.getElementById('ldcCheck');
|
||||
var sonstigesCheck = document.getElementById('sonstigesCheck');
|
||||
var sonstigesText = document.getElementById('sonstiges-text');
|
||||
var sonstigesInput = document.getElementById('sonstigesInput');
|
||||
|
||||
ldcCheck.addEventListener('change', function() {
|
||||
if (ldcCheck.checked) {
|
||||
sonstigesCheck.checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
sonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
sonstigesCheck.addEventListener('change', function() {
|
||||
checkInput();
|
||||
if (sonstigesCheck.checked) {
|
||||
ldcCheck.checked = false;
|
||||
sonstigesText.style.display = 'block';
|
||||
sonstigesInput.setAttribute('required', 'required'); // Setze required, wenn "Sonstiges" ausgewählt ist
|
||||
} else {
|
||||
sonstigesText.style.display = 'none';
|
||||
sonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
//
|
||||
// BEARBEITUNGS FORMULAR
|
||||
//
|
||||
|
||||
var edithours = document.getElementById('editStunden')
|
||||
var editMins = document.getElementById('editMinuten')
|
||||
var editldcCheck = document.getElementById('editldcCheck');
|
||||
var editsonstigesCheck = document.getElementById('editsonstigesCheck');
|
||||
var editsonstigesText = document.getElementById('edit-sonstiges-text');
|
||||
var editsonstigesInput = document.getElementById('editsonstigesInput');
|
||||
var EditSaveButton = document.getElementById('editEntrySubmitButton');
|
||||
|
||||
|
||||
// Änderungen überwachen damit die das Formular nicht zu früh abgesendet wird
|
||||
edithours.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
editMins.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
editsonstigesInput.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
|
||||
editldcCheck.addEventListener('change', function() {
|
||||
if (editldcCheck.checked) {
|
||||
editsonstigesCheck.checked = false;
|
||||
editsonstigesText.style.display = 'none';
|
||||
editsonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
editsonstigesCheck.addEventListener('change', function() {
|
||||
checkEditInput();
|
||||
if (editsonstigesCheck.checked) {
|
||||
editldcCheck.checked = false;
|
||||
editsonstigesText.style.display = 'block';
|
||||
editsonstigesInput.setAttribute('required', 'required'); // Setze required, wenn "Sonstiges" ausgewählt ist
|
||||
} else {
|
||||
editsonstigesText.style.display = 'none';
|
||||
editsonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('editEntrySubmitButton').addEventListener('click', () => {
|
||||
var ToEditID = document.getElementById('editEntryID').value
|
||||
// console.log(`ID: ${ToEditID}, Hours: ${editHours.values}, Minutes: ${editMins.values}`)
|
||||
|
||||
var type = 1
|
||||
|
||||
// Abfragen der Checkboxen
|
||||
const ldcCheck = document.getElementById('editldcCheck').checked
|
||||
if (ldcCheck) {
|
||||
type = 2
|
||||
document.getElementById('editldcCheck').checked = false
|
||||
} else if (document.getElementById('editsonstigesCheck').checked) {
|
||||
type = 3;
|
||||
document.getElementById('editsonstigesCheck').checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
}
|
||||
|
||||
updateEntry(parseInt(ToEditID), 'hours', {
|
||||
date: document.getElementById('editDatum').value,
|
||||
duration: (parseInt(edithours.value)*60) + parseInt(editMins.value),
|
||||
type: type,
|
||||
status: 1,
|
||||
sondertext: document.getElementById('editsonstigesInput').value,
|
||||
}).then(() => {
|
||||
// console.log('Eintrag aktualisiert.');
|
||||
location.reload()
|
||||
});
|
||||
})
|
||||
|
||||
document.getElementById('DeleteEntryButton').addEventListener('click', () => {
|
||||
const ToEditID = document.getElementById('editEntryID').value
|
||||
|
||||
deleteEntry(parseInt(ToEditID)).then(() => {
|
||||
console.log(`Löschen von Eintrag: ${ToEditID}`);
|
||||
location.reload()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
//
|
||||
// MONATSANSICHT Anpassen
|
||||
//
|
||||
|
||||
const monthEntrys = document.querySelectorAll('.monthEntrys');
|
||||
var diffMonth = 0
|
||||
|
||||
document.getElementById('dashbord_month_area').addEventListener('click', () =>{
|
||||
if (!document.getElementById('dashbord_month_area').classList.contains('ActiveMonth')) {
|
||||
document.getElementById('dashbord_month_area').classList.replace('closeMonthArea', 'openMonthArea')
|
||||
document.getElementById('dashbord_month_area').classList.add(['openMonthArea'])
|
||||
document.getElementById('dashbord_month_area').classList.add(['ActiveMonth'])
|
||||
document.getElementById('ActivitysTable').classList.add(['shrinkActivitys'])
|
||||
document.getElementById('ActivitysTable').classList.replace('growActivitys', 'shrinkActivitys')
|
||||
document.getElementById('dashbord_month_area').classList.remove('mt-4')
|
||||
// document.getElementById('dashbord_year_area').classList.add('fade-out')
|
||||
// Für jedes ausgewählte Element die neue Klasse hinzufügen
|
||||
setTimeout(function() {
|
||||
monthEntrys.forEach(element => {
|
||||
if (element.classList.contains('fade-out')) {
|
||||
element.classList.replace('fade-out', 'fade-in');
|
||||
} else {
|
||||
element.classList.add('fade-in');
|
||||
}
|
||||
});
|
||||
document.getElementById('MonthNameOfActual').innerHTML = "Aktuell:"
|
||||
}, 300);
|
||||
} else {
|
||||
document.getElementById('dashbord_month_area').classList.remove('ActiveMonth')
|
||||
document.getElementById('dashbord_month_area').classList.replace('openMonthArea', 'closeMonthArea')
|
||||
document.getElementById('ActivitysTable').classList.replace('shrinkActivitys', 'growActivitys')
|
||||
monthEntrys.forEach(element => {
|
||||
element.classList.replace('fade-in', 'fade-out');
|
||||
});
|
||||
document.getElementById('MonthNameOfActual').innerHTML = "Monat:"
|
||||
// document.getElementById('actualMonthName').innerHTML = monate[new Date().getMonth()];
|
||||
diffMonth = 0;
|
||||
getActualDataAndFillView(new Date().getMonth() + diffMonth)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('btn-month-before').addEventListener('click', () =>{
|
||||
console.log("CLICK-BEFORE: ", diffMonth)
|
||||
if (new Date().getMonth() + diffMonth == 0) {
|
||||
diffMonth = 11 - new Date().getMonth();
|
||||
getActualDataAndFillView(new Date().getMonth() + diffMonth)
|
||||
console.log("CLICK-BEFRORE-0TO11: ", diffMonth)
|
||||
} else {
|
||||
diffMonth = ((monate[new Date().getMonth() + diffMonth]) != 'September') ? diffMonth - 1 : diffMonth;
|
||||
// document.getElementById('actualMonthName').innerHTML = monate[new Date().getMonth() + diffMonth];
|
||||
getActualDataAndFillView(new Date().getMonth() + diffMonth)
|
||||
};
|
||||
console.log("CLICK-BEFORE-AFTER: ", diffMonth)
|
||||
});
|
||||
|
||||
document.getElementById('btn-month-next').addEventListener('click', () =>{
|
||||
console.log("CLICK-NEXT: ", diffMonth)
|
||||
if (new Date().getMonth() + diffMonth == 11) {
|
||||
diffMonth = 0 - new Date().getMonth();
|
||||
getActualDataAndFillView(new Date().getMonth() + diffMonth)
|
||||
console.log("CLICK-NEXT-11TO0: ", diffMonth)
|
||||
} else {
|
||||
diffMonth = ((monate[new Date().getMonth() + diffMonth]) != 'August') ? diffMonth + 1 : diffMonth;
|
||||
// document.getElementById('actualMonthName').innerHTML = monate[new Date().getMonth() + diffMonth];
|
||||
getActualDataAndFillView(new Date().getMonth() + diffMonth)
|
||||
};
|
||||
console.log("CLICK-NEXT-AFTER: ", diffMonth)
|
||||
})
|
||||
|
||||
function setNewCalsOfMonth(TheMonth) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// FUNKTIONEN LIBARY
|
||||
//
|
||||
|
||||
function checkInput() {
|
||||
// Eingabe feld für Beschreibung leeren wenn sonstiges abgewählt wird
|
||||
if (!sonstigesCheck.checked) {
|
||||
sonstigesInput.value = null;
|
||||
}
|
||||
// Debug ausgabe er Werte
|
||||
// console.log(`Check: ${sonstigesCheck.checked} - Input: ${sonstigesInput.value} - Stunden: ${hours.value} - Min: ${mins.value}`)
|
||||
|
||||
// Berechtigungen durch gehen damit Button verfügbar und Form abgesendet werden kann
|
||||
if ((sonstigesCheck.checked && sonstigesInput.value !== "") || !(sonstigesCheck.checked)) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Erste Bedingung: TRUE")
|
||||
if (hours.value && mins.value) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Zweite Bedingung: TRUE")
|
||||
if (!allowToSend) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Dritte Bedingung: TRUE")
|
||||
allowToSend = true;
|
||||
NewEntryButton.classList.remove('disabled');
|
||||
NewEntryButton.classList.replace('btn-light', 'btn-primary');
|
||||
}
|
||||
} else if (allowToSend) {
|
||||
allowToSend = false;
|
||||
NewEntryButton.classList.add('disabled');
|
||||
NewEntryButton.classList.replace('btn-primary', 'btn-light');
|
||||
}
|
||||
} else if (allowToSend) {
|
||||
allowToSend = false;
|
||||
NewEntryButton.classList.add('disabled');
|
||||
NewEntryButton.classList.replace('btn-primary', 'btn-light');
|
||||
}
|
||||
}
|
||||
|
||||
var editallowToSend = true;
|
||||
function checkEditInput() {
|
||||
var edithours = document.getElementById('editStunden')
|
||||
var editMins = document.getElementById('editMinuten')
|
||||
var editsonstigesCheck = document.getElementById('editsonstigesCheck');
|
||||
var editsonstigesText = document.getElementById('edit-sonstiges-text');
|
||||
var editsonstigesInput = document.getElementById('editsonstigesInput');
|
||||
var EditSaveButton = document.getElementById('editEntrySubmitButton');
|
||||
|
||||
// Eingabe feld für Beschreibung leeren wenn sonstiges abgewählt wird
|
||||
if (!editsonstigesCheck.checked) {
|
||||
editsonstigesInput.value = null;
|
||||
}
|
||||
|
||||
// Berechtigungen durch gehen damit Button verfügbar und Form abgesendet werden kann
|
||||
if ((editsonstigesCheck.checked && editsonstigesInput.value !== "") || !(editsonstigesCheck.checked)) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Erste Bedingung: TRUE")
|
||||
if (edithours.value && editMins.value) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Zweite Bedingung: TRUE")
|
||||
if (!editallowToSend) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Dritte Bedingung: TRUE")
|
||||
editallowToSend = true;
|
||||
EditSaveButton.classList.remove('disabled');
|
||||
EditSaveButton.classList.replace('btn-light', 'btn-primary');
|
||||
}
|
||||
} else if (editallowToSend) {
|
||||
editallowToSend = false;
|
||||
EditSaveButton.classList.add('disabled');
|
||||
EditSaveButton.classList.replace('btn-primary', 'btn-light');
|
||||
}
|
||||
} else if (editallowToSend) {
|
||||
editallowToSend = false;
|
||||
EditSaveButton.classList.add('disabled');
|
||||
EditSaveButton.classList.replace('btn-primary', 'btn-light');
|
||||
}
|
||||
// Debug ausgabe er Werte
|
||||
console.log(`Status: ${editallowToSend} - Check: ${editsonstigesCheck.checked} - Input: ${editsonstigesInput.value} - Stunden: ${edithours.value} - Min: ${editMinuten.value}`)
|
||||
}
|
||||
@ -1,906 +0,0 @@
|
||||
// Absende Formular Verwalten
|
||||
const hours = document.getElementById('stunden');
|
||||
const mins = document.getElementById('minuten');
|
||||
const NewEntryButton = document.getElementById('newEntrySubmitButton')
|
||||
const SETStudySaveButton = document.getElementById('SETStudySaveButton')
|
||||
const SETMonthGoalSaveButton = document.getElementById('SETMonthGoalSaveButton')
|
||||
const SETLdcNameSaveButton = document.getElementById('SETLdcNameSaveButton')
|
||||
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')
|
||||
const shareIcon = document.getElementById('share_icon')
|
||||
const shareReportModalEl = document.getElementById('shareReportModal')
|
||||
const shareReportStatus = document.getElementById('shareReportStatus')
|
||||
const shareReportPreview = document.getElementById('shareReportPreview')
|
||||
var allowToSend = false;
|
||||
const monate = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
|
||||
const currentDate = new Date();
|
||||
|
||||
// 55h-Regel (Jahresansicht): wird beim Start aus den Settings geladen und beim Umschalten
|
||||
// live aktualisiert, damit getActualDataAndFillView() sie synchron verwenden kann.
|
||||
var rule55Active = false;
|
||||
|
||||
// Individuell vergebbarer Name für "LDC" (SettingsID 4), überall im UI verwendet statt fest "LDC".
|
||||
var ldcLabel = 'LDC';
|
||||
|
||||
// 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).
|
||||
// Standardmäßig ausgeblendet, bis der Nutzer sie einmal selbst einblendet.
|
||||
var isFilterVisible = false;
|
||||
|
||||
// Ob die Aktivitäten-Tabelle gerade maximiert ist (Timer-Karte ist dann ausgeblendet).
|
||||
var isTableMaximized = false;
|
||||
|
||||
// Einträge des gerade angezeigten Monats, für den WhatsApp-Bericht (share_icon) wiederverwendet.
|
||||
var currentMonthEntries = [];
|
||||
|
||||
|
||||
//
|
||||
// Trigger
|
||||
//
|
||||
|
||||
//
|
||||
// ANONYME NUTZUNGSSTATISTIK (Admin-Dashboard)
|
||||
//
|
||||
|
||||
// Liest die zufällige Geräte-ID aus dem localStorage oder legt einmalig eine neue an.
|
||||
// Kein Bezug zu einer Person - rein lokal generiert, dient nur der Dashboard-Zählung.
|
||||
function getOrCreateDeviceId() {
|
||||
try {
|
||||
let id = localStorage.getItem('piomintDeviceId')
|
||||
if (!id) {
|
||||
id = (window.crypto && crypto.randomUUID) ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
localStorage.setItem('piomintDeviceId', id)
|
||||
}
|
||||
return id
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Meldet höchstens einmal pro Gerät und Tag ein anonymes "App genutzt"-Signal ans Backend.
|
||||
// Darf die App unter keinen Umständen blockieren oder zum Absturz bringen - daher überall try/catch.
|
||||
function pingUsageIfNeeded() {
|
||||
try {
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
if (localStorage.getItem('piomintLastPing') === today) {
|
||||
return
|
||||
}
|
||||
const deviceId = getOrCreateDeviceId()
|
||||
if (!deviceId) {
|
||||
return
|
||||
}
|
||||
const standalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches) || window.navigator.standalone === true
|
||||
|
||||
fetch('/app/track/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
device_id: deviceId,
|
||||
app_version: document.body.dataset.appVersion || '',
|
||||
standalone: standalone,
|
||||
}),
|
||||
}).then(() => {
|
||||
localStorage.setItem('piomintLastPing', today)
|
||||
}).catch(() => {
|
||||
// Kein Netz/Fehler beim Tracking - einfach beim nächsten Öffnen erneut versuchen
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Nutzungssignal konnte nicht gesendet werden:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn das Dokument komplett geladen ist
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
pingUsageIfNeeded()
|
||||
|
||||
initDB().then(async () => {
|
||||
console.log('IndexedDB erfolgreich initialisiert.');
|
||||
// Hier kannst du die App weiter initialisieren, z.B. Daten laden oder die UI aufbauen
|
||||
|
||||
// 55h-Regel Einstellung laden, bevor die erste Jahresansicht berechnet wird
|
||||
await loadRule55Setting();
|
||||
|
||||
// LDC-Bezeichnung laden, bevor die Tabelle erstmalig gefüllt wird
|
||||
await loadLdcLabel();
|
||||
|
||||
// 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
|
||||
getActualDataAndFillView();
|
||||
|
||||
//loadInitialData();
|
||||
}).catch((error) => {
|
||||
console.error('Fehler bei der Initialisierung der IndexedDB:', error);
|
||||
});
|
||||
});
|
||||
|
||||
// Behebt die Bootstrap-Konsolenwarnung "Blocked aria-hidden on an element because its
|
||||
// descendant retained focus": beim Schließen eines Modals (z.B. per "X"-Button) setzt
|
||||
// Bootstrap aria-hidden="true", obwohl der geklickte Button noch fokussiert ist. Fix: Fokus
|
||||
// vor dem Ausblenden gezielt vom Modal wegnehmen. Gilt für alle Modals der Seite.
|
||||
document.querySelectorAll('.modal').forEach(modalEl => {
|
||||
modalEl.addEventListener('hide.bs.modal', () => {
|
||||
if (document.activeElement && modalEl.contains(document.activeElement)) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Lädt die 55h-Regel Einstellung (SettingsID 3) aus der DB, setzt rule55Active
|
||||
// und synchronisiert die Checkbox sowie den Badge in der Jahres-Card.
|
||||
async function loadRule55Setting() {
|
||||
const result = await getAllEntries('settings')
|
||||
const setting = result.find(element => element.SettingsID === 3)
|
||||
rule55Active = (setting == undefined) ? false : !!parseInt(setting.SettingsValue)
|
||||
rule55hCheck.checked = rule55Active
|
||||
updateRule55Badge()
|
||||
}
|
||||
|
||||
// Zeigt/versteckt den "55h-Regel aktiv"-Hinweis an der Jahres-Card
|
||||
function updateRule55Badge() {
|
||||
document.getElementById('rule55Badge').classList.toggle('d-none', !rule55Active)
|
||||
}
|
||||
|
||||
// Einstellungen wenn die 55h-Regel umgeschaltet wird
|
||||
rule55hCheck.addEventListener('change', () => {
|
||||
rule55Active = rule55hCheck.checked
|
||||
updateRule55Badge()
|
||||
|
||||
const Rule55SettingsData = {
|
||||
SettingsID: 3,
|
||||
SettingsValue: rule55Active ? 1 : 0,
|
||||
}
|
||||
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
const SETDBData = result.find(element => element.SettingsID === 3)
|
||||
|
||||
if (SETDBData != undefined) {
|
||||
updateEntry(SETDBData.id, 'settings', Rule55SettingsData)
|
||||
} else {
|
||||
addEntry(Rule55SettingsData, 'settings')
|
||||
}
|
||||
|
||||
// Jahresansicht mit/ohne Deckel neu berechnen
|
||||
getActualDataAndFillView(cursorMonth, cursorYear)
|
||||
})
|
||||
})
|
||||
|
||||
//
|
||||
// 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
|
||||
hours.addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
mins.addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
document.getElementById('sonstigesInput').addEventListener('input', () => {
|
||||
checkInput();
|
||||
})
|
||||
|
||||
// Button-Status beim Öffnen des Modals einmal anhand der Standardwerte (z.B. Minuten = 0) setzen,
|
||||
// da sonst ohne ein tatsächliches 'input'-Event der Button trotz gültiger Vorbelegung deaktiviert bleibt
|
||||
document.getElementById('newEntryModal').addEventListener('show.bs.modal', () => {
|
||||
checkInput();
|
||||
})
|
||||
|
||||
// Einstellungen wenn Neuer Eintrag erstellt wird
|
||||
NewEntryButton.addEventListener('click', () => {
|
||||
|
||||
if (allowToSend === true) {
|
||||
var type = 1
|
||||
|
||||
// Abfragen der Checkboxen
|
||||
const ldcCheck = document.getElementById('ldcCheck').checked
|
||||
if (ldcCheck) {
|
||||
type = 2
|
||||
document.getElementById('ldcCheck').checked = false
|
||||
} else if (document.getElementById('sonstigesCheck').checked) {
|
||||
type = 3;
|
||||
document.getElementById('sonstigesCheck').checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
}
|
||||
|
||||
// Berechnen der Duration (Stunden sind optional -> ohne Eingabe wird 0 gespeichert)
|
||||
const duration = ((parseInt(hours.value) || 0)*60) + (parseInt(mins.value) || 0)
|
||||
|
||||
const newEntryData = {
|
||||
date: document.getElementById('datum').value,
|
||||
duration: duration,
|
||||
type: type,
|
||||
status: 1,
|
||||
sondertext: document.getElementById('sonstigesInput').value,
|
||||
partner: document.getElementById('partnerInput').value.trim(),
|
||||
}
|
||||
|
||||
addEntry(newEntryData, 'hours').then((id) => {
|
||||
// console.log('Eintrag hinzugefügt mit ID:', id);
|
||||
location.reload()
|
||||
});
|
||||
|
||||
hours.value = null
|
||||
mins.value = null
|
||||
document.getElementById('sonstigesInput').value = null
|
||||
document.getElementById('partnerInput').value = null
|
||||
checkInput()
|
||||
}
|
||||
})
|
||||
|
||||
// SETTINGS ACTIONS
|
||||
|
||||
SETStudySaveButton.addEventListener('click', () => {
|
||||
|
||||
var actualSettings = 0
|
||||
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
const SETDBData = result.find(element => element.SettingsID === 1)
|
||||
actualSettingsEntryID = SETDBData ? SETDBData.id : undefined;
|
||||
actualSettings = SETDBData ? SETDBData.SettingsValue : undefined;
|
||||
|
||||
// AKTUELLE DATEN AUS FELD ÜBERNHEMEN
|
||||
var ActInput = document.getElementById('studysinput').value
|
||||
|
||||
const StudySettingsData = {
|
||||
SettingsID: 1,
|
||||
SettingsValue: parseInt(ActInput),
|
||||
}
|
||||
|
||||
// ÜBERPRÜFEN OB EIN EINTRAG MTI DER SETTINGS ID VORHANDEN IST - WENN JA DANN UPDATE WENN NEIN DANN NEUEN ERSTELLEN
|
||||
if ( actualSettings != undefined ) {
|
||||
updateEntry(actualSettingsEntryID, 'settings', StudySettingsData)
|
||||
} else {
|
||||
addEntry(StudySettingsData, 'settings')
|
||||
}
|
||||
})
|
||||
|
||||
SETStudySaveButton.classList.add('d-none')
|
||||
})
|
||||
|
||||
SETMonthGoalSaveButton.addEventListener('click', () => {
|
||||
getAllEntries('settings')
|
||||
.then(function(result){
|
||||
|
||||
const SETDBData = result.find(element => element.SettingsID === 2)
|
||||
actualSettingsEntryID = SETDBData ? SETDBData.id : undefined;
|
||||
actualSettings = SETDBData ? SETDBData.SettingsValue : undefined;
|
||||
|
||||
// AKTUELLE DATEN AUS FELD ÜBERNHEMEN
|
||||
var ActInput = document.getElementById('MonthGoalInput').value
|
||||
|
||||
const StudySettingsData = {
|
||||
SettingsID: 2,
|
||||
SettingsValue: parseInt(ActInput),
|
||||
}
|
||||
|
||||
// ÜBERPRÜFEN OB EIN EINTRAG MTI DER SETTINGS ID VORHANDEN IST - WENN JA DANN UPDATE WENN NEIN DANN NEUEN ERSTELLEN
|
||||
if ( actualSettings != undefined ) {
|
||||
updateEntry(actualSettingsEntryID, 'settings', StudySettingsData)
|
||||
} else {
|
||||
addEntry(StudySettingsData, 'settings')
|
||||
}
|
||||
|
||||
// UPDATE DER MONATSANSICHT
|
||||
|
||||
// Aktueller Monatsstand an Stunden
|
||||
const GesamtStundenMonat = parseInt(document.getElementById('MonthSumHours').value)
|
||||
document.getElementById('monthHours').innerText = GesamtStundenMonat + "/" + parseInt(ActInput)
|
||||
document.getElementById('prBarMonth').style.width = `${(100/parseInt(ActInput))*parseInt(GesamtStundenMonat)}%`
|
||||
|
||||
// Aktueller Rest an Stunden
|
||||
const rest = parseInt(ActInput) - parseInt(GesamtStundenMonat)
|
||||
if (rest > 0) {
|
||||
document.getElementById('monthRest').innerText = rest
|
||||
} else {
|
||||
document.getElementById('monthRest').innerText = 0
|
||||
}
|
||||
|
||||
})
|
||||
SETMonthGoalSaveButton.classList.add('d-none')
|
||||
})
|
||||
|
||||
// Lädt die individuelle LDC-Bezeichnung (SettingsID 4) aus der DB, setzt ldcLabel,
|
||||
// füllt das Einstellungen-Feld vor und wendet den Namen überall im UI an.
|
||||
async function loadLdcLabel() {
|
||||
const result = await getAllEntries('settings')
|
||||
const setting = result.find(element => element.SettingsID === 4)
|
||||
ldcLabel = (setting == undefined || !setting.SettingsValue) ? 'LDC' : setting.SettingsValue
|
||||
document.getElementById('ldcNameInput').value = ldcLabel
|
||||
applyLdcLabel()
|
||||
}
|
||||
|
||||
// Setzt die aktuelle LDC-Bezeichnung überall dort, wo sie im UI (außerhalb der Tabelle) auftaucht.
|
||||
// Die Tabelle selbst bekommt den Namen bei jedem Render über fillTableWithEntries(...).
|
||||
function applyLdcLabel() {
|
||||
document.getElementById('ldcCheckLabel').innerText = ldcLabel
|
||||
document.getElementById('editldcCheckLabel').innerText = ldcLabel
|
||||
document.getElementById('monthHourLDCLabel').innerText = ldcLabel
|
||||
}
|
||||
|
||||
SETLdcNameSaveButton.addEventListener('click', () => {
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
const SETDBData = result.find(element => element.SettingsID === 4)
|
||||
actualSettingsEntryID = SETDBData ? SETDBData.id : undefined;
|
||||
actualSettings = SETDBData ? SETDBData.SettingsValue : undefined;
|
||||
|
||||
// AKTUELLE DATEN AUS FELD ÜBERNHEMEN - bei leerem Feld auf "LDC" zurückfallen
|
||||
var ActInput = document.getElementById('ldcNameInput').value.trim() || 'LDC'
|
||||
document.getElementById('ldcNameInput').value = ActInput
|
||||
|
||||
const LdcNameSettingsData = {
|
||||
SettingsID: 4,
|
||||
SettingsValue: ActInput,
|
||||
}
|
||||
|
||||
// ÜBERPRÜFEN OB EIN EINTRAG MTI DER SETTINGS ID VORHANDEN IST - WENN JA DANN UPDATE WENN NEIN DANN NEUEN ERSTELLEN
|
||||
if ( actualSettings != undefined ) {
|
||||
updateEntry(actualSettingsEntryID, 'settings', LdcNameSettingsData)
|
||||
} else {
|
||||
addEntry(LdcNameSettingsData, 'settings')
|
||||
}
|
||||
|
||||
ldcLabel = ActInput
|
||||
applyLdcLabel()
|
||||
// Tabelle neu füllen, damit die Typ-Spalte den neuen Namen sofort übernimmt
|
||||
getActualDataAndFillView(cursorMonth, cursorYear)
|
||||
})
|
||||
|
||||
SETLdcNameSaveButton.classList.add('d-none')
|
||||
})
|
||||
|
||||
//
|
||||
// BERICHT FÜR WHATSAPP TEILEN
|
||||
//
|
||||
|
||||
// Baut den vorformatierten Bericht für den gerade angezeigten Monat (WhatsApp-Formatierung: *fett*)
|
||||
function buildMonthReportText() {
|
||||
const monthLabel = `${monate[cursorMonth]} ${cursorYear}`
|
||||
const dienstHours = document.getElementById('monthHourService').innerText
|
||||
const bibelstudien = document.getElementById('studysinput').value
|
||||
const ldcHours = document.getElementById('monthHourLDC').innerText
|
||||
|
||||
// Sonstige Einträge nach ihrem individuellen Bemerkungstext gruppieren und Stunden je Gruppe summieren
|
||||
const sonstigeGroups = {}
|
||||
currentMonthEntries
|
||||
.filter(entry => entry.type === 3)
|
||||
.forEach(entry => {
|
||||
const label = entry.sondertext || 'Sonstiges'
|
||||
sonstigeGroups[label] = (sonstigeGroups[label] || 0) + entry.duration
|
||||
})
|
||||
|
||||
var message = `Bericht: *${monthLabel}*\n`
|
||||
message += `Dienst: ${dienstHours}h\n`
|
||||
message += `Bibelstudien: ${bibelstudien}\n`
|
||||
message += `Bemerkungen:\n`
|
||||
message += `• ${ldcLabel}: ${ldcHours}h`
|
||||
|
||||
Object.keys(sonstigeGroups).forEach(label => {
|
||||
message += `\n• ${label}: ${formatDuration(sonstigeGroups[label], true)}h`
|
||||
})
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
// Kopiert Text in die Zwischenablage - mit Fallback für Umgebungen ohne Clipboard-API
|
||||
async function copyTextToClipboard(text) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
return
|
||||
}
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
|
||||
// Bestätigung als Vorschau-Modal statt kurzem Icon-Blitz: so sieht man den Bericht direkt
|
||||
// (auch praktisch, wenn man ihn gar nicht kopieren, sondern nur ansehen möchte) und kann
|
||||
// in Ruhe wieder schließen.
|
||||
shareIcon.addEventListener('click', () => {
|
||||
const message = buildMonthReportText()
|
||||
shareReportPreview.innerText = message
|
||||
|
||||
copyTextToClipboard(message)
|
||||
.then(() => {
|
||||
shareReportStatus.innerText = '✓ In die Zwischenablage kopiert'
|
||||
shareReportStatus.style.color = 'rgb(120,170,86)'
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Bericht konnte nicht in die Zwischenablage kopiert werden:', error)
|
||||
shareReportStatus.innerText = 'Kopieren nicht möglich - Text unten manuell markieren'
|
||||
shareReportStatus.style.color = '#dc3545'
|
||||
})
|
||||
.finally(() => {
|
||||
new bootstrap.Modal(shareReportModalEl).show()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
async function getActualDataAndFillView(monat = currentDate.getMonth(), jahr = currentDate.getFullYear()) {
|
||||
try {
|
||||
|
||||
// Einfüfen des aktuellen Monats
|
||||
document.getElementById('actualMonthName').innerHTML = monate[monat !== 12 ? monat : 0];
|
||||
console.log("AKTUELLER MONAT SYSTEM: ", monat)
|
||||
console.log("AKTUELLER MONAT REAL: ", currentDate.getMonth())
|
||||
|
||||
// Settings vor ausfüllen
|
||||
|
||||
// Monatswunsch-Stunden Abfragen
|
||||
var MonthGoal = 50
|
||||
|
||||
// DATEN VON SETTINGS ABFRAGEN
|
||||
getAllEntries('settings')
|
||||
.then(function(result) {
|
||||
document.getElementById('studysinput').value = (result.find(element => element.SettingsID === 1) == undefined) ? 0 : parseInt(result.find(element => element.SettingsID === 1).SettingsValue)
|
||||
document.getElementById('MonthGoalInput').value = (result.find(element => element.SettingsID === 2) == undefined) ? 50 : parseInt(result.find(element => element.SettingsID === 2).SettingsValue)
|
||||
MonthGoal = (result.find(element => element.SettingsID === 2) == undefined) ? 50 : parseInt(result.find(element => element.SettingsID === 2).SettingsValue)
|
||||
});
|
||||
|
||||
// Filter funktion aufrufen und entsprechend mit dem aktuellen Datum füllen
|
||||
const filteredEntries = await filterEntriesByMonthYear(monat === 12 ? 1 : monat + 1, jahr);
|
||||
currentMonthEntries = filteredEntries
|
||||
|
||||
console.log("update-complete")
|
||||
console.log(jahr)
|
||||
|
||||
// Tabelle mit den aktuellen informationen füllen (funktion triggern und Daten übergeben)
|
||||
fillTableWithEntries(filteredEntries, sortField, sortDirection, ldcLabel)
|
||||
|
||||
// Ausgabe der Daten in den Log für DEV möglich
|
||||
// console.log('Gefilterte Einträge für September 2024:', filteredEntries);
|
||||
|
||||
// MonatsCard füllen
|
||||
|
||||
// Aktueller Monatsstand an Stunden
|
||||
const GesamtStundenMonat = formatDuration(sumDurations(filteredEntries,null,true), true)
|
||||
document.getElementById('monthHours').innerText = GesamtStundenMonat + "/" + MonthGoal
|
||||
document.getElementById('prBarMonth').style.width = `${(100/MonthGoal)*parseInt(GesamtStundenMonat)}%`
|
||||
document.getElementById('MonthSumHours').value = GesamtStundenMonat
|
||||
|
||||
// Aktueller Rest an Stunden
|
||||
const rest = MonthGoal - parseInt(GesamtStundenMonat)
|
||||
if (rest > 0) {
|
||||
document.getElementById('monthRest').innerText = rest
|
||||
} else {
|
||||
document.getElementById('monthRest').innerText = 0
|
||||
}
|
||||
|
||||
// Aufteilunge Stunden nach Dienst und LDC
|
||||
document.getElementById('monthHourService').innerText = formatDuration(sumDurations(filteredEntries,1,true), true)
|
||||
document.getElementById('monthHourLDC').innerText = formatDuration(sumDurations(filteredEntries,2,true), true)
|
||||
document.getElementById('monthHourOther').innerText = formatDuration(sumDurations(filteredEntries,3,true), true)
|
||||
|
||||
// Jahres Ansicht füllen (Dienstjahr, gekoppelt an den gerade angezeigten Monat/Jahr)
|
||||
// Bei aktiver 55h-Regel wird jeder Monat mit LDC/Sonstiges-Anteil auf max. 55h gedeckelt,
|
||||
// reine Dienststunden zählen dabei immer voll (siehe calculateServiceYearTotal).
|
||||
var requestServiceYear = getServiceYear(new Date(jahr, monat, 1));
|
||||
var yearEntries = await filterEntriesByServiceYear(requestServiceYear)
|
||||
var yearData = formatDuration(calculateServiceYearTotal(yearEntries, rule55Active), true)
|
||||
document.getElementById('yearHours').innerText = yearData
|
||||
document.getElementById('yearRest').innerText = 600 - parseInt(yearData)
|
||||
document.getElementById('prBarYear').style.width = `${Math.round((100/600)*parseInt(yearData))}%`
|
||||
updateRule55Badge()
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Initialisierung oder Filterung:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// NEUES EINTRAG FORMULAR
|
||||
//
|
||||
|
||||
// Checkboxen-Verhalten: Nur eine Checkbox kann ausgewählt werden
|
||||
var ldcCheck = document.getElementById('ldcCheck');
|
||||
var sonstigesCheck = document.getElementById('sonstigesCheck');
|
||||
var sonstigesText = document.getElementById('sonstiges-text');
|
||||
var sonstigesInput = document.getElementById('sonstigesInput');
|
||||
|
||||
ldcCheck.addEventListener('change', function() {
|
||||
if (ldcCheck.checked) {
|
||||
sonstigesCheck.checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
sonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
sonstigesCheck.addEventListener('change', function() {
|
||||
checkInput();
|
||||
if (sonstigesCheck.checked) {
|
||||
ldcCheck.checked = false;
|
||||
sonstigesText.style.display = 'block';
|
||||
sonstigesInput.setAttribute('required', 'required'); // Setze required, wenn "Sonstiges" ausgewählt ist
|
||||
} else {
|
||||
sonstigesText.style.display = 'none';
|
||||
sonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
//
|
||||
// BEARBEITUNGS FORMULAR
|
||||
//
|
||||
|
||||
var edithours = document.getElementById('editStunden')
|
||||
var editMins = document.getElementById('editMinuten')
|
||||
var editldcCheck = document.getElementById('editldcCheck');
|
||||
var editsonstigesCheck = document.getElementById('editsonstigesCheck');
|
||||
var editsonstigesText = document.getElementById('edit-sonstiges-text');
|
||||
var editsonstigesInput = document.getElementById('editsonstigesInput');
|
||||
var EditSaveButton = document.getElementById('editEntrySubmitButton');
|
||||
|
||||
|
||||
// Änderungen überwachen damit die das Formular nicht zu früh abgesendet wird
|
||||
edithours.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
editMins.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
editsonstigesInput.addEventListener('input', () => {
|
||||
checkEditInput();
|
||||
console.log(this.value)
|
||||
})
|
||||
|
||||
editldcCheck.addEventListener('change', function() {
|
||||
if (editldcCheck.checked) {
|
||||
editsonstigesCheck.checked = false;
|
||||
editsonstigesText.style.display = 'none';
|
||||
editsonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
editsonstigesCheck.addEventListener('change', function() {
|
||||
checkEditInput();
|
||||
if (editsonstigesCheck.checked) {
|
||||
editldcCheck.checked = false;
|
||||
editsonstigesText.style.display = 'block';
|
||||
editsonstigesInput.setAttribute('required', 'required'); // Setze required, wenn "Sonstiges" ausgewählt ist
|
||||
} else {
|
||||
editsonstigesText.style.display = 'none';
|
||||
editsonstigesInput.removeAttribute('required'); // Entferne required, wenn "Sonstiges" abgewählt ist
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('editEntrySubmitButton').addEventListener('click', () => {
|
||||
if (!editallowToSend) {
|
||||
return
|
||||
}
|
||||
|
||||
var ToEditID = document.getElementById('editEntryID').value
|
||||
// console.log(`ID: ${ToEditID}, Hours: ${editHours.values}, Minutes: ${editMins.values}`)
|
||||
|
||||
var type = 1
|
||||
|
||||
// Abfragen der Checkboxen
|
||||
const ldcCheck = document.getElementById('editldcCheck').checked
|
||||
if (ldcCheck) {
|
||||
type = 2
|
||||
document.getElementById('editldcCheck').checked = false
|
||||
} else if (document.getElementById('editsonstigesCheck').checked) {
|
||||
type = 3;
|
||||
document.getElementById('editsonstigesCheck').checked = false;
|
||||
sonstigesText.style.display = 'none';
|
||||
}
|
||||
|
||||
updateEntry(parseInt(ToEditID), 'hours', {
|
||||
date: document.getElementById('editDatum').value,
|
||||
duration: ((parseInt(edithours.value) || 0)*60) + (parseInt(editMins.value) || 0),
|
||||
type: type,
|
||||
status: 1,
|
||||
sondertext: document.getElementById('editsonstigesInput').value,
|
||||
partner: document.getElementById('editPartnerInput').value.trim(),
|
||||
}).then(() => {
|
||||
// console.log('Eintrag aktualisiert.');
|
||||
location.reload()
|
||||
});
|
||||
})
|
||||
|
||||
document.getElementById('DeleteEntryButton').addEventListener('click', () => {
|
||||
const ToEditID = document.getElementById('editEntryID').value
|
||||
|
||||
deleteEntry(parseInt(ToEditID)).then(() => {
|
||||
console.log(`Löschen von Eintrag: ${ToEditID}`);
|
||||
location.reload()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
//
|
||||
// MONATSANSICHT Anpassen
|
||||
//
|
||||
|
||||
const monthEntrys = document.querySelectorAll('.monthEntrys');
|
||||
|
||||
// Cursor für die Monatsnavigation: echtes Kalenderjahr + 0-basierter Monat (JS-Konvention).
|
||||
// Startet auf dem aktuellen Monat/Jahr und wird bei Vor/Zurück direkt fortgeschrieben,
|
||||
// unabhängig vom Dienstjahr-Label - dadurch keine künstliche Sperre mehr bei Sept/Aug.
|
||||
var cursorMonth = new Date().getMonth();
|
||||
var cursorYear = new Date().getFullYear();
|
||||
|
||||
document.getElementById('dashbord_month_area').addEventListener('click', () =>{
|
||||
if (!document.getElementById('dashbord_month_area').classList.contains('ActiveMonth')) {
|
||||
document.getElementById('dashbord_month_area').classList.replace('closeMonthArea', 'openMonthArea')
|
||||
document.getElementById('dashbord_month_area').classList.add(['openMonthArea'])
|
||||
document.getElementById('dashbord_month_area').classList.add(['ActiveMonth'])
|
||||
// ActivitysTable ist jetzt flex:1 1 auto (siehe custom.css) und macht der wachsenden
|
||||
// Monatskarte automatisch Platz - kein manuelles shrinkActivitys/growActivitys mehr nötig,
|
||||
// das würde der Flex-Berechnung sonst mit einer festen vh-Höhe in die Quere kommen.
|
||||
document.getElementById('dashbord_month_area').classList.remove('mt-3')
|
||||
// document.getElementById('dashbord_year_area').classList.add('fade-out')
|
||||
// Für jedes ausgewählte Element die neue Klasse hinzufügen
|
||||
setTimeout(function() {
|
||||
monthEntrys.forEach(element => {
|
||||
if (element.classList.contains('fade-out')) {
|
||||
element.classList.replace('fade-out', 'fade-in');
|
||||
} else {
|
||||
element.classList.add('fade-in');
|
||||
}
|
||||
});
|
||||
document.getElementById('MonthNameOfActual').innerHTML = "Aktuell:"
|
||||
}, 300);
|
||||
} else {
|
||||
document.getElementById('dashbord_month_area').classList.remove('ActiveMonth')
|
||||
document.getElementById('dashbord_month_area').classList.replace('openMonthArea', 'closeMonthArea')
|
||||
document.getElementById('dashbord_month_area').classList.add('mt-3')
|
||||
monthEntrys.forEach(element => {
|
||||
element.classList.replace('fade-in', 'fade-out');
|
||||
});
|
||||
document.getElementById('MonthNameOfActual').innerHTML = "Monat:"
|
||||
cursorMonth = new Date().getMonth();
|
||||
cursorYear = new Date().getFullYear();
|
||||
getActualDataAndFillView(cursorMonth, cursorYear)
|
||||
}
|
||||
})
|
||||
|
||||
document.getElementById('btn-month-before').addEventListener('click', () =>{
|
||||
if (cursorMonth === 0) {
|
||||
// Januar -> Dezember des Vorjahres
|
||||
cursorMonth = 11;
|
||||
cursorYear -= 1;
|
||||
} else {
|
||||
cursorMonth -= 1;
|
||||
}
|
||||
getActualDataAndFillView(cursorMonth, cursorYear)
|
||||
});
|
||||
|
||||
document.getElementById('btn-month-next').addEventListener('click', () =>{
|
||||
if (cursorMonth === 11) {
|
||||
// Dezember -> Januar des Folgejahres
|
||||
cursorMonth = 0;
|
||||
cursorYear += 1;
|
||||
} else {
|
||||
cursorMonth += 1;
|
||||
}
|
||||
getActualDataAndFillView(cursorMonth, cursorYear)
|
||||
})
|
||||
|
||||
function setNewCalsOfMonth(TheMonth) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// FUNKTIONEN LIBARY
|
||||
//
|
||||
|
||||
function checkInput() {
|
||||
// Eingabe feld für Beschreibung leeren wenn sonstiges abgewählt wird
|
||||
if (!sonstigesCheck.checked) {
|
||||
sonstigesInput.value = null;
|
||||
}
|
||||
// Debug ausgabe er Werte
|
||||
// console.log(`Check: ${sonstigesCheck.checked} - Input: ${sonstigesInput.value} - Stunden: ${hours.value} - Min: ${mins.value}`)
|
||||
|
||||
// Berechtigungen durch gehen damit Button verfügbar und Form abgesendet werden kann
|
||||
if ((sonstigesCheck.checked && sonstigesInput.value !== "") || !(sonstigesCheck.checked)) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Erste Bedingung: TRUE")
|
||||
if (mins.value) { // Stunden sind optional, Minuten bleiben Pflicht
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Zweite Bedingung: TRUE")
|
||||
if (!allowToSend) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Dritte Bedingung: TRUE")
|
||||
allowToSend = true;
|
||||
NewEntryButton.classList.remove('disabled');
|
||||
NewEntryButton.classList.replace('btn-light', 'btn-primary');
|
||||
NewEntryButton.disabled = false;
|
||||
}
|
||||
} else if (allowToSend) {
|
||||
allowToSend = false;
|
||||
NewEntryButton.classList.add('disabled');
|
||||
NewEntryButton.classList.replace('btn-primary', 'btn-light');
|
||||
NewEntryButton.disabled = true;
|
||||
}
|
||||
} else if (allowToSend) {
|
||||
allowToSend = false;
|
||||
NewEntryButton.classList.add('disabled');
|
||||
NewEntryButton.classList.replace('btn-primary', 'btn-light');
|
||||
NewEntryButton.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
var editallowToSend = true;
|
||||
function checkEditInput() {
|
||||
var edithours = document.getElementById('editStunden')
|
||||
var editMins = document.getElementById('editMinuten')
|
||||
var editsonstigesCheck = document.getElementById('editsonstigesCheck');
|
||||
var editsonstigesText = document.getElementById('edit-sonstiges-text');
|
||||
var editsonstigesInput = document.getElementById('editsonstigesInput');
|
||||
var EditSaveButton = document.getElementById('editEntrySubmitButton');
|
||||
|
||||
// Eingabe feld für Beschreibung leeren wenn sonstiges abgewählt wird
|
||||
if (!editsonstigesCheck.checked) {
|
||||
editsonstigesInput.value = null;
|
||||
}
|
||||
|
||||
// Berechtigungen durch gehen damit Button verfügbar und Form abgesendet werden kann
|
||||
if ((editsonstigesCheck.checked && editsonstigesInput.value !== "") || !(editsonstigesCheck.checked)) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Erste Bedingung: TRUE")
|
||||
if (editMins.value) { // Stunden sind optional, Minuten bleiben Pflicht
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Zweite Bedingung: TRUE")
|
||||
if (!editallowToSend) {
|
||||
// Debug Ausgabe für Bedingungsstufen
|
||||
// console.log("Dritte Bedingung: TRUE")
|
||||
editallowToSend = true;
|
||||
EditSaveButton.classList.remove('disabled');
|
||||
EditSaveButton.classList.replace('btn-light', 'btn-primary');
|
||||
EditSaveButton.disabled = false;
|
||||
}
|
||||
} else if (editallowToSend) {
|
||||
editallowToSend = false;
|
||||
EditSaveButton.classList.add('disabled');
|
||||
EditSaveButton.classList.replace('btn-primary', 'btn-light');
|
||||
EditSaveButton.disabled = true;
|
||||
}
|
||||
} else if (editallowToSend) {
|
||||
editallowToSend = false;
|
||||
EditSaveButton.classList.add('disabled');
|
||||
EditSaveButton.classList.replace('btn-primary', 'btn-light');
|
||||
EditSaveButton.disabled = true;
|
||||
}
|
||||
// Debug ausgabe er Werte
|
||||
console.log(`Status: ${editallowToSend} - Check: ${editsonstigesCheck.checked} - Input: ${editsonstigesInput.value} - Stunden: ${edithours.value} - Min: ${editMins.value}`)
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
def parse_user_agent(user_agent):
|
||||
"""Sehr einfache, dependency-freie Grobzuordnung von Plattform/Browser aus dem
|
||||
User-Agent-String - reicht für die Dashboard-Statistik, muss nicht exakt sein."""
|
||||
ua = (user_agent or "").lower()
|
||||
|
||||
if "iphone" in ua or "ipad" in ua or "ipod" in ua:
|
||||
platform = "iOS"
|
||||
elif "android" in ua:
|
||||
platform = "Android"
|
||||
elif "windows" in ua:
|
||||
platform = "Windows"
|
||||
elif "macintosh" in ua or "mac os x" in ua:
|
||||
platform = "macOS"
|
||||
elif "linux" in ua:
|
||||
platform = "Linux"
|
||||
else:
|
||||
platform = "Unbekannt"
|
||||
|
||||
if "edg/" in ua or "edga" in ua or "edgios" in ua:
|
||||
browser = "Edge"
|
||||
elif "opr/" in ua or "opera" in ua:
|
||||
browser = "Opera"
|
||||
elif "firefox" in ua or "fxios" in ua:
|
||||
browser = "Firefox"
|
||||
elif "crios" in ua or "chrome" in ua:
|
||||
browser = "Chrome"
|
||||
elif "safari" in ua:
|
||||
browser = "Safari"
|
||||
else:
|
||||
browser = "Unbekannt"
|
||||
|
||||
return platform, browser
|
||||
@ -1,7 +0,0 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def static_version(request):
|
||||
"""Stellt STATIC_VERSION allen Templates zur Verfügung, um CSS/JS-URLs mit
|
||||
?v=<version> zu versehen (Cache-Busting bei Bestandsnutzern, siehe settings.py)."""
|
||||
return {'STATIC_VERSION': getattr(settings, 'STATIC_VERSION', '')}
|
||||
Loading…
Reference in new issue