diff --git a/app.js b/app.js index 9cee3ff..8b13789 100644 --- a/app.js +++ b/app.js @@ -1,1649 +1 @@ -// TeeTracker - Main Application -// Version 4.0: Local file storage in data/ directory -// ============================================ -// Configuration -// ============================================ - -const STORAGE_KEY_TEAS = 'teatracker_teas'; -const STORAGE_KEY_ENTRIES = 'teatracker_entries'; -const DATA_DIRECTORY = 'data/'; - -// ============================================ -// Local File Storage -// ============================================ - -class LocalFileStorage { - constructor() { - this.basePath = DATA_DIRECTORY; - this.connected = false; - this.lastError = null; - this.tested = false; - } - - async testConnection() { - if (this.tested) return this.connected; - this.tested = true; - - try { - // First, check if we can read from the data directory - const testFile = this.basePath + 'teas.json'; - const readResponse = await fetch(testFile, { - method: 'GET' - }); - - if (readResponse.ok) { - // If we can read, try to write - const testWriteFile = this.basePath + '.write_test_' + Date.now() + '.tmp'; - const writeResponse = await fetch(testWriteFile, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ test: true }) - }); - - if (writeResponse.ok) { - // Clean up test file - await fetch(testWriteFile, { method: 'DELETE' }).catch(() => {}); - this.connected = true; - this.lastError = null; - return true; - } - } - - this.connected = false; - this.lastError = 'Server unterstützt Dateispeicherung nicht (PUT/DELETE nicht verfügbar)'; - return false; - } catch (error) { - this.connected = false; - this.lastError = error.message; - console.log('Local file storage not available:', error.message); - return false; - } - } - - async loadFile(filename) { - try { - const url = this.basePath + filename; - const response = await fetch(url, { - method: 'GET', - cache: 'no-store' - }); - - if (response.ok) { - const text = await response.text(); - if (!text || text.trim() === '') { - return []; - } - return JSON.parse(text); - } else if (response.status === 404) { - return null; - } - return null; - } catch (error) { - console.log(`Error loading ${filename}:`, error.message); - return null; - } - } - - async saveFile(filename, data) { - try { - const url = this.basePath + filename; - - // Try with PUT first (standard for creating/updating files) - let response = await fetch(url, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data, null, 2) - }); - - if (response.ok) { - return true; - } - - // If PUT fails, try POST (some servers use POST for file creation) - response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data, null, 2) - }); - - if (response.ok) { - return true; - } - - console.log(`Failed to save ${filename}: PUT and POST both failed`); - return false; - } catch (error) { - console.log(`Error saving ${filename}:`, error.message); - return false; - } - } - - async fileExists(filename) { - try { - const url = this.basePath + filename; - const response = await fetch(url, { - method: 'HEAD' - }); - return response.ok; - } catch (error) { - return false; - } - } - - async ensureDirectory() { - try { - const placeholder = this.basePath + '.gitkeep'; - const response = await fetch(placeholder, { - method: 'PUT', - headers: { - 'Content-Type': 'text/plain' - }, - body: 'This file ensures the data directory exists' - }); - return response.ok; - } catch (error) { - return false; - } - } -} - -// ============================================ -// Main TeeTracker Application -// ============================================ - -class TeeTracker { - constructor() { - // Storage - this.fileStorage = new LocalFileStorage(); - this.useFileStorage = false; - this.syncInProgress = false; - - // Data - this.teas = []; - this.entries = []; - this.currentTeaId = null; - this.charts = {}; - this.currentBgImage = null; - - // Tea Timer - this.timer = null; - this.timerSeconds = 180; - this.timerRunning = false; - this.timerPaused = false; - this.remainingSeconds = 180; - - // Initialize - this.initStorage(); - this.setupEventListeners(); - this.setupTimerListeners(); - this.loadData().then(() => { - this.renderAll(); - this.initCharts(); - this.updateSettingsStats(); - }); - } - - // ============================================ - // Storage Initialization - // ============================================ - - async initStorage() { - this.useFileStorage = await this.fileStorage.testConnection(); - this.updateSyncStatus(); - } - - async loadData() { - if (this.syncInProgress) return; - this.syncInProgress = true; - - try { - if (this.useFileStorage) { - await this.fileStorage.ensureDirectory(); - - const teas = await this.fileStorage.loadFile('teas.json'); - const entries = await this.fileStorage.loadFile('entries.json'); - - const localTeas = localStorage.getItem(STORAGE_KEY_TEAS); - const localEntries = localStorage.getItem(STORAGE_KEY_ENTRIES); - - if (teas && Array.isArray(teas)) { - this.teas = teas.map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false, - rating: tea.rating !== undefined ? tea.rating : 3 - })); - } else if (localTeas) { - try { - this.teas = JSON.parse(localTeas).map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false, - rating: tea.rating !== undefined ? tea.rating : 3 - })); - } catch (error) { - console.error('Error loading local teas:', error); - this.teas = []; - } - } else { - this.teas = []; - } - - if (entries && Array.isArray(entries)) { - this.entries = entries.map(entry => ({ - ...entry, - teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 - })); - } else if (localEntries) { - try { - this.entries = JSON.parse(localEntries).map(entry => ({ - ...entry, - teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 - })); - } catch (error) { - console.error('Error loading local entries:', error); - this.entries = []; - } - } else { - this.entries = []; - } - - if ((teas || entries) && (localTeas === null || localEntries === null)) { - this.saveToLocalStorage(); - } - } else { - this.loadFromLocalStorage(); - } - } catch (error) { - console.error('Error loading data:', error); - this.loadFromLocalStorage(); - } finally { - this.syncInProgress = false; - } - } - - loadFromLocalStorage() { - const teasData = localStorage.getItem(STORAGE_KEY_TEAS); - const entriesData = localStorage.getItem(STORAGE_KEY_ENTRIES); - - if (teasData) { - try { - this.teas = JSON.parse(teasData).map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false, - rating: tea.rating !== undefined ? tea.rating : 3 - })); - } catch (error) { - this.teas = []; - } - } - - if (entriesData) { - try { - this.entries = JSON.parse(entriesData).map(entry => ({ - ...entry, - teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 - })); - } catch (error) { - this.entries = []; - } - } - } - - async saveData() { - if (this.syncInProgress) return; - this.syncInProgress = true; - - try { - if (this.useFileStorage) { - const teasSaved = await this.fileStorage.saveFile('teas.json', this.teas); - const entriesSaved = await this.fileStorage.saveFile('entries.json', this.entries); - - this.saveToLocalStorage(); - - if (teasSaved && entriesSaved) { - this.showToast('Daten erfolgreich in data/-Verzeichnis gespeichert!', 'success'); - } else { - this.showToast('Lokale Speicherung erfolgreich, Dateispeicherung fehlgeschlagen', 'warning'); - } - } else { - this.saveToLocalStorage(); - } - } catch (error) { - console.error('Error saving data:', error); - this.showToast('Fehler beim Speichern der Daten', 'error'); - } finally { - this.syncInProgress = false; - } - } - - saveToLocalStorage() { - localStorage.setItem(STORAGE_KEY_TEAS, JSON.stringify(this.teas)); - localStorage.setItem(STORAGE_KEY_ENTRIES, JSON.stringify(this.entries)); - } - - updateSyncStatus() { - const statusBadge = document.getElementById('sync-status'); - if (!statusBadge) return; - - if (this.useFileStorage) { - statusBadge.textContent = 'Dateispeicherung aktiv'; - statusBadge.className = 'status-badge connected'; - } else { - statusBadge.textContent = 'Lokaler Modus (localStorage)'; - statusBadge.className = 'status-badge local'; - } - } - - generateId() { - return Date.now().toString(36) + Math.random().toString(36).substr(2); - } - - getTeaById(id) { - return this.teas.find(tea => tea.id === id); - } - - getTeaIndexById(id) { - return this.teas.findIndex(tea => tea.id === id); - } - - addTea(tea) { - const newTea = { - id: this.generateId(), - name: tea.name, - type: tea.type, - brand: tea.brand || '', - description: tea.description || '', - color: tea.color || '#8B4513', - organic: tea.organic || false, - rating: tea.rating || 3, - image: tea.image || '', - createdAt: new Date().toISOString() - }; - this.teas.push(newTea); - this.saveData(); - return newTea; - } - - updateTea(id, updates) { - const index = this.getTeaIndexById(id); - if (index !== -1) { - this.teas[index] = { ...this.teas[index], ...updates }; - this.saveData(); - return this.teas[index]; - } - return null; - } - - deleteTea(id) { - const index = this.getTeaIndexById(id); - if (index !== -1) { - this.entries = this.entries.filter(entry => entry.teaId !== id); - this.teas.splice(index, 1); - this.saveData(); - return true; - } - return false; - } - - addEntry(entry) { - const newEntry = { - id: this.generateId(), - teaId: entry.teaId, - date: entry.date, - time: entry.time || '', - amount: parseInt(entry.amount) || 1, - teaspoons: parseInt(entry.teaspoons) || 1, - notes: entry.notes || '', - createdAt: new Date().toISOString() - }; - this.entries.push(newEntry); - this.saveData(); - return newEntry; - } - - deleteEntry(id) { - const index = this.entries.findIndex(entry => entry.id === id); - if (index !== -1) { - this.entries.splice(index, 1); - this.saveData(); - return true; - } - return false; - } - - getTotalCups() { - return this.entries.reduce((sum, entry) => sum + entry.amount, 0); - } - - getTodayCups() { - const today = new Date().toISOString().split('T')[0]; - return this.entries - .filter(entry => entry.date === today) - .reduce((sum, entry) => sum + entry.amount, 0); - } - - getWeekCups() { - const now = new Date(); - const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - return this.entries - .filter(entry => new Date(entry.date) >= weekAgo) - .reduce((sum, entry) => sum + entry.amount, 0); - } - - getEntriesByPeriod(period) { - const now = new Date(); - let startDate; - - switch (period) { - case 'today': - startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - break; - case 'week': - startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - break; - case 'month': - startDate = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()); - break; - case 'year': - startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()); - break; - case 'all': - default: - startDate = new Date(0); - break; - } - - return this.entries.filter(entry => new Date(entry.date) >= startDate); - } - - getCupsByPeriod(period) { - const entries = this.getEntriesByPeriod(period); - return entries.reduce((sum, entry) => sum + entry.amount, 0); - } - - getCupsByTeaType(period = 'all') { - const entries = this.getEntriesByPeriod(period); - const typeCounts = {}; - - entries.forEach(entry => { - const tea = this.getTeaById(entry.teaId); - if (tea && tea.type) { - typeCounts[tea.type] = (typeCounts[tea.type] || 0) + entry.amount; - } - }); - - return typeCounts; - } - - getCupsByTea(period = 'all') { - const entries = this.getEntriesByPeriod(period); - const teaCounts = {}; - - entries.forEach(entry => { - const tea = this.getTeaById(entry.teaId); - if (tea) { - teaCounts[tea.id] = { - name: tea.name, - type: tea.type, - count: (teaCounts[tea.id] ? teaCounts[tea.id].count : 0) + entry.amount - }; - } - }); - - return teaCounts; - } - - getDailyConsumption(period = 'week') { - const entries = this.getEntriesByPeriod(period); - const dailyCounts = {}; - - entries.forEach(entry => { - const date = entry.date; - dailyCounts[date] = (dailyCounts[date] || 0) + entry.amount; - }); - - return dailyCounts; - } - - getMostConsumedTea(period = 'all') { - const teaCounts = this.getCupsByTea(period); - let maxCount = 0; - let mostConsumed = null; - - for (const teaId in teaCounts) { - if (teaCounts[teaId].count > maxCount) { - maxCount = teaCounts[teaId].count; - mostConsumed = teaCounts[teaId]; - } - } - - return mostConsumed; - } - - countUniqueDays(entries) { - const days = new Set(); - entries.forEach(entry => { - days.add(entry.date); - }); - return days.size; - } - - init() { - this.saveData().catch(() => {}); - } - - setupEventListeners() { - document.querySelectorAll('.tab-button').forEach(button => { - button.addEventListener('click', () => { - this.switchTab(button.dataset.tab); - }); - }); - - // Event delegation for tea card buttons - const teasList = document.getElementById('teas-list'); - if (teasList) { - teasList.addEventListener('click', (e) => { - const editBtn = e.target.closest('.tea-edit-btn'); - if (editBtn) { - const teaId = editBtn.dataset.teaId; - if (teaId) this.editTea(teaId); - return; - } - - const deleteBtn = e.target.closest('.tea-delete-btn'); - if (deleteBtn) { - const teaId = deleteBtn.dataset.teaId; - if (teaId) this.showDeleteTeaConfirm(teaId); - return; - } - }); - } - - const addTeaBtn = document.getElementById('add-tea-btn'); - if (addTeaBtn) { - addTeaBtn.addEventListener('click', () => this.openTeaModal()); - } - - const teaModal = document.getElementById('tea-modal'); - if (teaModal) { - teaModal.addEventListener('click', (e) => { - if (e.target.id === 'tea-modal') this.closeTeaModal(); - }); - } - - const teaForm = document.getElementById('tea-form'); - if (teaForm) { - teaForm.addEventListener('submit', (e) => this.handleTeaFormSubmit(e)); - } - - const cancelTeaBtn = document.getElementById('cancel-tea-btn'); - if (cancelTeaBtn) { - cancelTeaBtn.addEventListener('click', () => this.closeTeaModal()); - } - - const trackForm = document.getElementById('track-form'); - if (trackForm) { - trackForm.addEventListener('submit', (e) => this.handleTrackFormSubmit(e)); - } - - const statsPeriod = document.getElementById('stats-period'); - if (statsPeriod) { - statsPeriod.addEventListener('change', () => this.updateStats()); - } - - const exportBtn = document.getElementById('export-data'); - if (exportBtn) { - exportBtn.addEventListener('click', () => this.exportData()); - } - - const importBtn = document.getElementById('import-data'); - if (importBtn) { - importBtn.addEventListener('click', () => this.openImportModal()); - } - - const clearBtn = document.getElementById('clear-data'); - if (clearBtn) { - clearBtn.addEventListener('click', () => this.showClearDataConfirm()); - } - - const importModal = document.getElementById('import-modal'); - if (importModal) { - importModal.addEventListener('click', (e) => { - if (e.target.id === 'import-modal') this.closeImportModal(); - }); - } - - const closeImportBtn = document.getElementById('close-import-modal'); - if (closeImportBtn) { - closeImportBtn.addEventListener('click', () => this.closeImportModal()); - } - - const cancelImportBtn = document.getElementById('cancel-import-btn'); - if (cancelImportBtn) { - cancelImportBtn.addEventListener('click', () => this.closeImportModal()); - } - - const importDataBtn = document.getElementById('import-data-btn'); - if (importDataBtn) { - importDataBtn.addEventListener('click', () => this.handleImportData()); - } - - const confirmModal = document.getElementById('confirm-modal'); - if (confirmModal) { - confirmModal.addEventListener('click', (e) => { - if (e.target.id === 'confirm-modal') this.closeConfirmModal(); - }); - } - - const cancelConfirmBtn = document.getElementById('confirm-no'); - if (cancelConfirmBtn) { - cancelConfirmBtn.addEventListener('click', () => this.closeConfirmModal()); - } - - const confirmBtn = document.getElementById('confirm-yes'); - if (confirmBtn) { - confirmBtn.addEventListener('click', () => this.confirmAction()); - } - - this.setupBackgroundListeners(); - } - - setupTimerListeners() { - const startTimerBtn = document.getElementById('start-timer'); - const pauseTimerBtn = document.getElementById('pause-timer'); - const resetTimerBtn = document.getElementById('reset-timer'); - const timerInput = document.getElementById('timer-input'); - - if (startTimerBtn) { - startTimerBtn.addEventListener('click', () => this.startTimer()); - } - - if (pauseTimerBtn) { - pauseTimerBtn.addEventListener('click', () => this.pauseTimer()); - } - - if (resetTimerBtn) { - resetTimerBtn.addEventListener('click', () => this.resetTimer()); - } - - if (timerInput) { - timerInput.addEventListener('change', (e) => this.updateTimerFromInput(e)); - timerInput.addEventListener('input', (e) => this.validateTimerInput(e)); - } - } - - setupBackgroundListeners() { - const bgUpload = document.getElementById('bg-image-upload'); - if (bgUpload) { - bgUpload.addEventListener('change', (e) => this.handleBackgroundUpload(e)); - } - - const bgEnabled = document.getElementById('bg-enabled'); - if (bgEnabled) { - bgEnabled.addEventListener('change', () => this.toggleBackground()); - } - - const saveBgBtn = document.getElementById('save-bg'); - if (saveBgBtn) { - saveBgBtn.addEventListener('click', () => this.saveBackgroundSettings()); - } - - const removeBgBtn = document.getElementById('remove-bg'); - if (removeBgBtn) { - removeBgBtn.addEventListener('click', () => this.removeBackground()); - } - - this.loadBackgroundSettings(); - } - - handleBackgroundUpload(e) { - const file = e.target.files[0]; - if (!file) return; - - const preview = document.getElementById('bg-preview-img'); - const previewContainer = document.getElementById('bg-image-preview'); - const removeBtn = document.getElementById('remove-bg'); - - if (file.type.startsWith('image/')) { - const reader = new FileReader(); - reader.onload = (event) => { - preview.src = event.target.result; - previewContainer.style.display = 'block'; - removeBtn.style.display = 'inline-flex'; - this.currentBgImage = event.target.result; - }; - reader.readAsDataURL(file); - } - } - - toggleBackground() { - const enabled = document.getElementById('bg-enabled').checked; - if (enabled && this.currentBgImage) { - document.documentElement.style.setProperty('--bg-image', `url("${this.currentBgImage}")`); - } else { - document.documentElement.style.setProperty('--bg-image', 'none'); - } - } - - saveBackgroundSettings() { - const enabled = document.getElementById('bg-enabled').checked; - const settings = { - enabled: enabled, - image: this.currentBgImage || '' - }; - localStorage.setItem('teatracker_bg_settings', JSON.stringify(settings)); - this.showToast('Hintergrundeinstellungen gespeichert!', 'success'); - } - - removeBackground() { - this.currentBgImage = null; - document.getElementById('bg-image-upload').value = ''; - document.getElementById('bg-preview-img').src = ''; - document.getElementById('bg-image-preview').style.display = 'none'; - document.getElementById('remove-bg').style.display = 'none'; - document.documentElement.style.setProperty('--bg-image', 'none'); - document.getElementById('bg-enabled').checked = false; - } - - loadBackgroundSettings() { - const settings = localStorage.getItem('teatracker_bg_settings'); - if (settings) { - try { - const bgSettings = JSON.parse(settings); - this.currentBgImage = bgSettings.image || null; - - if (bgSettings.enabled && this.currentBgImage) { - document.getElementById('bg-enabled').checked = true; - document.getElementById('bg-preview-img').src = this.currentBgImage; - document.getElementById('bg-image-preview').style.display = 'block'; - document.getElementById('remove-bg').style.display = 'inline-flex'; - this.toggleBackground(); - } - } catch (error) { - console.error('Error loading background settings:', error); - } - } - } - - startTimer() { - if (this.timerRunning) return; - - if (this.timerPaused) { - this.timerPaused = false; - } else { - const input = document.getElementById('timer-input'); - if (input) { - this.timerSeconds = parseInt(input.value) || 180; - this.remainingSeconds = this.timerSeconds; - } else { - this.remainingSeconds = this.timerSeconds; - } - } - - this.timerRunning = true; - this.updateTimerDisplay(); - - this.timer = setInterval(() => { - this.remainingSeconds--; - this.updateTimerDisplay(); - - if (this.remainingSeconds <= 0) { - this.stopTimer(); - this.showToast('⏰ Tee ist fertig!', 'success'); - const audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2teleQAA'); - audio.play().catch(() => {}); - } - }, 1000); - } - - pauseTimer() { - if (!this.timerRunning) return; - clearInterval(this.timer); - this.timerRunning = false; - this.timerPaused = true; - } - - stopTimer() { - clearInterval(this.timer); - this.timerRunning = false; - this.timerPaused = false; - } - - resetTimer() { - this.stopTimer(); - const input = document.getElementById('timer-input'); - if (input) { - this.timerSeconds = parseInt(input.value) || 180; - } else { - this.timerSeconds = 180; - } - this.remainingSeconds = this.timerSeconds; - this.updateTimerDisplay(); - } - - updateTimerDisplay() { - const display = document.getElementById('timer-display'); - if (display) { - const minutes = Math.floor(this.remainingSeconds / 60); - const seconds = this.remainingSeconds % 60; - display.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; - } - } - - updateTimerFromInput(e) { - const value = parseInt(e.target.value); - if (value >= 10 && value <= 1800) { - this.timerSeconds = value; - this.remainingSeconds = value; - this.updateTimerDisplay(); - } - } - - validateTimerInput(e) { - const value = e.target.value; - if (value && (parseInt(value) < 10 || parseInt(value) > 1800)) { - e.target.value = this.timerSeconds; - } - } - - exportData() { - const data = { - teas: this.teas, - entries: this.entries, - exportedAt: new Date().toISOString() - }; - - const dataStr = JSON.stringify(data, null, 2); - const dataBlob = new Blob([dataStr], { type: 'application/json' }); - const url = URL.createObjectURL(dataBlob); - - const link = document.createElement('a'); - link.href = url; - link.download = `teatracker_export_${new Date().toISOString().split('T')[0]}.json`; - document.body.appendChild(link); - - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - - this.showToast('Daten wurden exportiert!', 'success'); - } - - openImportModal() { - const modal = document.getElementById('import-modal'); - if (modal) { - document.getElementById('import-data-textarea').value = ''; - modal.classList.add('active'); - } - } - - async handleImportData() { - const textarea = document.getElementById('import-data-textarea'); - if (!textarea) return; - - try { - const data = JSON.parse(textarea.value); - - if (data.teas && data.entries) { - if (confirm('Sollen die importierten Daten die bestehenden ersetzen?')) { - this.teas = data.teas.map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false - })); - this.entries = data.entries; - } else { - this.teas = [...this.teas, ...data.teas.map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false - }))]; - this.entries = [...this.entries, ...data.entries]; - } - - await this.saveData(); - this.closeImportModal(); - this.showToast('Daten wurden erfolgreich importiert!', 'success'); - this.renderAll(); - this.updateSettingsStats(); - } else { - this.showToast('Ungültiges Datenformat!', 'error'); - } - } catch (error) { - this.showToast('Fehler beim Importieren der Daten!', 'error'); - } - } - - closeImportModal() { - const modal = document.getElementById('import-modal'); - if (modal) { - modal.classList.remove('active'); - } - } - - showClearDataConfirm() { - const message = 'Möchtest du wirklich ALLE Daten löschen? Dieser Vorgang kann nicht rückgängig gemacht werden!'; - document.getElementById('confirm-message').textContent = message; - document.getElementById('confirm-modal').classList.add('active'); - this.tempConfirmAction = 'clearData'; - } - - async confirmAction() { - if (this.tempConfirmAction === 'clearData') { - await this.confirmClearData(); - } else if (this.tempConfirmAction === 'deleteTea') { - this.handleDeleteTea(); - } - this.tempConfirmAction = null; - } - - async confirmClearData() { - this.teas = []; - this.entries = []; - - localStorage.removeItem(STORAGE_KEY_TEAS); - localStorage.removeItem(STORAGE_KEY_ENTRIES); - - if (this.useFileStorage) { - await this.fileStorage.saveFile('teas.json', []); - await this.fileStorage.saveFile('entries.json', []); - } - - this.closeConfirmModal(); - this.showToast('Alle Daten wurden gelöscht!', 'success'); - this.renderAll(); - this.updateSettingsStats(); - } - - switchTab(tabId) { - document.querySelectorAll('.tab-button').forEach(button => { - button.classList.toggle('active', button.dataset.tab === tabId); - }); - - document.querySelectorAll('.tab-pane').forEach(pane => { - pane.classList.toggle('active', pane.id === tabId); - }); - - if (tabId === 'teas') { - this.renderTeasList(); - } else if (tabId === 'track') { - this.renderTrackForm(); - } else if (tabId === 'stats') { - this.updateStats(); - } else if (tabId === 'dashboard') { - this.updateDashboard(); - } else if (tabId === 'settings') { - this.updateSettingsStats(); - } - } - - renderAll() { - this.updateDashboard(); - this.renderTeasList(); - this.renderTrackForm(); - this.updateStats(); - this.updateSettingsStats(); - } - - updateSettingsStats() { - const totalCups = document.getElementById('settings-total-cups'); - const totalTeas = document.getElementById('settings-total-teas'); - const firstEntry = document.getElementById('settings-first-entry'); - const lastEntry = document.getElementById('settings-last-entry'); - - if (totalCups) totalCups.textContent = this.getTotalCups(); - if (totalTeas) totalTeas.textContent = this.teas.length; - - if (firstEntry) { - if (this.entries.length > 0) { - const first = this.entries.reduce((a, b) => new Date(a.createdAt) < new Date(b.createdAt) ? a : b); - firstEntry.textContent = new Date(first.createdAt).toLocaleDateString('de-DE'); - } else { - firstEntry.textContent = '-'; - } - } - - if (lastEntry) { - if (this.entries.length > 0) { - const last = this.entries.reduce((a, b) => new Date(a.createdAt) > new Date(b.createdAt) ? a : b); - lastEntry.textContent = new Date(last.createdAt).toLocaleDateString('de-DE'); - } else { - lastEntry.textContent = '-'; - } - } - } - - updateDashboard() { - const totalCups = document.getElementById('total-cups'); - const todayCups = document.getElementById('today-cups'); - const weekCups = document.getElementById('week-cups'); - const totalTeas = document.getElementById('total-teas'); - const recentActivities = document.getElementById('recent-entries'); - - if (totalCups) totalCups.textContent = this.getTotalCups(); - if (todayCups) todayCups.textContent = this.getTodayCups(); - if (weekCups) weekCups.textContent = this.getWeekCups(); - if (totalTeas) totalTeas.textContent = this.teas.length; - - this.renderRecentEntries(); - } - - renderRecentEntries() { - const container = document.getElementById('recent-entries'); - if (!container) return; - - const recentEntries = [...this.entries] - .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) - .slice(0, 5); - - if (recentEntries.length === 0) { - container.innerHTML = ` -