@ -4,7 +4,6 @@ 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' )
@ -18,10 +17,6 @@ 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 ( ) ;
@ -30,83 +25,23 @@ const currentDate = new Date();
// 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 ;
var isFilterVisible = true ;
// 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
@ -114,9 +49,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
// 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 ( ) ;
@ -132,18 +64,6 @@ document.addEventListener('DOMContentLoaded', (event) => {
} ) ;
} ) ;
// 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 ( ) {
@ -327,7 +247,7 @@ NewEntryButton.addEventListener('click', () => {
}
// Berechnen der Duration (Stunden sind optional -> ohne Eingabe wird 0 gespeichert)
const duration = ( ( parseInt ( hours . value ) || 0 ) * 60 ) + ( parseInt ( mins . value ) || 0 )
const duration = ( ( parseInt ( hours . value ) || 0 ) * 60 ) + parseInt ( mins . value )
const newEntryData = {
date : document . getElementById ( 'datum' ) . value ,
@ -335,7 +255,6 @@ NewEntryButton.addEventListener('click', () => {
type : type ,
status : 1 ,
sondertext : document . getElementById ( 'sonstigesInput' ) . value ,
partner : document . getElementById ( 'partnerInput' ) . value . trim ( ) ,
}
addEntry ( newEntryData , 'hours' ) . then ( ( id ) => {
@ -346,7 +265,6 @@ NewEntryButton.addEventListener('click', () => {
hours . value = null
mins . value = null
document . getElementById ( 'sonstigesInput' ) . value = null
document . getElementById ( 'partnerInput' ) . value = null
checkInput ( )
}
} )
@ -424,127 +342,6 @@ SETMonthGoalSaveButton.addEventListener('click', () => {
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 {
@ -569,13 +366,12 @@ async function getActualDataAndFillView(monat = currentDate.getMonth(), jahr = c
// 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 )
fillTableWithEntries ( filteredEntries , sortField , sortDirection )
// Ausgabe der Daten in den Log für DEV möglich
// console.log('Gefilterte Einträge für September 2024:', filteredEntries);
@ -700,10 +496,6 @@ editsonstigesCheck.addEventListener('change', function() {
document . getElementById ( 'editEntrySubmitButton' ) . addEventListener ( 'click' , ( ) => {
if ( ! editallowToSend ) {
return
}
var ToEditID = document . getElementById ( 'editEntryID' ) . value
// console.log(`ID: ${ToEditID}, Hours: ${editHours.values}, Minutes: ${editMins.values}`)
@ -722,11 +514,10 @@ document.getElementById('editEntrySubmitButton').addEventListener('click', () =>
updateEntry ( parseInt ( ToEditID ) , 'hours' , {
date : document . getElementById ( 'editDatum' ) . value ,
duration : ( ( parseInt ( edithours . value ) || 0 ) * 60 ) + ( parseInt ( editMins . value ) || 0 ) ,
duration : ( ( parseInt ( edithours . value ) || 0 ) * 60 ) + parseInt ( editMins . value ) ,
type : type ,
status : 1 ,
sondertext : document . getElementById ( 'editsonstigesInput' ) . value ,
partner : document . getElementById ( 'editPartnerInput' ) . value . trim ( ) ,
} ) . then ( ( ) => {
// console.log('Eintrag aktualisiert.');
location . reload ( )
@ -761,10 +552,9 @@ document.getElementById('dashbord_month_area').addEventListener('click', () =>{
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 ( '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 ( ) {
@ -780,7 +570,7 @@ document.getElementById('dashbord_month_area').addEventListener('click', () =>{
} 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 ')
document . getElementById ( ' ActivitysTable') . classList . replace ( 'shrinkActivitys' , 'growActivitys ')
monthEntrys . forEach ( element => {
element . classList . replace ( 'fade-in' , 'fade-out' ) ;
} ) ;
@ -844,19 +634,16 @@ function checkInput() {
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 ;
}
}
@ -887,20 +674,17 @@ function checkEditInput() {
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: ${ editMin s . value } ` )
console . log ( ` Status: ${ editallowToSend } - Check: ${ editsonstigesCheck . checked } - Input: ${ editsonstigesInput . value } - Stunden: ${ edithours . value } - Min: ${ editMin uten . value } ` )
}