diff --git a/README.md b/README.md index 245c236..cf02483 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [] [] [] -[] + --- @@ -65,11 +65,10 @@ - **Zeitraum-Filter**: **Heute**, Letzte 7 Tage, Monat, Jahr oder Gesamt ### ⚙️ Einstellungen (NEU!) -- **Nextcloud-Synchronisation** 🌐 - - Verbindung zu deiner Nextcloud-Instanz herstellen - - Automatische Synchronisation zwischen Geräten - - Verbindungstest - - Synchronisationsstatus-Anzeige +- **Lokale Dateispeicherung** 💾 + - Daten werden im `data/`-Verzeichnis gespeichert (falls ein lokaler Server läuft) + - Automatischer Fallback auf Browser-Speicher (localStorage) + - Export/Import-Funktionen für Datensicherung - **Datenverwaltung** - 📤 **Daten exportieren** (JSON-Datei herunterladen) - 📥 **Daten importieren** (JSON-Datei hochladen) @@ -112,21 +111,27 @@ - Folder: `/ (root)` auswählen 3. Deine App ist dann unter `https://trevor1969.github.io/teatracker/` erreichbar -### Option 3: Mit Nextcloud-Synchronisation (empfohlen!) +### Option 2: Mit lokalem Server (empfohlen für Dateispeicherung) -1. **App öffnen** (lokal oder über GitHub Pages) -2. Gehe zum Tab **⚙️ Einstellungen** -3. Aktiviere **Nextcloud-Synchronisation** -4. Trage deine Nextcloud-Daten ein: - - **URL**: `https://wralto.org/nextcloud3` - - **Benutzername**: `teetracker` - - **App-Passwort**: (dein App-Passwort aus Nextcloud) - - **Speicherpfad**: `/TeeTracker/` (Standard) -5. Klicke auf **🔍 Verbindung testen** -6. Speichere die Konfiguration mit **💾 Speichern** -7. **Fertig!** 🎉 Deine Daten werden jetzt automatisch synchronisiert +1. **Repository klonen** (oder Dateien herunterladen): + ```bash + git clone https://github.com/trevor1969/teatracker.git + cd teatracker + ``` -**Hinweis:** Aus Sicherheitsgründen musst du dein Passwort manuell eingeben. Es wird nicht in der App gespeichert (nur temporär in sessionStorage). +2. **Lokalen Server starten**: + ```bash + # Mit Python 3 + python -m http.server 8000 + + # Mit Node.js (npx) + npx serve + ``` + +3. **App öffnen** im Browser: `http://localhost:8000` +4. **Fertig!** 🎉 Deine Daten werden automatisch im `data/`-Verzeichnis gespeichert + +**Hinweis:** Die App versucht zunächst, Dateien im `data/`-Verzeichnis zu speichern. Falls das nicht funktioniert (z.B. bei direkter Datei-Öffnung ohne Server), fällt sie automatisch auf localStorage zurück. --- @@ -134,7 +139,7 @@ - **Frontend**: HTML5, CSS3, Vanilla JavaScript (ES6+) - **Charts**: [Chart.js](https://www.chartjs.org/) (über CDN) -- **Cloud-Speicher**: [Nextcloud WebDAV API](https://docs.nextcloud.com/server/latest/developer_manual/third_party/webdav.html) +- **Lokale Speicherung**: Dateisystem (data/-Verzeichnis) oder Browser localStorage - **Lokaler Speicher**: localStorage (Fallback) - **Design**: Responsive, Mobile-First, Tee-inspirierte Farben @@ -146,62 +151,67 @@ teatracker/ ├── index.html # Haupt-HTML-Datei mit allen Tabs ├── styles.css # Alle Styles und Design -├── app.js # gesamte Anwendungslogik + Nextcloud-Integration +├── app.js # gesamte Anwendungslogik + lokale Dateispeicherung ├── README.md # Diese Datei └── LICENSE # MIT License ``` --- -## 🌐 Nextcloud-Integration +## 💾 Lokale Dateispeicherung (Version 4.0) + +Ab Version 4.0 werden die Daten lokal im `data/`-Verzeichnis gespeichert, das sich im selben Verzeichnis wie die Programmdateien befindet. ### Wie es funktioniert -Die App nutzt die **WebDAV-API** von Nextcloud, um Daten als JSON-Dateien zu speichern: +Die App speichert Daten als JSON-Dateien im lokalen Dateisystem: ``` -/TeeTracker/ +data/ ├── teas.json # Deine Tee-Sorten ├── entries.json # Deine Tracking-Einträge -└── backup/ # Automatische Backups +└── backup/ # Manuelle Backups (optional) └── teetracker_backup_2024-06-05T10-30-00.json ``` +### Speichermechanismus + +1. **Mit lokalem Server**: Wenn die App über einen lokalen Server (z.B. `python -m http.server`) geöffnet wird, werden die Daten im `data/`-Verzeichnis gespeichert +2. **Ohne Server**: Falls die App direkt als Datei geöffnet wird, fällt sie automatisch auf Browser-Speicher (localStorage) zurück +3. **Export/Import**: Daten können jederzeit als JSON-Datei exportiert und importiert werden + ### Vorteile | Vorteil | Beschreibung | |---------|--------------| -| **🌍 Plattformübergreifend** | Zugriff von allen Geräten mit Internetverbindung | -| **🔄 Echtzeit-Sync** | Änderungen werden sofort synchronisiert | -| **💾 Automatische Backups** | Daten bleiben in Nextcloud erhalten | -| **⚡ Schnell** | Keine Datenbank nötig, einfache Dateispeicherung | -| **🔒 Sicher** | HTTPS-Verschlüsselung, App-Passwort (nicht Hauptpasswort) | +| **📁 Einfache Dateispeicherung** | Daten liegen direkt bei den Programmdateien | +| **🔄 Automatische Erkennung** | Erkennt automatisch, ob Dateispeicherung möglich ist | +| **💾 Fallback auf localStorage** | Funktioniert auch ohne Server | +| **⚡ Schnell** | Keine externe Abhängigkeiten, einfache JSON-Dateien | +| **🔒 Privatsphäre** | Alle Daten bleiben lokal auf deinem Gerät | -### Fallback-Mechanismus +### Daten sichern (Backup) -Falls Nextcloud nicht erreichbar ist: -1. Die App **funktioniert weiter** mit lokalen Daten (localStorage) -2. Änderungen werden **lokal gespeichert** -3. Beim nächsten erfolgreichen Verbindungsaufbau werden die Daten **automatisch synchronisiert** +**Option 1: Automatisch (Dateisystem)** +- Die Daten werden automatisch im `data/`-Verzeichnis gespeichert +- Einfach das gesamte `data/`-Verzeichnis sichern -### 🔐 Sicherheitshinweise +**Option 2: Manuell (Export)** +1. Gehe zu **Einstellungen → Datenverwaltung** +2. Klicke auf **📤 Daten exportieren** +3. Speichere die JSON-Datei an einem sicheren Ort -**WICHTIG:** Aus Sicherheitsgründen wird dein Passwort **NICHT** im Klartext in der App oder im localStorage gespeichert! +### Daten wiederherstellen (Restore) -- **sessionStorage**: Das Passwort wird nur in `sessionStorage` gespeichert (wird beim Schließen des Browsers gelöscht) -- **Keine Vorbefüllung**: Du musst dein Passwort manuell eingeben -- **Kein Klartext in Dateien**: Das Passwort erscheint nirgends im Code +**Option 1: Automatisch (Dateisystem)** +- Kopiere die gesicherten Dateien zurück in das `data/`-Verzeichnis +- Starte die App neu -### App-Passwort erstellen - -Falls du ein neues Passwort brauchst: -1. Logge dich in deine Nextcloud ein (`https://wralto.org/nextcloud3`) -2. Gehe zu **Einstellungen → Sicherheit** -3. Suche nach **"App-Passwörter"** -4. Erstelle ein neues Passwort mit dem Namen **"TeeTracker"** -5. Kopiere das Passwort und trage es in den Einstellungen ein - -**Tipp:** Speichere das App-Passwort in einem Passwort-Manager, da es nach dem Erstellen nicht mehr angezeigt wird. +**Option 2: Manuell (Import)** +1. Gehe zu **Einstellungen → Datenverwaltung** +2. Klicke auf **📥 Daten importieren** +3. Füge deine exportierten Daten ein oder lade die JSON-Datei hoch +4. Wähle, ob du die Daten **ersetzen** oder **zusammenführen** möchtest --- @@ -228,9 +238,9 @@ Falls du ein neues Passwort brauchst: ### Daten sichern (Backup) -**Option 1: Automatisch (Nextcloud)** -- Die Daten werden automatisch in deiner Nextcloud gespeichert -- Backups werden im `/TeeTracker/backup/` Verzeichnis erstellt +**Option 1: Automatisch (Dateisystem)** +- Die Daten werden automatisch im `data/`-Verzeichnis gespeichert +- Einfach das gesamte `data/`-Verzeichnis kopieren **Option 2: Manuell (Export)** 1. Gehe zu **Einstellungen → Datenverwaltung** @@ -239,9 +249,9 @@ Falls du ein neues Passwort brauchst: ### Daten wiederherstellen (Restore) -**Option 1: Automatisch (Nextcloud)** -- Einfach die App auf einem neuen Gerät öffnen -- Die Daten werden automatisch von Nextcloud geladen +**Option 1: Automatisch (Dateisystem)** +- Kopiere die gesicherten Dateien zurück in das `data/`-Verzeichnis +- Starte die App neu **Option 2: Manuell (Import)** 1. Gehe zu **Einstellungen → Datenverwaltung** @@ -258,59 +268,46 @@ Falls du von vorne beginnen möchtest: **Achtung:** Dieser Vorgang kann nicht rückgängig gemacht werden! -### Nextcloud-Verbindung prüfen - -Falls die Synchronisation nicht funktioniert: -1. Gehe zu **Einstellungen → Nextcloud-Synchronisation** -2. Klicke auf **🔍 Verbindung testen** -3. Prüfe die Fehlermeldung und korrigiere deine Eingaben - --- ## 🔧 Problembehebung -### ❌ Verbindung zu Nextcloud fehlgeschlagen +### ❌ Dateispeicherung funktioniert nicht **Mögliche Ursachen:** -- Falsche URL (muss mit `https://` beginnen) -- Falscher Benutzername oder Passwort -- Nextcloud ist nicht erreichbar -- WebDAV ist deaktiviert (standardmäßig aktiv) -- CORS-Probleme (Browser-Beschränkungen) +- App wird direkt als Datei geöffnet (ohne lokalen Server) +- Keine Schreibrechte im Verzeichnis +- Browser-Beschränkungen für Fetch-API **Lösungen:** -1. Prüfe, ob du dich manuell in Nextcloud einloggen kannst -2. Teste die WebDAV-URL direkt im Browser: - ``` - https://wralto.org/nextcloud3/remote.php/dav/ - ``` -3. Erstelle ein neues App-Passwort in Nextcloud -4. Versuche einen anderen Browser +1. Starte einen lokalen Server (z.B. `python -m http.server 8000`) +2. Öffne die App über `http://localhost:8000` +3. Prüfe, ob das `data/`-Verzeichnis existiert und beschreibbar ist +4. Falls nötig: Nutze die Export/Import-Funktionen für manuelles Speichern 5. Prüfe die Browser-Konsole (F12 → Console) auf Fehler -### ❌ Daten werden nicht synchronisiert +### ❌ Daten werden nicht gespeichert **Mögliche Ursachen:** -- Nextcloud ist offline -- Browser blockiert die Verbindung -- Zu viele Anfragen in kurzer Zeit +- App wird direkt als Datei geöffnet (ohne lokalen Server) +- Keine Schreibrechte im `data/`-Verzeichnis +- Browser blockiert Fetch-Anfragen **Lösungen:** -1. Prüfe deine Internetverbindung -2. Lade die Seite neu -3. Warte einige Minuten und versuche es erneut -4. Prüfe die Browser-Konsole auf Fehler -5. Versuche, die Daten manuell zu exportieren/importieren +1. Starte einen lokalen Server (z.B. `python -m http.server 8000`) +2. Öffne die App über `http://localhost:8000` +3. Prüfe die Browser-Konsole auf Fehler +4. Nutze die Export/Import-Funktionen als Alternative ### ❌ Daten sind verschwunden **Keine Sorge!** Die Daten sind an mehreren Orten gespeichert: -1. **Nextcloud** (falls verbunden) +1. **Dateisystem** (`data/`-Verzeichnis, falls Server läuft) 2. **localStorage** (Fallback) **Lösungen:** 1. Lade die Seite neu -2. Prüfe, ob du mit einem anderen Gerät/Browser verbunden bist +2. Prüfe, ob das `data/`-Verzeichnis existiert 3. Gehe zu **Einstellungen → Daten exportieren** und sichere deine Daten 4. Falls nötig: Importiere deine exportierten Daten @@ -337,7 +334,7 @@ Dieses Projekt steht unter der **MIT License** – sieh die [LICENSE](LICENSE) D ## 🙏 Danksagung - [Chart.js](https://www.chartjs.org/) – Für die tollen Diagramme -- [Nextcloud](https://nextcloud.com/) – Für die sichere Cloud-Speicherung + - Alle Tee-Liebhaber da draußen! ☕🍵 --- @@ -352,8 +349,8 @@ Fragen oder Feedback? Öffne einfach ein [Issue](https://github.com/trevor1969/t ### 🆕 Neueste Änderungen -- **🌐 Nextcloud-Integration** – Plattformübergreifende Synchronisation via WebDAV -- **⚙️ Einstellungen-Tab** – Nextcloud-Konfiguration, Daten-Export/Import +- **💾 Lokale Dateispeicherung** – Daten werden im `data/`-Verzeichnis gespeichert (Version 4.0) +- **⚙️ Einstellungen-Tab** – Datenverwaltung, Export/Import, Hintergrundbild - **🌱 Bio-Checkbox** – Kennzeichnung für Bio-Tees - **📅 Zeitraum "Heute"** – Filter für aktuelle Tagesdaten in Statistiken - **📝 Letzte 10 Einträge** – Anzeige auf der Tracken-Seite @@ -362,6 +359,7 @@ Fragen oder Feedback? Öffne einfach ein [Issue](https://github.com/trevor1969/t | Version | Datum | Änderungen | |---------|-------|-----------| +| 4.0.0 | Juni 2025 | Lokale Dateispeicherung im data/-Verzeichnis, Nextcloud-Integration entfernt | | 1.2.0 | Juni 2024 | Nextcloud-Integration, Einstellungen-Tab, Bio-Checkbox, "Heute"-Filter, letzte Einträge | | 1.1.0 | Juni 2024 | Bio-Checkbox, "Heute"-Zeitraum, letzte 10 Einträge auf Tracken-Seite | | 1.0.0 | Juni 2024 | Erste Version mit Dashboard, Tee-Verwaltung, Tracking, Statistiken | diff --git a/app.js b/app.js index 6cc5aac..a6a33f1 100644 --- a/app.js +++ b/app.js @@ -1,4 +1,5 @@ -// TeeTracker - Main Application with Nextcloud Support +// TeeTracker - Main Application +// Version 4.0: Local file storage in data/ directory // ============================================ // Configuration @@ -6,50 +7,31 @@ const STORAGE_KEY_TEAS = 'teatracker_teas'; const STORAGE_KEY_ENTRIES = 'teatracker_entries'; -const STORAGE_KEY_NC_CONFIG = 'teatracker_nextcloud_config'; - -// Data directory for Nextcloud storage const DATA_DIRECTORY = 'data/'; // ============================================ -// Nextcloud Storage Integration +// Local File Storage // ============================================ -class NextcloudStorage { - constructor(baseUrl, username, password, path = '/TeeTracker/') { - this.baseUrl = baseUrl.replace(/\/$/, ''); - this.username = username; - this.password = password; - this.path = path.replace(/^\//, '/').replace(/\/$/, '/'); - this.authHeader = 'Basic ' + btoa(`${username}:${password}`); +class LocalFileStorage { + constructor() { + this.basePath = DATA_DIRECTORY; this.connected = false; this.lastError = null; } - getFileUrl(filename) { - return `${this.baseUrl}/remote.php/dav/files/${encodeURIComponent(this.username)}${this.path}${filename}`; - } - - getDirectoryUrl() { - return `${this.baseUrl}/remote.php/dav/files/${encodeURIComponent(this.username)}${this.path}`; - } - async testConnection() { try { - const response = await fetch(this.getDirectoryUrl(), { - method: 'PROPFIND', - headers: { - 'Authorization': this.authHeader, - 'Content-Type': 'text/xml; charset=utf-8' - } + // Try to create a test file + const testFile = this.basePath + 'test_connection.txt'; + const response = await fetch(testFile, { + method: 'PUT', + body: 'test' }); - + if (response.ok) { - this.connected = true; - this.lastError = null; - return true; - } else if (response.status === 404) { - await this.ensureDirectory(); + // Clean up test file + await fetch(testFile, { method: 'DELETE' }); this.connected = true; this.lastError = null; return true; @@ -61,149 +43,59 @@ class NextcloudStorage { } catch (error) { this.connected = false; this.lastError = error.message; - console.error('Nextcloud connection error:', error); - return false; - } - } - - - // Migrate old files from root to data/ directory - async migrateToDataDirectory() { - try { - // Check if old teas.json exists in root - const oldTeasUrl = this.getFileUrl('teas.json'); - const oldTeasResponse = await fetch(oldTeasUrl, { - headers: { 'Authorization': this.authHeader } - }); - - if (oldTeasResponse.ok) { - const oldTeas = await oldTeasResponse.json(); - await this.saveFile(DATA_DIRECTORY + 'teas.json', oldTeas); - console.log('Migrated teas.json to data/ directory'); - } - - // Check if old entries.json exists in root - const oldEntriesUrl = this.getFileUrl('entries.json'); - const oldEntriesResponse = await fetch(oldEntriesUrl, { - headers: { 'Authorization': this.authHeader } - }); - - if (oldEntriesResponse.ok) { - const oldEntries = await oldEntriesResponse.json(); - await this.saveFile(DATA_DIRECTORY + 'entries.json', oldEntries); - console.log('Migrated entries.json to data/ directory'); - } - } catch (error) { - console.log('No old files to migrate or migration failed:', error.message); - } - } - async ensureDirectory() { - try { - const response = await fetch(this.getDirectoryUrl(), { - method: 'MKCOL', - headers: { 'Authorization': this.authHeader } - }); - await this.ensureSubDirectory('data'); - await this.ensureSubDirectory('backup'); - return response.ok || response.status === 405; - } catch (error) { - console.error('Error creating directory:', error); - return false; - } - } - - async ensureSubDirectory(subPath) { - try { - const subDirUrl = `${this.getDirectoryUrl()}${subPath}/`; - const response = await fetch(subDirUrl, { - method: 'MKCOL', - headers: { 'Authorization': this.authHeader } - }); - return response.ok || response.status === 405; - } catch (error) { - console.error('Error creating subdirectory:', error); + console.log('Local file storage not available:', error.message); return false; } } async loadFile(filename) { try { - const url = this.getFileUrl(filename); + const url = this.basePath + filename; const response = await fetch(url, { - headers: { 'Authorization': this.authHeader } + method: 'GET' }); if (response.ok) { const text = await response.text(); - // Return empty array if file is empty, otherwise parse JSON return text.trim() === '' ? [] : (text ? JSON.parse(text) : null); } else if (response.status === 404) { return null; } return null; } catch (error) { - console.error(`Error loading ${filename}:`, error); + console.log(`Error loading ${filename}:`, error.message); return null; } } async saveFile(filename, data) { try { - const url = this.getFileUrl(filename); + const url = this.basePath + filename; const response = await fetch(url, { method: 'PUT', headers: { - 'Authorization': this.authHeader, 'Content-Type': 'application/json' }, body: JSON.stringify(data, null, 2) }); return response.ok; } catch (error) { - console.error(`Error saving ${filename}:`, error); + console.log(`Error saving ${filename}:`, error.message); return false; } } async fileExists(filename) { try { - const url = this.getFileUrl(filename); + const url = this.basePath + filename; const response = await fetch(url, { - method: 'HEAD', - headers: { 'Authorization': this.authHeader } + method: 'HEAD' }); return response.ok; } catch (error) { return false; } } - - async createBackup(backupName, teas, entries) { - try { - await this.ensureSubDirectory('data'); - await this.ensureSubDirectory('backup'); - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); - const backupData = { timestamp, teas, entries }; - const filename = `backup/${backupName}_${timestamp}.json`; - return await this.saveFile(filename, backupData); - } catch (error) { - console.error('Error creating backup:', error); - return false; - } - } - - async restoreBackup(backupFilename) { - try { - const data = await this.loadFile(backupFilename); - if (data && data.teas && data.entries) { - return { teas: data.teas, entries: data.entries }; - } - return null; - } catch (error) { - console.error('Error restoring backup:', error); - return null; - } - } } // ============================================ @@ -212,9 +104,9 @@ class NextcloudStorage { class TeeTracker { constructor() { - // Storage configuration - this.useNextcloud = false; - this.nextcloudStorage = null; + // Storage + this.fileStorage = new LocalFileStorage(); + this.useFileStorage = false; this.syncInProgress = false; // Data @@ -226,7 +118,7 @@ class TeeTracker { // Tea Timer this.timer = null; - this.timerSeconds = 180; // 3 minutes in seconds + this.timerSeconds = 180; this.timerRunning = false; this.timerPaused = false; this.remainingSeconds = 180; @@ -246,101 +138,75 @@ class TeeTracker { // Storage Initialization // ============================================ - initStorage() { - // Load Nextcloud configuration from localStorage - const ncConfig = localStorage.getItem(STORAGE_KEY_NC_CONFIG); - const ncPassword = sessionStorage.getItem(STORAGE_KEY_NC_CONFIG + '_password'); - - if (ncConfig) { - try { - const config = JSON.parse(ncConfig); - // Load password from sessionStorage (more secure than localStorage) - const password = ncPassword || ''; - - if (password) { - this.nextcloudStorage = new NextcloudStorage( - config.baseUrl, - config.username, - password, - config.path || '/TeeTracker/' - ); - this.useNextcloud = true; - this.updateSyncStatus(); - } else { - // Password not available, user needs to re-enter it - this.useNextcloud = false; - this.showStatusMessage('Bitte gib dein Nextcloud-Passwort erneut ein.', 'info'); - } - } catch (error) { - console.error('Invalid Nextcloud config:', error); - this.useNextcloud = false; - } - } else { - // Don't pre-fill credentials - user must enter them manually - this.useNextcloud = false; - } + async initStorage() { + // Test if file storage is available + this.useFileStorage = await this.fileStorage.testConnection(); + this.updateSyncStatus(); } async loadData() { - if (this.useNextcloud && this.nextcloudStorage) { - // Try to load from Nextcloud first - await this.nextcloudStorage.ensureDirectory(); - - // Check if old files exist in root directory and migrate to data/ directory - await this.nextcloudStorage.migrateToDataDirectory(); - - const teas = await this.nextcloudStorage.loadFile(DATA_DIRECTORY + 'teas.json'); - const entries = await this.nextcloudStorage.loadFile(DATA_DIRECTORY + 'entries.json'); - - // Always load from localStorage first as backup - const localTeas = localStorage.getItem(STORAGE_KEY_TEAS); - const localEntries = localStorage.getItem(STORAGE_KEY_ENTRIES); - - // Load from Nextcloud if available, otherwise use local data - if (teas) { - this.teas = teas.map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false, - rating: tea.rating !== undefined ? tea.rating : 3 - })); - } else if (localTeas) { - // Nextcloud has no teas, use local data - try { - this.teas = JSON.parse(localTeas).map(tea => ({ + if (this.syncInProgress) return; + this.syncInProgress = true; + + try { + if (this.useFileStorage) { + // Try to load from file storage first + const teas = await this.fileStorage.loadFile('teas.json'); + const entries = await this.fileStorage.loadFile('entries.json'); + + // Always load from localStorage as backup + const localTeas = localStorage.getItem(STORAGE_KEY_TEAS); + const localEntries = localStorage.getItem(STORAGE_KEY_ENTRIES); + + // Load from file storage if available, otherwise use local data + if (teas) { + this.teas = teas.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); + } 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 = []; } - } else { - this.teas = []; - } - - if (entries) { - this.entries = entries.map(entry => ({ - ...entry, - teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 - })); - } else if (localEntries) { - // Nextcloud has no entries, use local data - try { - this.entries = JSON.parse(localEntries).map(entry => ({ + + if (entries) { + this.entries = entries.map(entry => ({ ...entry, teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 })); - } catch (error) { - console.error('Error loading local entries:', error); + } 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 = []; } } else { - this.entries = []; + // Load from localStorage only + this.loadFromLocalStorage(); } - } else { - // Load from localStorage + } catch (error) { + console.error('Error loading data:', error); this.loadFromLocalStorage(); + } finally { + this.syncInProgress = false; } } @@ -377,20 +243,18 @@ class TeeTracker { this.syncInProgress = true; try { - if (this.useNextcloud && this.nextcloudStorage) { - await this.nextcloudStorage.ensureDirectory(); - - // Save to Nextcloud - const teasSaved = await this.nextcloudStorage.saveFile(DATA_DIRECTORY + 'teas.json', this.teas); - const entriesSaved = await this.nextcloudStorage.saveFile(DATA_DIRECTORY + 'entries.json', this.entries); + if (this.useFileStorage) { + // Save to file storage + const teasSaved = await this.fileStorage.saveFile('teas.json', this.teas); + const entriesSaved = await this.fileStorage.saveFile('entries.json', this.entries); // Also save locally as backup this.saveToLocalStorage(); if (teasSaved && entriesSaved) { - this.showToast('Daten erfolgreich mit Nextcloud synchronisiert!', 'success'); + this.showToast('Daten erfolgreich in data/-Verzeichnis gespeichert!', 'success'); } else { - this.showToast('Lokale Speicherung erfolgreich, Nextcloud-Sync fehlgeschlagen', 'warning'); + this.showToast('Lokale Speicherung erfolgreich, Dateispeicherung fehlgeschlagen', 'warning'); } } else { // Save only locally @@ -413,16 +277,11 @@ class TeeTracker { const statusBadge = document.getElementById('sync-status'); if (!statusBadge) return; - if (this.useNextcloud) { - if (this.nextcloudStorage && this.nextcloudStorage.connected) { - statusBadge.textContent = 'Verbunden mit Nextcloud'; - statusBadge.className = 'status-badge connected'; - } else { - statusBadge.textContent = 'Verbindung fehlgeschlagen'; - statusBadge.className = 'status-badge disconnected'; - } + if (this.useFileStorage) { + statusBadge.textContent = 'Dateispeicherung aktiv'; + statusBadge.className = 'status-badge connected'; } else { - statusBadge.textContent = 'Lokaler Modus'; + statusBadge.textContent = 'Lokaler Modus (localStorage)'; statusBadge.className = 'status-badge local'; } } @@ -501,7 +360,7 @@ class TeeTracker { date: entry.date, time: entry.time || '', amount: parseInt(entry.amount) || 1, - teaspoons: entry.teaspoons ? parseFloat(entry.teaspoons) : 1, + teaspoons: parseInt(entry.teaspoons) || 1, notes: entry.notes || '', createdAt: new Date().toISOString() }; @@ -538,7 +397,6 @@ class TeeTracker { 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); @@ -562,10 +420,9 @@ class TeeTracker { startDate = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate()); break; case 'all': + default: startDate = new Date(0); break; - default: - startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); } return this.entries.filter(entry => new Date(entry.date) >= startDate); @@ -582,7 +439,7 @@ class TeeTracker { entries.forEach(entry => { const tea = this.getTeaById(entry.teaId); - if (tea) { + if (tea && tea.type) { typeCounts[tea.type] = (typeCounts[tea.type] || 0) + entry.amount; } }); @@ -597,7 +454,11 @@ class TeeTracker { entries.forEach(entry => { const tea = this.getTeaById(entry.teaId); if (tea) { - teaCounts[tea.name] = (teaCounts[tea.name] || 0) + entry.amount; + teaCounts[tea.id] = { + name: tea.name, + type: tea.type, + count: (teaCounts[tea.id] ? teaCounts[tea.id].count : 0) + entry.amount + }; } }); @@ -621,244 +482,90 @@ class TeeTracker { let maxCount = 0; let mostConsumed = null; - for (const [teaName, count] of Object.entries(teaCounts)) { - if (count > maxCount) { - maxCount = count; - mostConsumed = teaName; + for (const teaId in teaCounts) { + if (teaCounts[teaId].count > maxCount) { + maxCount = teaCounts[teaId].count; + mostConsumed = teaCounts[teaId]; } } - return { name: mostConsumed, count: maxCount }; + return mostConsumed; } countUniqueDays(entries) { const days = new Set(); - entries.forEach(entry => days.add(entry.date)); + entries.forEach(entry => { + days.add(entry.date); + }); return days.size; } // ============================================ - // UI Initialization + // Initialization // ============================================ init() { - // Initialization is now handled in constructor - // This method is kept for backward compatibility - this.renderAll(); - this.initCharts(); - this.updateSettingsStats(); + // Ensure data directory exists by attempting to save + this.saveData().catch(() => {}); } setupEventListeners() { // Tab navigation document.querySelectorAll('.tab-button').forEach(button => { - button.addEventListener('click', (e) => this.switchTab(e.target.dataset.tab)); + button.addEventListener('click', () => { + this.switchTab(button.dataset.tab); + }); }); // Add tea button - if (document.getElementById('add-tea-btn')) { - document.getElementById('add-tea-btn').addEventListener('click', () => this.openTeaModal()); - } - - // Event delegation for tea action buttons (edit/delete) - const teasListContainer = document.getElementById('teas-list'); - if (teasListContainer) { - teasListContainer.addEventListener('click', (e) => { - const editBtn = e.target.closest('.tea-edit-btn'); - const deleteBtn = e.target.closest('.tea-delete-btn'); - - if (editBtn) { - const teaId = editBtn.dataset.teaId; - if (teaId) this.editTea(teaId); - } else if (deleteBtn) { - const teaId = deleteBtn.dataset.teaId; - if (teaId) this.showDeleteTeaConfirm(teaId); - } - }); + const addTeaBtn = document.getElementById('add-tea-btn'); + if (addTeaBtn) { + addTeaBtn.addEventListener('click', () => this.openTeaModal()); } // Tea modal - if (document.getElementById('close-modal')) { - document.getElementById('close-modal').addEventListener('click', () => this.closeTeaModal()); - } - if (document.getElementById('tea-form')) { - document.getElementById('tea-form').addEventListener('submit', (e) => this.handleTeaFormSubmit(e)); - } - if (document.getElementById('delete-tea-btn')) { - document.getElementById('delete-tea-btn').addEventListener('click', () => this.handleDeleteTea()); - } - - // Star rating for tea form - const teaRating = document.getElementById('tea-rating'); - if (teaRating) { - teaRating.addEventListener('click', (e) => this.handleStarRatingClick(e)); - } - - // Tea image upload - const teaImageUpload = document.getElementById('tea-image-upload'); - if (teaImageUpload) { - teaImageUpload.addEventListener('change', (e) => this.handleImageUpload(e)); - } - - // Confirm modal - if (document.getElementById('close-confirm')) { - document.getElementById('close-confirm').addEventListener('click', () => this.closeConfirmModal()); - } - if (document.getElementById('confirm-no')) { - document.getElementById('confirm-no').addEventListener('click', () => this.closeConfirmModal()); - } - if (document.getElementById('confirm-yes')) { - document.getElementById('confirm-yes').addEventListener('click', () => this.confirmDelete()); - } - - // Track form - if (document.getElementById('track-form')) { - document.getElementById('track-form').addEventListener('submit', (e) => this.handleTrackFormSubmit(e)); - } - if (document.getElementById('cancel-track')) { - document.getElementById('cancel-track').addEventListener('click', () => this.resetTrackForm()); - } - - // Tea search and filter - if (document.getElementById('tea-search')) { - document.getElementById('tea-search').addEventListener('input', (e) => this.filterTeas()); - } - if (document.getElementById('tea-type-filter')) { - document.getElementById('tea-type-filter').addEventListener('change', (e) => this.filterTeas()); - } - - // Stats period filter - if (document.getElementById('stats-period')) { - document.getElementById('stats-period').addEventListener('change', (e) => this.updateStats()); - } - - // Close modals on outside click - if (document.getElementById('tea-modal')) { - document.getElementById('tea-modal').addEventListener('click', (e) => { + const teaModal = document.getElementById('tea-modal'); + if (teaModal) { + teaModal.addEventListener('click', (e) => { if (e.target.id === 'tea-modal') this.closeTeaModal(); }); } - if (document.getElementById('confirm-modal')) { - document.getElementById('confirm-modal').addEventListener('click', (e) => { - if (e.target.id === 'confirm-modal') this.closeConfirmModal(); - }); + // Tea form + const teaForm = document.getElementById('tea-form'); + if (teaForm) { + teaForm.addEventListener('submit', (e) => this.handleTeaFormSubmit(e)); } - // Background image settings - this.setupBackgroundListeners(); - - // Nextcloud settings - this.setupNextcloudListeners(); - - // Tea Timer controls - this.setupTimerListeners(); - } - - setupTimerListeners() { - // Timer control buttons - const startBtn = document.getElementById('timer-start'); - const pauseBtn = document.getElementById('timer-pause'); - const resetBtn = document.getElementById('timer-reset'); - const decreaseBtn = document.getElementById('timer-decrease'); - const increaseBtn = document.getElementById('timer-increase'); - const timerInput = document.getElementById('timer-input'); - - if (startBtn) { - startBtn.addEventListener('click', () => this.startTimer()); - } - if (pauseBtn) { - pauseBtn.addEventListener('click', () => this.pauseTimer()); - } - if (resetBtn) { - resetBtn.addEventListener('click', () => this.resetTimer()); - } - if (decreaseBtn) { - decreaseBtn.addEventListener('click', () => this.decreaseTimer()); - } - if (increaseBtn) { - increaseBtn.addEventListener('click', () => this.increaseTimer()); - } - if (timerInput) { - timerInput.addEventListener('change', (e) => this.updateTimerFromInput(e)); - timerInput.addEventListener('input', (e) => this.validateTimerInput(e)); - } - } - - setupNextcloudListeners() { - // Toggle Nextcloud config visibility - const ncEnabled = document.getElementById('nc-enabled'); - const ncConfig = document.getElementById('nc-config'); - - if (ncEnabled && ncConfig) { - ncEnabled.addEventListener('change', () => { - ncConfig.style.display = ncEnabled.checked ? 'block' : 'none'; - }); - - // Load saved config - const config = localStorage.getItem(STORAGE_KEY_NC_CONFIG); - if (config) { - try { - const ncConfigData = JSON.parse(config); - ncEnabled.checked = true; - ncConfig.style.display = 'block'; - document.getElementById('nc-url').value = ncConfigData.baseUrl || ''; - document.getElementById('nc-username').value = ncConfigData.username || ''; - document.getElementById('nc-password').value = ncConfigData.password || ''; - document.getElementById('nc-path').value = ncConfigData.path || '/TeeTracker/'; - } catch (error) { - console.error('Error loading NC config:', error); - } - } + // Cancel tea button + const cancelTeaBtn = document.getElementById('cancel-tea-btn'); + if (cancelTeaBtn) { + cancelTeaBtn.addEventListener('click', () => this.closeTeaModal()); } - // Test connection button - const testBtn = document.getElementById('test-nc-connection'); - if (testBtn) { - testBtn.addEventListener('click', async () => { - await this.testNextcloudConnection(); - }); + // Track form + const trackForm = document.getElementById('track-form'); + if (trackForm) { + trackForm.addEventListener('submit', (e) => this.handleTrackFormSubmit(e)); } - // Save config button - const saveBtn = document.getElementById('save-nc-config'); - if (saveBtn) { - saveBtn.addEventListener('click', async () => { - await this.saveNextcloudConfig(); - }); - } - - // Export data button + // Export/Import/Clear data buttons const exportBtn = document.getElementById('export-data'); if (exportBtn) { exportBtn.addEventListener('click', () => this.exportData()); } - // Import data button const importBtn = document.getElementById('import-data'); if (importBtn) { importBtn.addEventListener('click', () => this.openImportModal()); } - // Close import modal button - const closeImportBtn = document.getElementById('close-import-modal'); - if (closeImportBtn) { - closeImportBtn.addEventListener('click', () => this.closeImportModal()); + const clearBtn = document.getElementById('clear-data'); + if (clearBtn) { + clearBtn.addEventListener('click', () => this.showClearDataConfirm()); } - // Cancel import button - const cancelImportBtn = document.getElementById('cancel-import-btn'); - if (cancelImportBtn) { - cancelImportBtn.addEventListener('click', () => this.closeImportModal()); - } - - // Import data button in modal - const importDataBtn = document.getElementById('import-data-btn'); - if (importDataBtn) { - importDataBtn.addEventListener('click', () => this.handleImportData()); - } - - // Close import modal on outside click + // Import modal const importModal = document.getElementById('import-modal'); if (importModal) { importModal.addEventListener('click', (e) => { @@ -866,10 +573,65 @@ class TeeTracker { }); } - // Clear data button - const clearBtn = document.getElementById('clear-data'); - if (clearBtn) { - clearBtn.addEventListener('click', () => this.showClearDataConfirm()); + 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()); + } + + // Confirm modal + 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()); + } + + // Background settings + this.setupBackgroundListeners(); + } + + setupTimerListeners() { + // Timer controls + 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)); } } @@ -902,6 +664,10 @@ class TeeTracker { this.loadBackgroundSettings(); } + // ============================================ + // Background Functions + // ============================================ + handleBackgroundUpload(e) { const file = e.target.files[0]; if (!file) return; @@ -933,179 +699,127 @@ class TeeTracker { saveBackgroundSettings() { const enabled = document.getElementById('bg-enabled').checked; - const bgImage = this.currentBgImage; - - if (enabled && bgImage) { - localStorage.setItem('teatracker_bg_enabled', 'true'); - localStorage.setItem('teatracker_bg_image', bgImage); - this.showToast('Hintergrundbild gespeichert!', 'success'); - } else { - localStorage.removeItem('teatracker_bg_enabled'); - localStorage.removeItem('teatracker_bg_image'); - this.showToast('Hintergrundbild deaktiviert!', 'success'); - } - this.toggleBackground(); + 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; - this.toggleBackground(); } loadBackgroundSettings() { - this.currentBgImage = localStorage.getItem('teatracker_bg_image'); - const enabled = localStorage.getItem('teatracker_bg_enabled') === 'true'; - - if (this.currentBgImage) { - document.getElementById('bg-enabled').checked = enabled; - if (enabled) { - document.documentElement.style.setProperty('--bg-image', `url("${this.currentBgImage}")`); - } - - // Show preview - const preview = document.getElementById('bg-preview-img'); - const previewContainer = document.getElementById('bg-image-preview'); - const removeBtn = document.getElementById('remove-bg'); - if (preview && previewContainer && removeBtn) { - preview.src = this.currentBgImage; - previewContainer.style.display = 'block'; - removeBtn.style.display = 'inline-flex'; + 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); } } } // ============================================ - // Nextcloud Functions + // Timer Functions // ============================================ - async testNextcloudConnection() { - const statusElement = document.getElementById('nc-status'); - if (!statusElement) return; + startTimer() { + if (this.timerRunning) return; - const url = document.getElementById('nc-url').value; - const username = document.getElementById('nc-username').value; - const password = document.getElementById('nc-password').value; - - if (!url || !username || !password) { - this.showStatusMessage('Bitte fülle alle Pflichtfelder aus!', 'error'); - 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.showStatusMessage('Verbindung wird getestet...', 'info'); + this.timerRunning = true; + this.updateTimerDisplay(); - try { - const ncStorage = new NextcloudStorage(url, username, password); - const connected = await ncStorage.testConnection(); + this.timer = setInterval(() => { + this.remainingSeconds--; + this.updateTimerDisplay(); - if (connected) { - this.showStatusMessage('✅ Verbindung erfolgreich! Nextcloud ist erreichbar.', 'success'); - } else { - this.showStatusMessage(`❌ Verbindung fehlgeschlagen: ${ncStorage.lastError}`, 'error'); + 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(() => {}); } - } catch (error) { - this.showStatusMessage(`❌ Fehler: ${error.message}`, 'error'); + }, 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')}`; } } - async saveNextcloudConfig() { - const statusElement = document.getElementById('nc-status'); - if (!statusElement) return; - - const enabled = document.getElementById('nc-enabled').checked; - const url = document.getElementById('nc-url').value; - const username = document.getElementById('nc-username').value; - const password = document.getElementById('nc-password').value; - const path = document.getElementById('nc-path').value; - - if (enabled && (!url || !username || !password)) { - this.showStatusMessage('Bitte fülle alle Pflichtfelder aus!', 'error'); - return; - } - - this.showStatusMessage('Konfiguration wird gespeichert...', 'info'); - - try { - if (enabled) { - // Test connection first - const ncStorage = new NextcloudStorage(url, username, password, path); - const connected = await ncStorage.testConnection(); - - if (!connected) { - this.showStatusMessage(`❌ Verbindung fehlgeschlagen: ${ncStorage.lastError}`, 'error'); - return; - } - - // Save config WITHOUT password for security - // Password will be requested each time or stored in sessionStorage - const config = { baseUrl: url, username, path }; - localStorage.setItem(STORAGE_KEY_NC_CONFIG, JSON.stringify(config)); - - // Store password in sessionStorage (cleared when browser closes) - sessionStorage.setItem(STORAGE_KEY_NC_CONFIG + '_password', password); - - // Update app state - this.nextcloudStorage = ncStorage; - this.useNextcloud = true; - - // Load data from Nextcloud first (this is the critical fix!) - // This will automatically fall back to localStorage if Nextcloud has no data - await this.loadData(); - - // Only save to Nextcloud if we actually have data to save - // This prevents overwriting existing Nextcloud data with empty arrays - if (this.teas.length > 0 || this.entries.length > 0) { - await this.saveData(); - } else { - // No data to save, but check if Nextcloud has existing data - const existingTeas = await ncStorage.loadFile(DATA_DIRECTORY + 'teas.json'); - const existingEntries = await ncStorage.loadFile(DATA_DIRECTORY + 'entries.json'); - - // If Nextcloud has data, keep it and load it - if (existingTeas || existingEntries) { - if (existingTeas) { - this.teas = existingTeas.map(tea => ({ - ...tea, - organic: tea.organic !== undefined ? tea.organic : false, - rating: tea.rating !== undefined ? tea.rating : 3 - })); - } - if (existingEntries) { - this.entries = existingEntries.map(entry => ({ - ...entry, - teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1 - })); - } - // Don't save, just use the existing Nextcloud data - } - // If Nextcloud has no data and we have no data, nothing to do - } - - this.showStatusMessage('✅ Konfiguration gespeichert und Daten synchronisiert!', 'success'); - } else { - // Disable Nextcloud - localStorage.removeItem(STORAGE_KEY_NC_CONFIG); - sessionStorage.removeItem(STORAGE_KEY_NC_CONFIG + '_password'); - this.useNextcloud = false; - this.nextcloudStorage = null; - this.showStatusMessage('✅ Nextcloud-Synchronisation deaktiviert. Daten werden lokal gespeichert.', 'success'); - } - - this.updateSyncStatus(); - this.updateSettingsStats(); - } catch (error) { - this.showStatusMessage(`❌ Fehler: ${error.message}`, 'error'); + updateTimerFromInput(e) { + const value = parseInt(e.target.value); + if (value >= 10 && value <= 1800) { + this.timerSeconds = value; + this.remainingSeconds = value; + this.updateTimerDisplay(); } } - showStatusMessage(message, type = 'info') { - const statusElement = document.getElementById('nc-status'); - if (statusElement) { - statusElement.textContent = message; - statusElement.className = `status-message ${type}`; + validateTimerInput(e) { + const value = e.target.value; + if (value && (parseInt(value) < 10 || parseInt(value) > 1800)) { + e.target.value = this.timerSeconds; } } @@ -1128,6 +842,7 @@ class TeeTracker { 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); @@ -1192,21 +907,31 @@ class TeeTracker { document.getElementById('confirm-message').textContent = message; document.getElementById('confirm-modal').classList.add('active'); - // Override confirm function temporarily + // Set action type 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 = []; - // Clear both localStorage and Nextcloud + // Clear localStorage localStorage.removeItem(STORAGE_KEY_TEAS); localStorage.removeItem(STORAGE_KEY_ENTRIES); - if (this.useNextcloud && this.nextcloudStorage) { - await this.nextcloudStorage.saveFile(DATA_DIRECTORY + 'teas.json', []); - await this.nextcloudStorage.saveFile(DATA_DIRECTORY + 'entries.json', []); + // Clear file storage if available + if (this.useFileStorage) { + await this.fileStorage.saveFile('teas.json', []); + await this.fileStorage.saveFile('entries.json', []); } this.closeConfirmModal(); @@ -1286,11 +1011,12 @@ class TeeTracker { 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()} Tassen`; - if (todayCups) todayCups.textContent = `${this.getTodayCups()} Tassen`; - if (weekCups) weekCups.textContent = `${this.getWeekCups()} Tassen`; - if (totalTeas) totalTeas.textContent = `${this.teas.length} Sorten`; + 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(); } @@ -1307,7 +1033,7 @@ class TeeTracker { container.innerHTML = `
Verbinde TeeTracker mit deiner Nextcloud-Instanz für plattformübergreifende Synchronisation.
- -Daten werden im data/-Verzeichnis gespeichert (falls ein lokaler Server läuft) oder im Browser-Speicher (localStorage).