Clean app.js: Remove all merge conflicts and duplicate classes

Co-authored-by: trevor1969 <trevor1969@users.noreply.github.com>
This commit is contained in:
Vibe Nuage Agent
2026-06-06 12:47:37 +00:00
parent 23c83ef4d1
commit ee6c690645

317
app.js
View File

@ -1,91 +1,14 @@
// Local File Storage
// TeeTracker - Main Application
// Version 4.0: Local file storage in data/ directory
// ============================================
// Configuration
// ============================================
class LocalFileStorage {
constructor() {
this.basePath = DATA_DIRECTORY;
this.connected = false;
this.lastError = null;
}
const STORAGE_KEY_TEAS = 'teatracker_teas';
const STORAGE_KEY_ENTRIES = 'teatracker_entries';
const DATA_DIRECTORY = 'data/';
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
// ============================================
@ -166,8 +89,6 @@ class LocalFileStorage {
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)
@ -217,8 +138,6 @@ class LocalFileStorage {
}
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, {
@ -233,104 +152,6 @@ class LocalFileStorage {
return false;
}
}
}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;
}
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;
}
}
}
// ============================================
@ -374,7 +195,6 @@ class TeeTracker {
// ============================================
async initStorage() {
// Test if file storage is available
this.useFileStorage = await this.fileStorage.testConnection();
this.updateSyncStatus();
}
@ -385,18 +205,14 @@ 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');
// 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 && Array.isArray(teas)) {
this.teas = teas.map(tea => ({
...tea,
@ -437,12 +253,10 @@ class TeeTracker {
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();
}
} catch (error) {
@ -487,11 +301,9 @@ class TeeTracker {
try {
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) {
@ -500,7 +312,6 @@ class TeeTracker {
this.showToast('Lokale Speicherung erfolgreich, Dateispeicherung fehlgeschlagen', 'warning');
}
} else {
// Save only locally
this.saveToLocalStorage();
}
} catch (error) {
@ -529,18 +340,10 @@ class TeeTracker {
}
}
// ============================================
// Generate unique ID
// ============================================
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// ============================================
// Get data by ID
// ============================================
getTeaById(id) {
return this.teas.find(tea => tea.id === id);
}
@ -549,10 +352,6 @@ class TeeTracker {
return this.teas.findIndex(tea => tea.id === id);
}
// ============================================
// Tea Management
// ============================================
addTea(tea) {
const newTea = {
id: this.generateId(),
@ -592,10 +391,6 @@ class TeeTracker {
return false;
}
// ============================================
// Entry Management
// ============================================
addEntry(entry) {
const newEntry = {
id: this.generateId(),
@ -622,10 +417,6 @@ class TeeTracker {
return false;
}
// ============================================
// Statistics
// ============================================
getTotalCups() {
return this.entries.reduce((sum, entry) => sum + entry.amount, 0);
}
@ -743,28 +534,21 @@ class TeeTracker {
return days.size;
}
// ============================================
// Initialization
// ============================================
init() {
// Ensure data directory exists by attempting to save
this.saveData().catch(() => {});
}
setupEventListeners() {
// Tab navigation
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
this.switchTab(button.dataset.tab);
});
});
// Event delegation for tea card buttons (edit and delete)
// Event delegation for tea card buttons
const teasList = document.getElementById('teas-list');
if (teasList) {
teasList.addEventListener('click', (e) => {
// Edit button
const editBtn = e.target.closest('.tea-edit-btn');
if (editBtn) {
const teaId = editBtn.dataset.teaId;
@ -772,7 +556,6 @@ class TeeTracker {
return;
}
// Delete button
const deleteBtn = e.target.closest('.tea-delete-btn');
if (deleteBtn) {
const teaId = deleteBtn.dataset.teaId;
@ -782,13 +565,11 @@ class TeeTracker {
});
}
// Add tea button
const addTeaBtn = document.getElementById('add-tea-btn');
if (addTeaBtn) {
addTeaBtn.addEventListener('click', () => this.openTeaModal());
}
// Tea modal
const teaModal = document.getElementById('tea-modal');
if (teaModal) {
teaModal.addEventListener('click', (e) => {
@ -796,31 +577,26 @@ class TeeTracker {
});
}
// Tea form
const teaForm = document.getElementById('tea-form');
if (teaForm) {
teaForm.addEventListener('submit', (e) => this.handleTeaFormSubmit(e));
}
// Cancel tea button
const cancelTeaBtn = document.getElementById('cancel-tea-btn');
if (cancelTeaBtn) {
cancelTeaBtn.addEventListener('click', () => this.closeTeaModal());
}
// Track form
const trackForm = document.getElementById('track-form');
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');
if (exportBtn) {
exportBtn.addEventListener('click', () => this.exportData());
@ -836,7 +612,6 @@ class TeeTracker {
clearBtn.addEventListener('click', () => this.showClearDataConfirm());
}
// Import modal
const importModal = document.getElementById('import-modal');
if (importModal) {
importModal.addEventListener('click', (e) => {
@ -859,7 +634,6 @@ class TeeTracker {
importDataBtn.addEventListener('click', () => this.handleImportData());
}
// Confirm modal
const confirmModal = document.getElementById('confirm-modal');
if (confirmModal) {
confirmModal.addEventListener('click', (e) => {
@ -877,12 +651,10 @@ class TeeTracker {
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');
@ -907,38 +679,29 @@ class TeeTracker {
}
setupBackgroundListeners() {
// Background image upload
const bgUpload = document.getElementById('bg-image-upload');
if (bgUpload) {
bgUpload.addEventListener('change', (e) => this.handleBackgroundUpload(e));
}
// Background enable toggle
const bgEnabled = document.getElementById('bg-enabled');
if (bgEnabled) {
bgEnabled.addEventListener('change', () => this.toggleBackground());
}
// Save background button
const saveBgBtn = document.getElementById('save-bg');
if (saveBgBtn) {
saveBgBtn.addEventListener('click', () => this.saveBackgroundSettings());
}
// Remove background button
const removeBgBtn = document.getElementById('remove-bg');
if (removeBgBtn) {
removeBgBtn.addEventListener('click', () => this.removeBackground());
}
// Load saved background settings
this.loadBackgroundSettings();
}
// ============================================
// Background Functions
// ============================================
handleBackgroundUpload(e) {
const file = e.target.files[0];
if (!file) return;
@ -1008,10 +771,6 @@ class TeeTracker {
}
}
// ============================================
// Timer Functions
// ============================================
startTimer() {
if (this.timerRunning) return;
@ -1045,7 +804,6 @@ class TeeTracker {
pauseTimer() {
if (!this.timerRunning) return;
clearInterval(this.timer);
this.timerRunning = false;
this.timerPaused = true;
@ -1094,10 +852,6 @@ class TeeTracker {
}
}
// ============================================
// Data Export/Import
// ============================================
exportData() {
const data = {
teas: this.teas,
@ -1137,7 +891,6 @@ class TeeTracker {
const data = JSON.parse(textarea.value);
if (data.teas && data.entries) {
// Merge or replace data
if (confirm('Sollen die importierten Daten die bestehenden ersetzen?')) {
this.teas = data.teas.map(tea => ({
...tea,
@ -1145,7 +898,6 @@ class TeeTracker {
}));
this.entries = data.entries;
} else {
// Merge data
this.teas = [...this.teas, ...data.teas.map(tea => ({
...tea,
organic: tea.organic !== undefined ? tea.organic : false
@ -1177,8 +929,6 @@ class TeeTracker {
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');
// Set action type
this.tempConfirmAction = 'clearData';
}
@ -1195,11 +945,9 @@ class TeeTracker {
this.teas = [];
this.entries = [];
// Clear localStorage
localStorage.removeItem(STORAGE_KEY_TEAS);
localStorage.removeItem(STORAGE_KEY_ENTRIES);
// Clear file storage if available
if (this.useFileStorage) {
await this.fileStorage.saveFile('teas.json', []);
await this.fileStorage.saveFile('entries.json', []);
@ -1211,10 +959,6 @@ class TeeTracker {
this.updateSettingsStats();
}
// ============================================
// Tab Navigation
// ============================================
switchTab(tabId) {
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.toggle('active', button.dataset.tab === tabId);
@ -1273,10 +1017,6 @@ class TeeTracker {
}
}
// ============================================
// Dashboard
// ============================================
updateDashboard() {
const totalCups = document.getElementById('total-cups');
const todayCups = document.getElementById('today-cups');
@ -1329,10 +1069,6 @@ class TeeTracker {
}).join('');
}
// ============================================
// Tea List
// ============================================
renderTeasList() {
const container = document.getElementById('teas-list');
if (!container) return;
@ -1346,7 +1082,6 @@ class TeeTracker {
<button class="btn btn-primary add-first-tea-btn"> Tee hinzufügen</button>
</div>
`;
// Add event listener for the add button
const addBtn = container.querySelector('.add-first-tea-btn');
if (addBtn) {
addBtn.addEventListener('click', () => this.openTeaModal());
@ -1418,10 +1153,6 @@ class TeeTracker {
`).join('');
}
// ============================================
// Tea Modal
// ============================================
openTeaModal(teaId = null) {
const modal = document.getElementById('tea-modal');
const form = document.getElementById('tea-form');
@ -1439,12 +1170,10 @@ class TeeTracker {
document.getElementById('tea-organic').checked = tea.organic || false;
document.getElementById('tea-image-preview-img').src = tea.image || '';
document.getElementById('tea-image-preview').style.display = tea.image ? 'block' : 'none';
this.renderStarRating(tea.rating || 3, 'tea-rating');
this.currentTeaId = tea.id;
}
} else {
// New tea
form.reset();
document.getElementById('tea-color').value = '#8B4513';
document.getElementById('tea-organic').checked = false;
@ -1477,8 +1206,6 @@ class TeeTracker {
const color = document.getElementById('tea-color').value;
const organic = document.getElementById('tea-organic').checked;
const image = document.getElementById('tea-image-preview').src || '';
// Get rating from star rating
const rating = this.getStarRating('tea-rating');
if (!name || !type) {
@ -1489,11 +1216,9 @@ class TeeTracker {
const teaData = { name, type, brand, description, color, organic, rating, image };
if (id) {
// Update existing tea
this.updateTea(id, teaData);
this.showToast('Tee-Sorte erfolgreich aktualisiert!', 'success');
} else {
// Add new tea
this.addTea(teaData);
this.showToast('Tee-Sorte erfolgreich hinzugefügt!', 'success');
}
@ -1578,7 +1303,6 @@ class TeeTracker {
const message = `Möchtest du wirklich die Tee-Sorte "${tea ? tea.name : 'Unbekannt'}" löschen? Alle damit verbundenen Einträge werden ebenfalls gelöscht!`;
document.getElementById('confirm-message').textContent = message;
document.getElementById('confirm-modal').classList.add('active');
this.tempConfirmAction = 'deleteTea';
this.currentTeaId = teaId;
}
@ -1610,10 +1334,6 @@ class TeeTracker {
this.currentTeaId = null;
}
// ============================================
// Track Form
// ============================================
renderTrackForm() {
const teaSelect = document.getElementById('track-tea');
const today = new Date().toISOString().split('T')[0];
@ -1662,7 +1382,6 @@ class TeeTracker {
this.showToast('Tee-Konsum erfolgreich getrackt!', 'success');
this.resetTrackForm();
this.updateDashboard();
this.updateStats();
this.renderTrackRecentEntries();
@ -1716,12 +1435,7 @@ class TeeTracker {
}).join('');
}
// ============================================
// Charts
// ============================================
initCharts() {
// Check if Chart.js is loaded
if (typeof Chart === 'undefined') {
console.log('Chart.js not loaded, skipping charts initialization');
return;
@ -1739,7 +1453,6 @@ class TeeTracker {
const labels = Object.keys(typeCounts);
const data = Object.values(typeCounts);
// Define colors for each tea type
const typeColors = {
'Schwarztee': '#8B4513',
'Grüntee': '#228B22',
@ -1800,7 +1513,6 @@ class TeeTracker {
const labels = Object.keys(dailyCounts).sort();
const data = labels.map(date => dailyCounts[date] || 0);
// Format dates for display
const formattedLabels = labels.map(date => {
const d = new Date(date);
return d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric' });
@ -1850,15 +1562,9 @@ class TeeTracker {
});
}
// ============================================
// Statistics
// ============================================
updateStats() {
const period = document.getElementById('stats-period')?.value || 'all';
this.renderDetailedStats(period);
// Update charts
this.createTeaTypeChart();
this.createDailyChart();
}
@ -1900,7 +1606,6 @@ class TeeTracker {
calculateAveragePerDay(period) {
const entries = this.getEntriesByPeriod(period);
if (entries.length === 0) return 0;
const totalCups = this.getCupsByPeriod(period);
const days = this.countUniqueDays(entries);
return days > 0 ? totalCups / days : 0;
@ -1918,10 +1623,6 @@ class TeeTracker {
return teaIds.size;
}
// ============================================
// Toast Notifications
// ============================================
showToast(message, type = 'info') {
const toastContainer = document.getElementById('toast-container');
if (!toastContainer) return;