feat: Add bio checkbox, today period, and recent entries on track page

- Add organic/bio checkbox to tea form and display 🌱 badge
- Add 'Heute' (today) option to stats period filter
- Add recent 10 entries display on track page below form
- Update tea cards and dropdowns to show bio indicator
- Add migration for existing teas without organic field
- Style organic badge with green color and hover effect
- Update all entry displays to show bio status

Generated by Vibe Code

Co-authored-by: trevor1969 <trevor1969@users.noreply.github.com>
This commit is contained in:
Vibe Nuage Agent
2026-06-04 11:10:29 +00:00
parent 2bce6906ef
commit 1263a5238b
3 changed files with 167 additions and 15 deletions

116
app.js
View File

@ -20,7 +20,14 @@ class TeaTracker {
// Load data from localStorage
loadTeas() {
const data = localStorage.getItem(STORAGE_KEY_TEAS);
return data ? JSON.parse(data) : [];
if (!data) return [];
const teas = JSON.parse(data);
// Migrate existing teas to add organic field if missing
return teas.map(tea => ({
...tea,
organic: tea.organic !== undefined ? tea.organic : false
}));
}
loadEntries() {
@ -64,6 +71,7 @@ class TeaTracker {
brand: tea.brand || '',
description: tea.description || '',
color: tea.color || '#8B4513',
organic: tea.organic || false,
createdAt: new Date().toISOString()
};
this.teas.push(newTea);
@ -139,20 +147,14 @@ class TeaTracker {
.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);
}
getCupsByPeriod(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;
@ -174,6 +176,47 @@ class TeaTracker {
.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':
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);
}
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 = {};
@ -389,7 +432,7 @@ class TeaTracker {
return `
<div class="activity-item">
<div class="activity-info">
<div class="activity-tea">${tea ? tea.name : 'Unbekannter Tee'}</div>
<div class="activity-tea">${tea ? tea.name : 'Unbekannter Tee'} ${tea && tea.organic ? '<span class="organic-badge" title="Bio-Tee">🌱</span>' : ''}</div>
<div class="activity-details">${formattedDate} ${formattedTime ? `um ${formattedTime}` : ''}</div>
</div>
<div class="activity-amount">${entry.amount} Tasse${entry.amount > 1 ? 'n' : ''}</div>
@ -420,7 +463,7 @@ class TeaTracker {
<div class="tea-card" data-id="${tea.id}">
<div class="tea-card-header">
<div>
<h3 class="tea-name">${tea.name}</h3>
<h3 class="tea-name">${tea.name} ${tea.organic ? '<span class="organic-badge" title="Bio-Tee">🌱</span>' : ''}</h3>
<span class="tea-type">${tea.type}</span>
</div>
</div>
@ -463,7 +506,7 @@ class TeaTracker {
<div class="tea-card" data-id="${tea.id}">
<div class="tea-card-header">
<div>
<h3 class="tea-name">${tea.name}</h3>
<h3 class="tea-name">${tea.name} ${tea.organic ? '<span class="organic-badge" title="Bio-Tee">🌱</span>' : ''}</h3>
<span class="tea-type">${tea.type}</span>
</div>
</div>
@ -477,6 +520,42 @@ class TeaTracker {
`).join('');
}
// Render recent entries on track page
renderTrackRecentEntries() {
const container = document.getElementById('track-recent-entries');
const recentEntries = [...this.entries]
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.slice(0, 10);
if (recentEntries.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📝</div>
<div class="empty-state-text">Keine Einträge vorhanden</div>
<div class="empty-state-subtext">Fange an, deinen Tee-Konsum zu tracken!</div>
</div>
`;
return;
}
container.innerHTML = recentEntries.map(entry => {
const tea = this.getTeaById(entry.teaId);
const date = new Date(entry.date);
const formattedDate = date.toLocaleDateString('de-DE');
const formattedTime = entry.time ? entry.time.substring(0, 5) : '';
return `
<div class="activity-item">
<div class="activity-info">
<div class="activity-tea">${tea ? tea.name : 'Unbekannter Tee'} ${tea && tea.organic ? '<span class="organic-badge" title="Bio-Tee">🌱</span>' : ''}</div>
<div class="activity-details">${formattedDate} ${formattedTime ? `um ${formattedTime}` : ''}</div>
</div>
<div class="activity-amount">${entry.amount} Tasse${entry.amount > 1 ? 'n' : ''}</div>
</div>
`;
}).join('');
}
openTeaModal(teaId = null) {
const modal = document.getElementById('tea-modal');
const form = document.getElementById('tea-form');
@ -492,6 +571,7 @@ class TeaTracker {
document.getElementById('tea-brand').value = tea.brand || '';
document.getElementById('tea-description').value = tea.description || '';
document.getElementById('tea-color').value = tea.color || '#8B4513';
document.getElementById('tea-organic').checked = tea.organic || false;
deleteBtn.style.display = 'inline-flex';
this.currentTeaId = tea.id;
}
@ -508,6 +588,7 @@ class TeaTracker {
closeTeaModal() {
document.getElementById('tea-modal').classList.remove('active');
document.getElementById('tea-form').reset();
this.currentTeaId = null;
}
@ -519,7 +600,8 @@ class TeaTracker {
type: document.getElementById('tea-type').value,
brand: document.getElementById('tea-brand').value.trim(),
description: document.getElementById('tea-description').value.trim(),
color: document.getElementById('tea-color').value
color: document.getElementById('tea-color').value,
organic: document.getElementById('tea-organic').checked
};
const teaId = document.getElementById('tea-id').value;
@ -602,9 +684,12 @@ class TeaTracker {
} else {
teaSelect.innerHTML = '<option value="">-- Wähle eine Tee-Sorte --</option>' +
this.teas.map(tea =>
`<option value="${tea.id}">${tea.name} (${tea.type})</option>`
`<option value="${tea.id}">${tea.name} (${tea.type})${tea.organic ? ' 🌱' : ''}</option>`
).join('');
}
// Render recent entries
this.renderTrackRecentEntries();
}
handleTrackFormSubmit(e) {
@ -638,6 +723,7 @@ class TeaTracker {
// Update UI
this.updateDashboard();
this.updateStats();
this.renderTrackRecentEntries();
}
resetTrackForm() {