philippurbschat.de/plants/ui.js

553 lines
32 KiB
JavaScript

// Version: 01.10.2025 11:30 (FINAL LEXICON FIX)
import * as api from './api.js';
// === Zustandsvariablen für Event Listener ===
let activeModalListeners = new Map();
let settingsDebounceTimer = null;
let pendingSettings = {};
// === Hilfsfunktionen für Event Listener ===
function addManagedListener(element, event, handler) {
if (element) {
element.addEventListener(event, handler);
if (!activeModalListeners.has(element)) {
activeModalListeners.set(element, []);
}
activeModalListeners.get(element).push({ event, handler });
}
}
function removeAllManagedListeners() {
activeModalListeners.forEach((listeners, element) => {
listeners.forEach(({ event, handler }) => {
element.removeEventListener(event, handler);
});
});
activeModalListeners.clear();
}
// === Allgemeine UI-Funktionen ===
export function showToast(message, isError = false) {
const toast = document.createElement('div');
toast.className = `toast ${isError ? 'error' : ''}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => document.body.removeChild(toast), 300);
}, 3000);
}
// === Pflanzen-Dashboard & Karten ===
export function renderPlantDashboard(plants, dashboardElement) {
dashboardElement.innerHTML = '';
const dashboardControls = document.querySelector('.dashboard-controls');
if (plants.length === 0) {
dashboardElement.innerHTML = `<div class="empty-state"><h2>Noch keine Pflanzen hier!</h2><p>Klicke oben auf "hinzufügen", um deine grüne Oase zu starten.</p></div>`;
if (dashboardControls) dashboardControls.style.display = 'none';
return;
}
if (dashboardControls) dashboardControls.style.display = 'flex';
plants.forEach(plant => {
dashboardElement.appendChild(createPlantCardElement(plant));
dashboardElement.appendChild(createPlantListItemElement(plant));
dashboardElement.appendChild(createPlantCompactCardElement(plant));
});
}
function formatLastDate(dateString) {
if (!dateString || dateString === '0000-00-00') return '';
const lastDate = new Date(dateString);
const today = new Date();
today.setHours(0, 0, 0, 0);
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
lastDate.setHours(0, 0, 0, 0);
if (lastDate.getTime() === today.getTime()) return 'heute';
if (lastDate.getTime() === yesterday.getTime()) return 'gestern';
return `am ${lastDate.toLocaleString('de-DE', { day: '2-digit', month: '2-digit' })}`;
}
function createPlantCardHTML(plant) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const msPerDay = 1000 * 60 * 60 * 24;
const getProgress = (diff, interval) => {
if (diff <= 0) return { percent: 100, status: 'red' };
if (diff >= interval) return { percent: 0, status: 'green' };
return { percent: 100 - ((diff / interval) * 100), status: (diff / interval) < 0.25 ? 'yellow' : 'green' };
};
const getStatusText = (days, compact = false) => {
if (days < 0) return `fällig!`;
if (days === 0) return `heute`;
if (days === 1) return `morgen`;
return `in ${days} ${compact ? 'T.' : 'Tagen'}`;
};
let waterButtonHTML, compactWaterPillHTML;
if (plant.watering_interval > 0 && plant.next_watering_due) {
const nextWatering = new Date(plant.next_watering_due + 'T00:00:00');
const diff = Math.round((nextWatering - today) / msPerDay);
const progress = getProgress(diff, plant.watering_interval);
waterButtonHTML = `<button class="action-btn water-btn" data-action="water"><div class="action-btn-progress" style="width: ${100 - progress.percent}%;" data-status="${progress.status}"></div><div class="action-btn-content"><span>Gießen</span><span class="care-status">${getStatusText(diff)}</span></div></button>`;
compactWaterPillHTML = `<button class="compact-action-pill" data-action="water"><div class="compact-action-progress" style="width: ${100 - progress.percent}%;" data-status="${progress.status}"></div><div class="compact-action-content"><div class="action-label"><span class="action-text">Gießen</span><span class="action-emoji">💧</span></div><span class="care-status">${getStatusText(diff, true)}</span></div></button>`;
} else {
waterButtonHTML = `<button class="action-btn water-btn" data-action="water"><div class="action-btn-progress" style="width: 0%;" data-status="disabled"></div><div class="action-btn-content"><span>Gießen</span><span class="care-status">${formatLastDate(plant.last_watered)}</span></div></button>`;
compactWaterPillHTML = `<button class="compact-action-pill" data-action="water"><div class="compact-action-progress" style="width: 0%;" data-status="disabled"></div><div class="compact-action-content"><div class="action-label"><span class="action-text">Gießen</span><span class="action-emoji">💧</span></div><span class="care-status">${formatLastDate(plant.last_watered)}</span></div></button>`;
}
let fertilizeButtonHTML, compactFertilizePillHTML;
if (plant.fertilizing_interval > 0 && plant.next_fertilizing_due) {
const nextFertilizing = new Date(plant.next_fertilizing_due + 'T00:00:00');
const diff = Math.round((nextFertilizing - today) / msPerDay);
const progress = getProgress(diff, plant.fertilizing_interval);
fertilizeButtonHTML = `<button class="action-btn fertilize-btn" data-action="fertilize"><div class="action-btn-progress" style="width: ${100 - progress.percent}%;" data-status="${progress.status}"></div><div class="action-btn-content"><span>Düngen</span><span class="care-status">${getStatusText(diff)}</span></div></button>`;
compactFertilizePillHTML = `<button class="compact-action-pill" data-action="fertilize"><div class="compact-action-progress" style="width: ${100 - progress.percent}%;" data-status="${progress.status}"></div><div class="compact-action-content"><div class="action-label"><span class="action-text">Düngen</span><span class="action-emoji">🌱</span></div><span class="care-status">${getStatusText(diff, true)}</span></div></button>`;
} else {
fertilizeButtonHTML = `<button class="action-btn fertilize-btn" data-action="fertilize"><div class="action-btn-progress" style="width: 0%;" data-status="disabled"></div><div class="action-btn-content"><span>Düngen</span><span class="care-status">${formatLastDate(plant.last_fertilized)}</span></div></button>`;
compactFertilizePillHTML = `<button class="compact-action-pill" data-action="fertilize"><div class="compact-action-progress" style="width: 0%;" data-status="disabled"></div><div class="compact-action-content"><div class="action-label"><span class="action-text">Düngen</span><span class="action-emoji">🌱</span></div><span class="care-status">${formatLastDate(plant.last_fertilized)}</span></div></button>`;
}
const placeholderImg = 'https://placehold.co/600x600/eeeeee/3D403D?text=' + encodeURIComponent(plant.name);
let tipsContentHTML = plant.latin_name ? `<div class="care-tips-latin-name" data-action="show-lexicon" data-latin-name="${plant.latin_name}" title="Lexikoneintrag öffnen"><div><strong>Botanisch:</strong> <em>${plant.latin_name}</em></div><span class="latin-name-hint">Mehr erfahren &rarr;</span></div>` : '';
if (plant.care_tips) {
tipsContentHTML += plant.care_tips.split(/\n\s*\n/).map(tip => `<p>${tip.replace(/(\p{Emoji}[^:]+:)/gu, '<strong>$1</strong>')}</p>`).join('');
}
if (!plant.care_tips && !plant.latin_name) {
tipsContentHTML = '<p>Keine Pflegetipps hinterlegt.</p>';
}
return { waterButtonHTML, fertilizeButtonHTML, placeholderImg, tipsContentHTML, compactWaterPillHTML, compactFertilizePillHTML };
}
function createPlantCardElement(plant) {
const cardWrapper = document.createElement('div');
cardWrapper.className = 'plant-card';
cardWrapper.dataset.id = plant.id;
const { waterButtonHTML, fertilizeButtonHTML, placeholderImg, tipsContentHTML } = createPlantCardHTML(plant);
cardWrapper.innerHTML = `<div class="plant-card-inner"><div class="plant-card-front"><div class="plant-img-container"><img src="${plant.photo || placeholderImg}" alt="${plant.name}" class="plant-img" onerror="this.src='${placeholderImg}'"><div class="plant-name-overlay"><h3>${plant.name}</h3></div></div><div class="plant-info"><div class="card-actions">${waterButtonHTML}${fertilizeButtonHTML}</div></div><div class="card-overlay"><button class="card-btn" data-action="open-diary" title="Tagebuch"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg></button>${(plant.care_tips || plant.latin_name) ? `<button class="card-btn" data-action="flip" title="Pflegetipps"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></button>` : ''}<div class="menu-container"><button class="card-btn" data-action="toggle-menu" title="Menü"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="menu-dropdown"><button data-action="edit">Bearbeiten</button><button data-action="delete" class="delete-btn">Löschen</button></div></div></div></div><div class="plant-card-back" data-action="flip-back"><h3>${plant.name}</h3><div class="care-tips-content">${tipsContentHTML}</div><div class="back-controls"><span><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg> Zum Umdrehen klicken</span></div></div></div>`;
return cardWrapper;
}
function createPlantListItemElement(plant) {
const listItem = document.createElement('div');
listItem.className = 'plant-list-item';
listItem.dataset.id = plant.id;
const { waterButtonHTML, fertilizeButtonHTML, placeholderImg, tipsContentHTML } = createPlantCardHTML(plant);
listItem.innerHTML = `<div class="list-item-img-container"><img src="${plant.photo || placeholderImg}" alt="${plant.name}" class="list-item-img" onerror="this.src='${placeholderImg}'"></div><div class="list-item-info"><h3>${plant.name}</h3></div><div class="list-item-actions">${waterButtonHTML}${fertilizeButtonHTML}</div><div class="list-item-menu"><button class="card-btn" data-action="open-diary" title="Tagebuch"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg></button>${(plant.care_tips || plant.latin_name) ? `<button class="card-btn" data-action="show-care-tips" title="Pflegetipps"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></button>` : ''}<div class="menu-container"><button class="card-btn" data-action="toggle-menu" title="Menü"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg></button><div class="menu-dropdown"><button data-action="edit">Bearbeiten</button><button data-action="delete" class="delete-btn">Löschen</button></div></div></div><div class="list-item-care-tips"><div class="list-item-care-tips-content">${tipsContentHTML}</div></div>`;
return listItem;
}
function createPlantCompactCardElement(plant) {
const cardWrapper = document.createElement('div');
cardWrapper.className = 'plant-compact-card';
cardWrapper.dataset.id = plant.id;
const { placeholderImg, compactWaterPillHTML, compactFertilizePillHTML } = createPlantCardHTML(plant);
cardWrapper.innerHTML = `<div class="compact-img-container" data-action="show-detail"><img src="${plant.photo || placeholderImg}" alt="${plant.name}" class="compact-img" onerror="this.src='${placeholderImg}'"><div class="compact-name-overlay">${plant.name}</div></div><div class="compact-card-actions">${compactWaterPillHTML}${compactFertilizePillHTML}</div>`;
return cardWrapper;
}
// === Pflanzen-Modal ===
export function openPlantModal(plant = null, handlers = {}) {
const plantModal = document.getElementById('plantModal');
const plantForm = document.getElementById('plantForm');
if (!plantModal || !plantForm) return;
plantForm.reset();
clearAiResults(plantForm);
if (plant) {
plantModal.querySelector('#modalTitle').textContent = 'Pflanze bearbeiten';
document.getElementById('ai-analysis-section').style.display = 'none';
plantForm.querySelector('#plantId').value = plant.id;
plantForm.querySelector('#plantName').value = plant.name;
plantForm.querySelector('#latinName').value = plant.latin_name || '';
plantForm.querySelector('#wateringInterval').value = plant.watering_interval > 0 ? plant.watering_interval : '';
plantForm.querySelector('#fertilizingInterval').value = plant.fertilizing_interval > 0 ? plant.fertilizing_interval : '';
plantForm.querySelector('#careTips').value = plant.care_tips || '';
plantForm.querySelector('#currentPhoto').value = plant.photo || '';
if (plant.photo) showImagePreview(plant.photo);
else {
document.getElementById('initialUploadArea').style.display = 'flex';
document.getElementById('imagePreviewContainer').style.display = 'none';
}
} else {
plantModal.querySelector('#modalTitle').textContent = 'Neue Pflanze hinzufügen';
document.getElementById('ai-analysis-section').style.display = 'none';
document.getElementById('initialUploadArea').style.display = 'flex';
document.getElementById('imagePreviewContainer').style.display = 'none';
}
addManagedListener(plantForm, 'submit', handlers.onSubmit);
addManagedListener(document.getElementById('plantPhoto'), 'change', handlers.onPhotoSelect);
addManagedListener(document.getElementById('analyzeBtn'), 'click', handlers.onAnalyze);
addManagedListener(document.getElementById('retryAiBtn'), 'click', handlers.onAnalyze);
addManagedListener(document.getElementById('latinName'), 'blur', handlers.onLatinNameBlur);
addManagedListener(document.getElementById('startCameraBtn'), 'click', () => startCamera('main'));
addManagedListener(document.getElementById('retakePhotoBtn'), 'click', () => startCamera('main'));
addManagedListener(document.getElementById('captureBtn'), 'click', () => capturePhoto('main'));
addManagedListener(plantModal.querySelector('.close-btn'), 'click', closePlantModal);
addManagedListener(plantModal, 'click', (e) => { if (e.target === plantModal) closePlantModal(); });
plantModal.classList.add('show');
document.body.classList.add('modal-open');
}
export function closePlantModal() {
const plantModal = document.getElementById('plantModal');
if (plantModal) {
stopCamera('main');
removeAllManagedListeners();
plantModal.classList.remove('show');
if (!document.querySelector('.modal.show')) {
document.body.classList.remove('modal-open');
}
}
}
// === Detail Overlay (Mobile) ===
export function openPlantDetailOverlay(plant) {
const plantDetailOverlay = document.getElementById('plantDetailOverlay');
const plantDetailContent = document.getElementById('plantDetailContent');
if (plantDetailOverlay && plantDetailContent) {
plantDetailContent.innerHTML = '';
plantDetailContent.appendChild(createPlantCardElement(plant));
plantDetailOverlay.classList.add('show');
document.body.classList.add('modal-open');
}
}
export function closePlantDetailOverlay() {
const plantDetailOverlay = document.getElementById('plantDetailOverlay');
if (plantDetailOverlay) {
plantDetailOverlay.classList.remove('show');
if (!document.querySelector('.modal.show')) {
document.body.classList.remove('modal-open');
}
}
}
// === KI UI-Funktionen ===
export function showAiSection() {
document.getElementById('ai-analysis-section').style.display = 'flex';
}
export function clearAiResults(form) {
if (!form) return;
form.querySelectorAll('input[name^="ai_"]').forEach(el => el.value = '');
document.getElementById('retryAiBtn').style.display = 'none';
showEnrichmentLoader(false);
}
export function runAiAnimation(container) {
if(!container) return () => {};
const analysisTextElement = container.querySelector('.ai-analysis-text');
const analysisMessages = ["Initialisiere Scan", "Analysiere Blattform", "Extrahiere Merkmale", "Vergleiche Datenbank", "Bestimme Gattung", "Finalisiere..."];
let messageIndex = 0;
container.classList.add('scanning');
if(analysisTextElement) analysisTextElement.textContent = analysisMessages[messageIndex];
const textInterval = setInterval(() => {
messageIndex = (messageIndex + 1) % analysisMessages.length;
if(analysisTextElement) analysisTextElement.textContent = analysisMessages[messageIndex];
}, 1500);
return function cleanup() {
clearInterval(textInterval);
container.classList.remove('scanning');
};
}
function ensureEnrichmentLoaders() {
['wateringInterval', 'fertilizingInterval', 'careTips'].forEach(fieldId => {
const label = document.querySelector(`label[for="${fieldId}"]`);
if (label && !label.querySelector('.enrichment-loader')) {
const loader = document.createElement('span');
loader.className = 'enrichment-loader';
loader.style.display = 'none';
label.appendChild(loader);
}
});
}
export function showEnrichmentLoader(isLoading) {
ensureEnrichmentLoaders();
document.querySelectorAll('.enrichment-loader').forEach(loader => {
loader.style.display = isLoading ? 'inline-block' : 'none';
});
}
export function populateFormWithIdentification(form, idData) {
form.querySelector('#plantName').value = idData.name || '';
form.querySelector('#latinName').value = idData.latin_name || '';
}
export function populateFormWithEnrichment(form, enrichmentData, settings) {
if (settings.auto_fill_watering && form.querySelector('#wateringInterval').value === '') {
form.querySelector('#wateringInterval').value = enrichmentData.watering_interval_days || '';
}
if (settings.auto_fill_fertilizing && form.querySelector('#fertilizingInterval').value === '') {
form.querySelector('#fertilizingInterval').value = enrichmentData.fertilizing_interval_days || '';
}
if (settings.auto_fill_care_tips && form.querySelector('#careTips').value === '') {
form.querySelector('#careTips').value = enrichmentData.care_tips || '';
}
const enrichmentField = form.querySelector('input[name="ai_enrichment_json"]');
if (enrichmentField) {
enrichmentField.value = JSON.stringify(enrichmentData);
}
}
// === Tagebuch-Modal ===
export function openDiaryModal(plant) {
const diaryModal = document.getElementById('diaryModal');
if(!diaryModal) return;
diaryModal.querySelector('#diaryPlantName').textContent = plant.name;
diaryModal.querySelector('#diaryPlantId').value = plant.id;
document.getElementById('diaryForm')?.reset();
removeDiaryPhoto();
const diaryContent = document.getElementById('diaryContent');
if(diaryContent) diaryContent.innerHTML = '<p class="empty-diary">Lade Einträge...</p>';
api.getDiaryEntries(plant.id)
.then(entries => renderDiaryEntries(entries, plant.id))
.catch(() => {
if(diaryContent) diaryContent.innerHTML = '<p class="empty-diary">Einträge konnten nicht geladen werden.</p>';
showToast('Tagebuch konnte nicht geladen werden.', true);
});
addManagedListener(document.getElementById('diaryForm'), 'submit', handleDiaryFormSubmit);
addManagedListener(diaryModal.querySelector('.close-btn'), 'click', closeDiaryModal);
addManagedListener(diaryModal, 'click', (e) => { if (e.target === diaryModal) closeDiaryModal(); });
diaryModal.classList.add('show');
document.body.classList.add('modal-open');
}
async function handleDiaryFormSubmit(e) {
e.preventDefault();
const diaryForm = document.getElementById('diaryForm');
const saveBtn = diaryForm.querySelector('button[type="submit"]');
saveBtn.disabled = true;
try {
const result = await api.addDiaryEntry(new FormData(diaryForm));
if(result.success) {
showToast('Eintrag gespeichert!');
const plantId = result.entry.plant_id;
const entries = await api.getDiaryEntries(plantId);
renderDiaryEntries(entries, plantId);
diaryForm.reset();
removeDiaryPhoto();
} else { throw new Error(result.error); }
} catch (error) { showToast('Eintrag konnte nicht gespeichert werden.', true);
} finally {
saveBtn.disabled = false;
}
}
export function closeDiaryModal() {
const diaryModal = document.getElementById('diaryModal');
if (diaryModal) {
stopCamera('diary');
removeAllManagedListeners();
diaryModal.classList.remove('show');
if (!document.querySelector('.modal.show')) {
document.body.classList.remove('modal-open');
}
}
}
export function renderDiaryEntries(entries, plantId) {
const diaryContent = document.getElementById('diaryContent');
if (!diaryContent) return;
diaryContent.innerHTML = '';
if (entries.length === 0) {
diaryContent.innerHTML = '<p class="empty-diary">Noch keine Einträge vorhanden.</p>';
return;
}
const groupedEntries = entries.reduce((acc, entry) => {
const date = new Date(entry.entry_date).toLocaleDateString('de-DE', { year: 'numeric', month: 'long', day: 'numeric' });
if (!acc[date]) acc[date] = [];
acc[date].push(entry);
return acc;
}, {});
for (const date in groupedEntries) {
const dateHeader = document.createElement('h4');
dateHeader.className = 'diary-date-header';
dateHeader.textContent = date;
diaryContent.appendChild(dateHeader);
groupedEntries[date].forEach(entry => diaryContent.appendChild(createDiaryEntryElement(entry, plantId)));
}
}
function createDiaryEntryElement(entry, plantId) {
const div = document.createElement('div');
div.className = `diary-entry ${entry.entry_type === 'milestone' ? 'is-milestone' : ''}`;
div.dataset.plantId = plantId;
div.dataset.entryId = entry.id;
if(entry.photo) div.dataset.photo = entry.photo;
const icons = { manual: '📝', watered: '💧', fertilized: '🌱', milestone: '⭐', added: '🎉' };
const titles = { manual: 'Notiz', watered: 'Gegossen', fertilized: 'Gedüngt', milestone: 'Meilenstein', added: 'Pflanze hinzugefügt' };
let effectiveType = (entry.entry_type === 'milestone' && entry.notes === 'Pflanze hinzugefügt') ? 'added' : entry.entry_type;
let photoHTML = entry.photo ? `<div class="diary-photo-container"><img src="${entry.photo}" class="diary-photo"><button class="card-btn set-profile-btn" data-action="set-profile-pic" title="Als Profilbild"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg></button></div>` : '';
div.innerHTML = `<div class="diary-icon">${icons[effectiveType] || '📌'}</div><div class="diary-entry-content"><button class="card-btn delete-diary-entry-btn" data-action="delete-diary-entry" title="Löschen"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button><p class="diary-entry-title">${titles[effectiveType] || 'Eintrag'}</p>${(entry.notes && effectiveType !== 'added') ? `<p class="diary-entry-notes">${entry.notes.replace(/\n/g, '<br>')}</p>` : ''}${photoHTML}<span class="diary-entry-time">${new Date(entry.entry_date).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</span></div>`;
return div;
}
export function removeDiaryPhoto() {
const diaryPhotoInput = document.getElementById('diaryPhoto');
if(diaryPhotoInput) diaryPhotoInput.value = '';
document.getElementById('diaryImagePreviewContainer').style.display = 'none';
document.getElementById('diaryInitialUploadArea').style.display = 'flex';
}
// === Settings Modal ---
function handleSettingsChange(e, onSave) {
const target = e.target;
let key, value;
if (target.matches('.toggle-switch input')) {
key = target.id.replace(/-/g, '_');
value = target.checked;
} else if (target.matches('.theme-btn')) {
key = 'theme';
value = target.dataset.theme;
document.body.dataset.theme = value;
document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.remove('active'));
target.classList.add('active');
} else {
return;
}
pendingSettings[key] = value;
clearTimeout(settingsDebounceTimer);
settingsDebounceTimer = setTimeout(() => {
if (Object.keys(pendingSettings).length > 0) {
onSave(pendingSettings);
pendingSettings = {};
}
}, 750);
}
export function openSettingsModal(data, onSave) {
const modal = document.getElementById('settingsModal');
if (modal) {
populateSettingsModal(data);
addManagedListener(modal, 'change', (e) => handleSettingsChange(e, onSave));
addManagedListener(modal, 'click', (e) => handleSettingsChange(e, onSave));
addManagedListener(modal.querySelector('.close-btn'), 'click', closeSettingsModal);
addManagedListener(modal, 'click', (e) => { if(e.target === modal) closeSettingsModal(); });
modal.classList.add('show');
document.body.classList.add('modal-open');
}
}
export function closeSettingsModal() {
const modal = document.getElementById('settingsModal');
if (modal) {
removeAllManagedListeners();
modal.classList.remove('show');
if (!document.querySelector('.modal.show')) {
document.body.classList.remove('modal-open');
}
}
}
function populateSettingsModal(data) {
if (!data) return;
const safeSet = (id, property, value) => {
const el = document.getElementById(id);
if (el) el[property] = value;
};
safeSet('settings-profile-pic', 'src', data.profile_image || `https://placehold.co/100x100/eeeeee/3D403D?text=${data.username.charAt(0)}`);
safeSet('settings-username', 'textContent', data.username);
safeSet('stats-plant-count', 'textContent', data.plant_count);
safeSet('stats-ai-credits', 'textContent', data.ai_credits);
safeSet('allow-photo-sharing', 'checked', data.allow_photo_sharing == 1);
safeSet('auto-fill-watering', 'checked', data.auto_fill_watering == 1);
safeSet('auto-fill-fertilizing', 'checked', data.auto_fill_fertilizing == 1);
safeSet('auto-fill-care-tips', 'checked', data.auto_fill_care_tips == 1);
document.querySelectorAll('.theme-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.theme === data.theme);
});
}
// === Kamera UI-Funktionen ===
let cameraStream = null;
export function showImagePreview(src) {
const imagePreview = document.getElementById('imagePreview');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const initialUploadArea = document.getElementById('initialUploadArea');
if (imagePreview) imagePreview.src = src;
if (imagePreviewContainer) imagePreviewContainer.style.display = 'block';
if (initialUploadArea) initialUploadArea.style.display = 'none';
}
export async function startCamera(context = 'main') {
if (cameraStream) stopCamera(context);
const elements = context === 'main'
? { view: document.getElementById('cameraView'), form: document.getElementById('formContent'), video: document.getElementById('videoElement') }
: { view: document.getElementById('diaryCameraView'), form: document.getElementById('diaryContentWrapper'), video: document.getElementById('diaryVideoElement'), footer: document.querySelector('#diaryModal .modal-footer') };
if (!elements.view || !elements.form || !elements.video) return;
try {
cameraStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: 1080, height: 1080 } });
elements.video.srcObject = cameraStream;
elements.form.style.display = 'none';
if(elements.footer) elements.footer.style.display = 'none';
elements.view.style.display = 'block';
} catch (err) {
showToast("Kamerazugriff verweigert.", true);
}
}
export function stopCamera(context = 'main') {
if (cameraStream) {
cameraStream.getTracks().forEach(track => track.stop());
cameraStream = null;
}
const elements = context === 'main'
? { view: document.getElementById('cameraView'), form: document.getElementById('formContent') }
: { view: document.getElementById('diaryCameraView'), form: document.getElementById('diaryContentWrapper'), footer: document.querySelector('#diaryModal .modal-footer') };
if (elements.view) elements.view.style.display = 'none';
if (elements.form) elements.form.style.display = 'block';
if (elements.footer) elements.footer.style.display = 'block';
}
export function capturePhoto(context = 'main') {
const elements = context === 'main'
? { canvas: document.getElementById('photoCanvas'), video: document.getElementById('videoElement'), input: document.getElementById('plantPhoto'), showPreview: showImagePreview }
: { canvas: document.getElementById('diaryPhotoCanvas'), video: document.getElementById('diaryVideoElement'), input: document.getElementById('diaryPhoto'), showPreview: (files) => document.getElementById('plantPhoto').dispatchEvent(new CustomEvent('change', { detail: files })) };
if (!elements.canvas || !elements.video || !elements.input) return;
const context2d = elements.canvas.getContext('2d');
const size = Math.min(elements.video.videoWidth, elements.video.videoHeight);
elements.canvas.width = size;
elements.canvas.height = size;
context2d.drawImage(elements.video, (elements.video.videoWidth - size) / 2, (elements.video.videoHeight - size) / 2, size, size, 0, 0, size, size);
const dataUrl = elements.canvas.toDataURL('image/webp');
if (context === 'main') {
elements.showPreview(dataUrl);
showAiSection();
clearAiResults(document.getElementById('plantForm'));
}
elements.canvas.toBlob(blob => {
const imageFile = new File([blob], `aufnahme.webp`, { type: 'image/webp' });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(imageFile);
elements.input.files = dataTransfer.files;
// Manually trigger change event
const changeEvent = new Event('change', { bubbles: true });
elements.input.dispatchEvent(changeEvent);
stopCamera(context);
}, 'image/webp');
}