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 // Load data from localStorage
loadTeas() { loadTeas() {
const data = localStorage.getItem(STORAGE_KEY_TEAS); 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() { loadEntries() {
@ -64,6 +71,7 @@ class TeaTracker {
brand: tea.brand || '', brand: tea.brand || '',
description: tea.description || '', description: tea.description || '',
color: tea.color || '#8B4513', color: tea.color || '#8B4513',
organic: tea.organic || false,
createdAt: new Date().toISOString() createdAt: new Date().toISOString()
}; };
this.teas.push(newTea); this.teas.push(newTea);
@ -139,20 +147,14 @@ class TeaTracker {
.reduce((sum, entry) => sum + entry.amount, 0); .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) { getCupsByPeriod(period) {
const now = new Date(); const now = new Date();
let startDate; let startDate;
switch (period) { switch (period) {
case 'today':
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
break;
case 'week': case 'week':
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
break; break;
@ -174,6 +176,47 @@ class TeaTracker {
.reduce((sum, entry) => sum + entry.amount, 0); .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') { getCupsByTeaType(period = 'all') {
const entries = this.getEntriesByPeriod(period); const entries = this.getEntriesByPeriod(period);
const typeCounts = {}; const typeCounts = {};
@ -389,7 +432,7 @@ class TeaTracker {
return ` return `
<div class="activity-item"> <div class="activity-item">
<div class="activity-info"> <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 class="activity-details">${formattedDate} ${formattedTime ? `um ${formattedTime}` : ''}</div>
</div> </div>
<div class="activity-amount">${entry.amount} Tasse${entry.amount > 1 ? 'n' : ''}</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" data-id="${tea.id}">
<div class="tea-card-header"> <div class="tea-card-header">
<div> <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> <span class="tea-type">${tea.type}</span>
</div> </div>
</div> </div>
@ -463,7 +506,7 @@ class TeaTracker {
<div class="tea-card" data-id="${tea.id}"> <div class="tea-card" data-id="${tea.id}">
<div class="tea-card-header"> <div class="tea-card-header">
<div> <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> <span class="tea-type">${tea.type}</span>
</div> </div>
</div> </div>
@ -477,6 +520,42 @@ class TeaTracker {
`).join(''); `).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) { openTeaModal(teaId = null) {
const modal = document.getElementById('tea-modal'); const modal = document.getElementById('tea-modal');
const form = document.getElementById('tea-form'); const form = document.getElementById('tea-form');
@ -492,6 +571,7 @@ class TeaTracker {
document.getElementById('tea-brand').value = tea.brand || ''; document.getElementById('tea-brand').value = tea.brand || '';
document.getElementById('tea-description').value = tea.description || ''; document.getElementById('tea-description').value = tea.description || '';
document.getElementById('tea-color').value = tea.color || '#8B4513'; document.getElementById('tea-color').value = tea.color || '#8B4513';
document.getElementById('tea-organic').checked = tea.organic || false;
deleteBtn.style.display = 'inline-flex'; deleteBtn.style.display = 'inline-flex';
this.currentTeaId = tea.id; this.currentTeaId = tea.id;
} }
@ -508,6 +588,7 @@ class TeaTracker {
closeTeaModal() { closeTeaModal() {
document.getElementById('tea-modal').classList.remove('active'); document.getElementById('tea-modal').classList.remove('active');
document.getElementById('tea-form').reset();
this.currentTeaId = null; this.currentTeaId = null;
} }
@ -519,7 +600,8 @@ class TeaTracker {
type: document.getElementById('tea-type').value, type: document.getElementById('tea-type').value,
brand: document.getElementById('tea-brand').value.trim(), brand: document.getElementById('tea-brand').value.trim(),
description: document.getElementById('tea-description').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; const teaId = document.getElementById('tea-id').value;
@ -602,9 +684,12 @@ class TeaTracker {
} else { } else {
teaSelect.innerHTML = '<option value="">-- Wähle eine Tee-Sorte --</option>' + teaSelect.innerHTML = '<option value="">-- Wähle eine Tee-Sorte --</option>' +
this.teas.map(tea => 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(''); ).join('');
} }
// Render recent entries
this.renderTrackRecentEntries();
} }
handleTrackFormSubmit(e) { handleTrackFormSubmit(e) {
@ -638,6 +723,7 @@ class TeaTracker {
// Update UI // Update UI
this.updateDashboard(); this.updateDashboard();
this.updateStats(); this.updateStats();
this.renderTrackRecentEntries();
} }
resetTrackForm() { resetTrackForm() {

View File

@ -110,6 +110,11 @@
<button type="button" id="cancel-track" class="btn btn-secondary">Abbrechen</button> <button type="button" id="cancel-track" class="btn btn-secondary">Abbrechen</button>
</div> </div>
</form> </form>
<div class="recent-tracked">
<h3>Letzte Einträge</h3>
<div id="track-recent-entries" class="activity-list"></div>
</div>
</section> </section>
<!-- Statistiken Tab --> <!-- Statistiken Tab -->
@ -119,6 +124,7 @@
<div class="stats-filters"> <div class="stats-filters">
<label for="stats-period">Zeitraum:</label> <label for="stats-period">Zeitraum:</label>
<select id="stats-period"> <select id="stats-period">
<option value="today">Heute</option>
<option value="week">Letzte 7 Tage</option> <option value="week">Letzte 7 Tage</option>
<option value="month">Letzter Monat</option> <option value="month">Letzter Monat</option>
<option value="year">Letztes Jahr</option> <option value="year">Letztes Jahr</option>
@ -186,6 +192,14 @@
<textarea id="tea-description" rows="3" placeholder="Beschreibung des Tees..."></textarea> <textarea id="tea-description" rows="3" placeholder="Beschreibung des Tees..."></textarea>
</div> </div>
<div class="form-group checkbox-group">
<label>
<input type="checkbox" id="tea-organic">
<span class="checkmark"></span>
Bio-Tee
</label>
</div>
<div class="form-group"> <div class="form-group">
<label for="tea-color">Farbe</label> <label for="tea-color">Farbe</label>
<input type="color" id="tea-color" value="#8B4513"> <input type="color" id="tea-color" value="#8B4513">

View File

@ -378,6 +378,45 @@ header p {
min-height: 80px; min-height: 80px;
} }
/* Checkbox Styling */
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
font-weight: normal;
color: var(--text-color);
}
.checkbox-group input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary-color);
}
/* Recent tracked section */
.recent-tracked {
background-color: var(--card-bg);
border-radius: var(--border-radius);
padding: 20px;
box-shadow: var(--box-shadow);
margin-top: 30px;
}
.recent-tracked h3 {
margin-bottom: 15px;
color: var(--text-color);
border-bottom: 2px solid var(--primary-light);
padding-bottom: 10px;
}
.form-actions { .form-actions {
display: flex; display: flex;
gap: 15px; gap: 15px;
@ -763,3 +802,16 @@ header p {
margin-right: 10px; margin-right: 10px;
border: 2px solid #ddd; border: 2px solid #ddd;
} }
/* Organic Badge */
.organic-badge {
display: inline-block;
font-size: 0.9em;
margin-left: 5px;
color: var(--success-color);
cursor: help;
}
.organic-badge:hover {
transform: scale(1.1);
}