@ -18,10 +18,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 ( ) ;
@ -38,75 +34,18 @@ 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
@ -132,18 +71,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 +254,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 +262,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 +272,6 @@ NewEntryButton.addEventListener('click', () => {
hours . value = null
mins . value = null
document . getElementById ( 'sonstigesInput' ) . value = null
document . getElementById ( 'partnerInput' ) . value = null
checkInput ( )
}
} )
@ -474,77 +399,6 @@ SETLdcNameSaveButton.addEventListener('click', () => {
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,7 +423,6 @@ 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 )
@ -700,10 +553,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 +571,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 +609,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 +627,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 +691,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 +731,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 } ` )
}