diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..2654c3d
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,38 @@
+# TeeTracker .htaccess Configuration
+# Enable PUT and DELETE methods for the data directory
+# Required for file-based storage to work on Apache servers
+
+
+ RewriteEngine On
+
+ # Allow PUT and DELETE for data directory
+ RewriteCond %{REQUEST_METHOD} ^(PUT|DELETE)$
+ RewriteCond %{REQUEST_URI} ^/data/ [NC]
+ RewriteRule ^ - [L]
+
+
+# Enable all HTTP methods for the data directory
+
+ Require all granted
+
+
+
+ Require all denied
+
+
+# Set proper MIME types for JSON files
+
+ AddType application/json .json
+
+
+# Disable caching for data files
+
+
+ ExpiresActive Off
+
+
+ Header set Cache-Control "no-cache, no-store, must-revalidate"
+ Header set Pragma "no-cache"
+ Header set Expires 0
+
+
diff --git a/SERVER_SETUP.md b/SERVER_SETUP.md
new file mode 100644
index 0000000..1143d3e
--- /dev/null
+++ b/SERVER_SETUP.md
@@ -0,0 +1,193 @@
+# TeeTracker Server Setup
+
+## Problem: Dateispeicherung funktioniert nicht
+
+Die TeeTracker-App versucht, Daten im `data/`-Verzeichnis zu speichern, aber auf den meisten Webservern funktioniert das nicht standardmäßig. Hier sind die Lösungen:
+
+---
+
+## Lösung 1: LocalStorage (Empfohlen für Shared Hosting)
+
+**Funktioniert immer, ohne Server-Konfiguration!**
+
+Die App fällt automatisch auf localStorage zurück, wenn die Dateispeicherung nicht verfügbar ist.
+- Alle Daten werden im Browser gespeichert
+- Funktioniert auf jedem Webserver
+- Daten sind nur auf dem aktuellen Gerät/Browser verfügbar
+
+**Status prüfen:**
+- Gehe zu "Einstellungen" → "Datenverwaltung"
+- Wenn "Lokaler Modus (localStorage)" angezeigt wird, funktioniert die Dateispeicherung nicht
+- Wenn "Dateispeicherung aktiv" angezeigt wird, funktioniert die Dateispeicherung
+
+---
+
+## Lösung 2: Apache Server (.htaccess)
+
+Falls dein Server Apache verwendet (häufig bei Shared Hosting):
+
+1. **Stelle sicher, dass die `.htaccess`-Datei aktiv ist:**
+ - Die Datei liegt bereits im Hauptverzeichnis
+ - Prüfe, ob `mod_rewrite` aktiviert ist
+ - Kontaktiere deinen Hosting-Anbieter, falls `.htaccess` ignoriert wird
+
+2. **Erlaube PUT und DELETE Methoden:**
+ Die `.htaccess`-Datei enthält bereits die notwendigen Regeln:
+ ```apache
+
+ Require all granted
+
+ ```
+
+3. **Setze die richtigen Berechtigungen:**
+ ```bash
+ chmod 755 data/
+ chmod 644 data/.gitkeep
+ ```
+
+4. **Aktiviere die notwendigen Apache-Module:**
+ ```bash
+ a2enmod rewrite
+ a2enmod headers
+ a2enmod expires
+ service apache2 restart
+ ```
+
+---
+
+## Lösung 3: PHP-Backend (Alternative)
+
+Falls dein Server PHP unterstützt, kannst du ein einfaches Backend erstellen:
+
+### 1. Erstelle `save_data.php`:
+```php
+ 'Not allowed']));
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+ if (file_exists($filename)) {
+ readfile($filename);
+ } else {
+ echo '[]';
+ }
+} elseif ($_SERVER['REQUEST_METHOD'] === 'PUT' || $_SERVER['REQUEST_METHOD'] === 'POST') {
+ $data = file_get_contents('php://input');
+ file_put_contents($filename, $data);
+ echo json_encode(['success' => true]);
+} elseif ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
+ if (file_exists($filename)) {
+ unlink($filename);
+ }
+ echo json_encode(['success' => true]);
+}
+```
+
+### 2. Ändere die `LocalFileStorage`-Klasse:
+Ersetze die `fetch()`-Aufrufe durch Aufrufe an dein PHP-Skript.
+
+---
+
+## Lösung 4: Node.js/Express Server
+
+Falls du Node.js verwenden kannst:
+
+```javascript
+const express = require('express');
+const fs = require('fs');
+const path = require('path');
+const app = express();
+
+app.use(express.json());
+app.use(express.static('public'));
+
+// Enable PUT and DELETE for data directory
+app.use('/data', express.raw({ type: '*/*' }));
+
+app.put('/data/:file', (req, res) => {
+ const filePath = path.join(__dirname, 'data', req.params.file);
+ fs.writeFile(filePath, req.body, (err) => {
+ if (err) return res.status(500).send(err.message);
+ res.sendStatus(200);
+ });
+});
+
+app.get('/data/:file', (req, res) => {
+ const filePath = path.join(__dirname, 'data', req.params.file);
+ if (fs.existsSync(filePath)) {
+ res.sendFile(filePath);
+ } else {
+ res.status(404).send('[]');
+ }
+});
+
+app.delete('/data/:file', (req, res) => {
+ const filePath = path.join(__dirname, 'data', req.params.file);
+ if (fs.existsSync(filePath)) {
+ fs.unlink(filePath, (err) => {
+ if (err) return res.status(500).send(err.message);
+ res.sendStatus(200);
+ });
+ } else {
+ res.sendStatus(200);
+ }
+});
+
+app.listen(3000, () => console.log('Server running on port 3000'));
+```
+
+---
+
+## Häufige Probleme und Lösungen
+
+### Problem: "405 Method Not Allowed"
+**Lösung:** Der Server unterstützt PUT/DELETE nicht. Verwende Lösung 2, 3 oder 4.
+
+### Problem: "403 Forbidden"
+**Lösung:** Die Berechtigungen sind falsch. Führe aus:
+```bash
+chmod 755 data/
+chmod 644 data/*
+```
+
+### Problem: "404 Not Found" für data/-Dateien
+**Lösung:** Das `data/`-Verzeichnis existiert nicht oder die Dateien sind nicht hochgeladen.
+
+### Problem: CORS-Fehler
+**Lösung:** Falls du die App von einer anderen Domain lädst, musst du CORS-Header setzen:
+```apache
+Header set Access-Control-Allow-Origin "*"
+Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
+Header set Access-Control-Allow-Headers "Content-Type"
+```
+
+---
+
+## Testen der Dateispeicherung
+
+1. Öffne die Browser-Konsole (F12 → Console)
+2. Gib ein:
+```javascript
+fetch('data/test.txt', { method: 'PUT', body: 'test' })
+ .then(r => r.text())
+ .then(console.log)
+ .catch(console.error);
+```
+3. Wenn du eine erfolgreiche Antwort erhältst, funktioniert die Dateispeicherung
+
+---
+
+## Empfehlung
+
+**Für Shared Hosting:** Verwende einfach localStorage (Lösung 1). Die Daten werden im Browser gespeichert und funktionieren ohne Server-Konfiguration.
+
+**Für eigene Server:** Konfiguriere Apache mit der `.htaccess`-Datei (Lösung 2) oder verwende das PHP-Backend (Lösung 3).
+
+**Für Entwickler:** Verwende den Node.js-Server (Lösung 4) oder einen lokalen Entwicklungsserver wie `python -m http.server`.
diff --git a/app.js b/app.js
index a6a33f1..3d6d429 100644
--- a/app.js
+++ b/app.js
@@ -1,4 +1,239 @@
-// TeeTracker - Main Application
+// Local File Storage
+// ============================================
+
+class LocalFileStorage {
+ constructor() {
+ this.basePath = DATA_DIRECTORY;
+ this.connected = false;
+ this.lastError = null;
+ }
+
+ async testConnection() {
+ try {
+ // 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) {
+ // Clean up test file
+ await fetch(testFile, { method: 'DELETE' });
+ this.connected = true;
+ this.lastError = null;
+ return true;
+ } else {
+ this.connected = false;
+ this.lastError = `HTTP Error: ${response.status}`;
+ 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'
+ });
+
+ if (response.ok) {
+ const text = await response.text();
+ return text.trim() === '' ? [] : (text ? JSON.parse(text) : null);
+ } 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;
+ const response = await fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data, null, 2)
+ });
+ return response.ok;
+ } 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;
+ }
+ }
+}
+=======
+// ============================================
+// 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 {
+ // First ensure the data directory exists by trying to create it
+ // Note: We can't actually create directories via fetch, but we can try to save the file
+ 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 to create a placeholder file to ensure directory exists
+ // This works on servers that support PUT for file creation
+ 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;
+ }
+ }
+}TeeTracker - Main Application
// Version 4.0: Local file storage in data/ directory
// ============================================
@@ -150,6 +385,9 @@ class TeeTracker {
try {
if (this.useFileStorage) {
+ // Try to ensure data directory exists
+ await this.fileStorage.ensureDirectory();
+
// Try to load from file storage first
const teas = await this.fileStorage.loadFile('teas.json');
const entries = await this.fileStorage.loadFile('entries.json');
@@ -159,7 +397,7 @@ class TeeTracker {
const localEntries = localStorage.getItem(STORAGE_KEY_ENTRIES);
// Load from file storage if available, otherwise use local data
- if (teas) {
+ if (teas && Array.isArray(teas)) {
this.teas = teas.map(tea => ({
...tea,
organic: tea.organic !== undefined ? tea.organic : false,
@@ -180,7 +418,7 @@ class TeeTracker {
this.teas = [];
}
- if (entries) {
+ if (entries && Array.isArray(entries)) {
this.entries = entries.map(entry => ({
...entry,
teaspoons: entry.teaspoons !== undefined ? entry.teaspoons : 1
@@ -198,6 +436,11 @@ class TeeTracker {
} else {
this.entries = [];
}
+
+ // If we loaded from file storage, save to localStorage as backup
+ if ((teas || entries) && (localTeas === null || localEntries === null)) {
+ this.saveToLocalStorage();
+ }
} else {
// Load from localStorage only
this.loadFromLocalStorage();
@@ -548,6 +791,12 @@ class TeeTracker {
if (trackForm) {
trackForm.addEventListener('submit', (e) => this.handleTrackFormSubmit(e));
}
+
+ // Stats period filter
+ const statsPeriod = document.getElementById('stats-period');
+ if (statsPeriod) {
+ statsPeriod.addEventListener('change', () => this.updateStats());
+ }
// Export/Import/Clear data buttons
const exportBtn = document.getElementById('export-data');
@@ -1579,7 +1828,8 @@ class TeeTracker {
// ============================================
updateStats() {
- this.updateDetailedStats('all');
+ const period = document.getElementById('stats-period')?.value || 'all';
+ this.renderDetailedStats(period);
// Update charts
this.createTeaTypeChart();
@@ -1666,7 +1916,7 @@ class TeeTracker {
}
// Initialize application
-let app;
+window.app = new TeeTracker();
document.addEventListener('DOMContentLoaded', () => {
- app = new TeeTracker();
+ window.app = new TeeTracker();
});
diff --git a/data/.gitkeep b/data/.gitkeep
new file mode 100644
index 0000000..72a4f9a
--- /dev/null
+++ b/data/.gitkeep
@@ -0,0 +1,2 @@
+# This file ensures the data directory exists and is tracked by git
+# TeeTracker stores teas.json and entries.json in this directory