diff --git a/web/i18n/de.json b/web/i18n/de.json index 7bbe21a..bcaba5c 100644 --- a/web/i18n/de.json +++ b/web/i18n/de.json @@ -26,6 +26,7 @@ "timeout": "Zeit\u00fcberschreitung bei Standortabfrage." }, "add": "Profil hinzuf\u00fcgen", + "save": "Profil speichern", "edit": "Bearbeiten", "delete": "L\u00f6schen", "noItems": "Noch keine Profile. Erstellen Sie eines, um zu beginnen." @@ -62,6 +63,7 @@ "tooltip": "Aktuelle oder gew\u00fcnschte Raumtemperatur. Standard 25\u00b0C." }, "add": "Raum hinzuf\u00fcgen", + "save": "Raum speichern", "noItems": "Noch keine R\u00e4ume. F\u00fcgen Sie R\u00e4ume zu Ihrem Profil hinzu." }, "devices": { @@ -75,6 +77,7 @@ "wattsPeak": { "label": "Watt (Spitze)", "tooltip": "Leistungsaufnahme bei Maximallast (z.B. Gaming)" }, "dutyCycle": { "label": "Einschaltdauer", "tooltip": "Anteil der aktiven Zeit (0\u20131). K\u00fchlschrank \u2248 0,3, PC \u2248 1,0." }, "add": "Ger\u00e4t hinzuf\u00fcgen", + "save": "Ger\u00e4t speichern", "noItems": "Noch keine Ger\u00e4te." }, "occupants": { @@ -89,6 +92,7 @@ }, "vulnerable": { "label": "Schutzbed\u00fcrftig", "tooltip": "Ankreuzen bei \u00e4lteren Menschen, Kleinkindern oder gesundheitlich eingeschr\u00e4nkten Personen. F\u00fcgt Pflegeerinnerungen hinzu." }, "add": "Bewohner hinzuf\u00fcgen", + "save": "Bewohner speichern", "noItems": "Noch keine Bewohner." }, "ac": { @@ -105,6 +109,7 @@ "dehumidify": { "label": "Entfeuchtung", "tooltip": "Ob das Ger\u00e4t einen Entfeuchtungsmodus hat" }, "rooms": { "label": "Zugewiesene R\u00e4ume", "tooltip": "Welche R\u00e4ume dieses Klimager\u00e4t versorgt" }, "add": "Klimager\u00e4t hinzuf\u00fcgen", + "save": "Klimager\u00e4t speichern", "noItems": "Noch keine Klimager\u00e4te." }, "toggles": { @@ -173,7 +178,23 @@ "riskLow": "Niedrig", "riskModerate": "Mittel", "riskHigh": "Hoch", - "riskExtreme": "Extrem" + "riskExtreme": "Extrem", + "noActions": "Keine Maßnahmen", + "effort": "Aufwand", + "impact": "Wirkung", + "aiDisclaimer": "KI-generierte Zusammenfassung. Kein Ersatz für professionelle Beratung.", + "coolVentilate": "Fenster öffnen", + "coolAC": "Klimaanlage", + "coolOverloaded": "Klima überlastet", + "aiActions": "KI-empfohlene Maßnahmen", + "category": { + "shading": "Verschattung", + "ventilation": "Lüftung", + "internal_gains": "Wärmequellen", + "ac_strategy": "Klimastrategie", + "hydration": "Flüssigkeit", + "care": "Pflege" + } }, "guide": { "title": "Erste Schritte", diff --git a/web/i18n/en.json b/web/i18n/en.json index a957a01..b46aea8 100644 --- a/web/i18n/en.json +++ b/web/i18n/en.json @@ -26,6 +26,7 @@ "timeout": "Location request timed out." }, "add": "Add Profile", + "save": "Save Profile", "edit": "Edit", "delete": "Delete", "noItems": "No profiles yet. Create one to get started." @@ -62,6 +63,7 @@ "tooltip": "Current or target indoor temperature. Default 25\u00b0C." }, "add": "Add Room", + "save": "Save Room", "noItems": "No rooms yet. Add rooms to your profile." }, "devices": { @@ -75,6 +77,7 @@ "wattsPeak": { "label": "Watts (Peak)", "tooltip": "Power draw at maximum load (e.g. gaming)" }, "dutyCycle": { "label": "Duty Cycle", "tooltip": "Fraction of time active (0\u20131). Fridge \u2248 0.3, PC \u2248 1.0." }, "add": "Add Device", + "save": "Save Device", "noItems": "No devices yet." }, "occupants": { @@ -89,6 +92,7 @@ }, "vulnerable": { "label": "Vulnerable", "tooltip": "Check if elderly, young children, or health-compromised. Adds care reminders." }, "add": "Add Occupants", + "save": "Save Occupants", "noItems": "No occupants yet." }, "ac": { @@ -105,6 +109,7 @@ "dehumidify": { "label": "Dehumidify", "tooltip": "Whether the unit has a dehumidify mode" }, "rooms": { "label": "Assigned Rooms", "tooltip": "Which rooms this AC unit serves" }, "add": "Add AC Unit", + "save": "Save AC Unit", "noItems": "No AC units yet." }, "toggles": { @@ -173,7 +178,23 @@ "riskLow": "Low", "riskModerate": "Moderate", "riskHigh": "High", - "riskExtreme": "Extreme" + "riskExtreme": "Extreme", + "noActions": "No actions", + "effort": "Effort", + "impact": "Impact", + "aiDisclaimer": "AI-generated summary. Not a substitute for professional advice.", + "coolVentilate": "Open windows", + "coolAC": "AC cooling", + "coolOverloaded": "AC overloaded", + "aiActions": "AI-recommended actions", + "category": { + "shading": "Shading", + "ventilation": "Ventilation", + "internal_gains": "Heat Sources", + "ac_strategy": "AC Strategy", + "hydration": "Hydration", + "care": "Care" + } }, "guide": { "title": "Getting Started", diff --git a/web/js/setup.js b/web/js/setup.js index adf585e..9e3095c 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -2,6 +2,10 @@ (function() { "use strict"; + // SVG icon templates + const iconEdit = ''; + const iconDelete = ''; + // Tab switching const tabBtns = document.querySelectorAll(".tab-btn"); const tabPanels = document.querySelectorAll(".tab-panel"); @@ -67,6 +71,7 @@ form.reset(); const hidden = form.querySelector('input[name="id"]'); if (hidden) hidden.value = ""; + exitEditMode(form); } function numOrDefault(val, def) { @@ -74,6 +79,47 @@ return isNaN(n) ? def : n; } + // Edit mode helpers + function enterEditMode(form) { + form.classList.add("ring-2", "ring-orange-400"); + const submitBtn = form.querySelector(".submit-btn"); + const cancelBtn = form.querySelector(".cancel-btn"); + if (submitBtn) submitBtn.textContent = submitBtn.dataset.saveText; + if (cancelBtn) cancelBtn.classList.remove("hidden"); + } + + function exitEditMode(form) { + form.classList.remove("ring-2", "ring-orange-400"); + const submitBtn = form.querySelector(".submit-btn"); + const cancelBtn = form.querySelector(".cancel-btn"); + if (submitBtn) submitBtn.textContent = submitBtn.dataset.addText; + if (cancelBtn) cancelBtn.classList.add("hidden"); + } + + // Wire up all cancel buttons + document.querySelectorAll(".cancel-btn").forEach(btn => { + btn.addEventListener("click", () => { + const form = btn.closest("form"); + if (form) resetForm(form); + }); + }); + + // List item card classes + const cardClasses = "bg-white dark:bg-gray-800 rounded-lg p-3 shadow-sm flex items-center justify-between hover:shadow-md transition-all duration-200"; + + // Action button builders + function editBtn(onclick) { + return ``; + } + + function deleteBtn(onclick) { + return ``; + } + + function actionGroup(editOnclick, deleteOnclick) { + return `
${editBtn(editOnclick)}${deleteBtn(deleteOnclick)}
`; + } + // ========== Profiles ========== async function loadProfiles() { const profiles = await dbGetAll("profiles"); @@ -86,17 +132,16 @@ list.innerHTML = profiles.map(p => { const isActive = activeId === p.id; return ` -
+
${esc(p.name)} ${p.latitude.toFixed(4)}, ${p.longitude.toFixed(4)} · ${esc(p.timezone || "")}
-
+
- - + ${actionGroup(`editProfileUI(${p.id})`, `deleteProfileUI(${p.id})`)}
`; }).join(""); @@ -118,6 +163,7 @@ form.querySelector('input[name="latitude"]').value = p.latitude; form.querySelector('input[name="longitude"]').value = p.longitude; form.querySelector('input[name="timezone"]').value = p.timezone || "Europe/Berlin"; + enterEditMode(form); form.querySelector('input[name="name"]').focus(); }; @@ -188,13 +234,12 @@ return; } list.innerHTML = rooms.map(r => ` -
+
${esc(r.name)} ${r.areaSqm}m² · ${r.orientation} · SHGC ${r.shgc} · ${r.indoorTempC || 25}°C
- - + ${actionGroup(`editRoomUI(${r.id})`, `deleteRoomUI(${r.id})`)}
`).join(""); } @@ -216,6 +261,7 @@ form.querySelector('input[name="shgc"]').value = r.shgc || 0.6; form.querySelector('select[name="insulation"]').value = r.insulation || "average"; form.querySelector('input[name="indoorTempC"]').value = r.indoorTempC || 25; + enterEditMode(form); form.querySelector('input[name="name"]').focus(); }; @@ -276,13 +322,12 @@ return; } list.innerHTML = allDevices.map(d => ` -
+
${esc(d.name)} ${esc(d._roomName)} · ${d.wattsTypical}W typical
- - + ${actionGroup(`editDeviceUI(${d.id})`, `deleteDeviceUI(${d.id})`)}
`).join(""); } @@ -299,6 +344,7 @@ form.querySelector('input[name="wattsTypical"]').value = d.wattsTypical || 0; form.querySelector('input[name="wattsPeak"]').value = d.wattsPeak || 0; form.querySelector('input[name="dutyCycle"]').value = d.dutyCycle ?? 1.0; + enterEditMode(form); form.querySelector('input[name="name"]').focus(); }; @@ -347,13 +393,12 @@ return; } list.innerHTML = allOccupants.map(o => ` -
+
${o.count}x ${esc(o.activityLevel)} ${esc(o._roomName)}${o.vulnerable ? ' · ⚠ vulnerable' : ''}
- - + ${actionGroup(`editOccupantUI(${o.id})`, `deleteOccupantUI(${o.id})`)}
`).join(""); } @@ -367,6 +412,7 @@ form.querySelector('input[name="count"]').value = o.count || 1; form.querySelector('select[name="activityLevel"]').value = o.activityLevel || "sedentary"; form.querySelector('input[name="vulnerable"]').checked = !!o.vulnerable; + enterEditMode(form); form.querySelector('input[name="count"]').focus(); }; @@ -414,13 +460,12 @@ const roomIds = assignments.filter(a => a.acId === u.id).map(a => a.roomId); const roomNames = roomIds.map(id => roomMap[id] || `Room ${id}`).join(", "); return ` -
+
${esc(u.name)} ${u.capacityBtu} BTU · ${esc(u.acType)}${roomNames ? ' · ' + esc(roomNames) : ''}
- - + ${actionGroup(`editACUI(${u.id})`, `deleteACUI(${u.id})`)}
`; }).join(""); @@ -442,6 +487,7 @@ document.querySelectorAll('#ac-room-checkboxes input').forEach(cb => { cb.checked = assignedRoomIds.has(parseInt(cb.value)); }); + enterEditMode(form); form.querySelector('input[name="name"]').focus(); }; diff --git a/web/templates/layout.html b/web/templates/layout.html index 0293bee..c0b8d04 100644 --- a/web/templates/layout.html +++ b/web/templates/layout.html @@ -32,7 +32,7 @@
-
+
{{block "content" .}}{{end}}
diff --git a/web/templates/setup.html b/web/templates/setup.html index d9c96f9..80a4166 100644 --- a/web/templates/setup.html +++ b/web/templates/setup.html @@ -43,7 +43,8 @@ - + +
@@ -117,7 +118,8 @@
- + +
@@ -161,7 +163,10 @@
- +
+ + +
@@ -196,7 +201,10 @@
- +
+ + +
@@ -239,7 +247,10 @@
- +
+ + +