feat(ui): SOS-Button prominent (rot gefüllt, großes weißes „SOS") + Bestätigung & Status
Der Notruf-Button war ein kleines 36px-Outline-Icon (🆘) — leicht zu übersehen.
Jetzt ein rot gefüllter Button (bg-red-600) mit großem, fettem weißem „SOS" in
der (immer sichtbaren) Kopfzeile — der Senior erkennt ihn sofort. Sprachwahl
und Menü bleiben unverändert an ihrem Platz.
- Bestätigung: statt native confirm() ein großer eigener Dialog (rote SOS-Kopf-
zeile, Warnzeile, große Knöpfe „JA, HILFE RUFEN" / „Abbrechen"). Ein Tipp auf
SOS, ein Tipp auf JA — schnell, aber gegen Fehlalarm geschützt.
- Feedback am Button selbst: setSosState idle|calling|done|error —
„Rufe …" (pulsierend) → „Gerufen ✓" (grün) → zurück zu „SOS". So sieht der
Senior unmittelbar am Knopf, dass der Alarm ausgelöst wurde.
Nur Frontend (app/web), live ohne Neustart (No-Cache-Static, Bump auf v=49).
Verifiziert via Headless-Chrome/CDP: Idle/Dialog/Calling/Done-Zustände.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b4111c887e
commit
0ef4fe9cb8
2 changed files with 92 additions and 26 deletions
|
|
@ -2245,29 +2245,74 @@ function deviceInfo() {
|
|||
return { mobile: /Mobi|Android|iPhone|iPad|iPod/i.test(ua), platform: ua.slice(0, 120) };
|
||||
}
|
||||
|
||||
if (emergencyBtn) {
|
||||
emergencyBtn.addEventListener("click", async () => {
|
||||
if (!confirm("Wirklich Hilfe rufen?\nIch alarmiere sofort Helfer!")) return;
|
||||
const lang = (langSel && langSel.value) || "de";
|
||||
// Sichtbarer Zustand am SOS-Button selbst: idle | calling | done | error.
|
||||
// So erkennt der Senior unmittelbar am Knopf, dass der Alarm ausgelöst wurde.
|
||||
function setSosState(state) {
|
||||
if (!emergencyBtn) return;
|
||||
emergencyBtn.classList.remove(
|
||||
"bg-red-600", "hover:bg-red-700", "bg-green-600", "animate-pulse"
|
||||
);
|
||||
if (state === "calling") {
|
||||
emergencyBtn.textContent = "Rufe …";
|
||||
emergencyBtn.disabled = true;
|
||||
try {
|
||||
const g = await captureGeo();
|
||||
const body = { language: lang, device: deviceInfo() };
|
||||
if (g.ok) body.geo = { lat: g.lat, lon: g.lon, accuracy: g.accuracy, ts: g.ts };
|
||||
else body.geo_error = g.error;
|
||||
const res = await fetch("/api/emergency", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const notice = (data && data.notice) ||
|
||||
"ACHTUNG: Es ist noch keine Benachrichtigung eingebaut.";
|
||||
addMessage("emergency", "🆘 " + notice);
|
||||
} catch (e) {
|
||||
addMessage("emergency", "🆘 Fehler beim Auslösen des Notrufs.");
|
||||
} finally {
|
||||
emergencyBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
emergencyBtn.classList.add("bg-red-600", "animate-pulse");
|
||||
} else if (state === "done") {
|
||||
emergencyBtn.textContent = "Gerufen ✓";
|
||||
emergencyBtn.disabled = true;
|
||||
emergencyBtn.classList.add("bg-green-600");
|
||||
} else if (state === "error") {
|
||||
emergencyBtn.textContent = "Fehler";
|
||||
emergencyBtn.disabled = false;
|
||||
emergencyBtn.classList.add("bg-red-600", "hover:bg-red-700");
|
||||
} else { // idle
|
||||
emergencyBtn.textContent = "SOS";
|
||||
emergencyBtn.disabled = false;
|
||||
emergencyBtn.classList.add("bg-red-600", "hover:bg-red-700");
|
||||
}
|
||||
}
|
||||
|
||||
async function runEmergency() {
|
||||
const lang = (langSel && langSel.value) || "de";
|
||||
setSosState("calling");
|
||||
try {
|
||||
const g = await captureGeo();
|
||||
const body = { language: lang, device: deviceInfo() };
|
||||
if (g.ok) body.geo = { lat: g.lat, lon: g.lon, accuracy: g.accuracy, ts: g.ts };
|
||||
else body.geo_error = g.error;
|
||||
const res = await fetch("/api/emergency", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const notice = (data && data.notice) ||
|
||||
"ACHTUNG: Es ist noch keine Benachrichtigung eingebaut.";
|
||||
addMessage("emergency", "🆘 " + notice);
|
||||
setSosState("done");
|
||||
setTimeout(() => setSosState("idle"), 6000); // danach zurück zu „SOS"
|
||||
} catch (e) {
|
||||
addMessage("emergency", "🆘 Fehler beim Auslösen des Notrufs.");
|
||||
setSosState("error");
|
||||
setTimeout(() => setSosState("idle"), 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// Großer eigener Bestätigungsdialog statt native confirm() — seniorentauglich.
|
||||
const sosConfirm = $("#sos-confirm");
|
||||
function openSosConfirm() { if (sosConfirm) sosConfirm.classList.remove("hidden"); }
|
||||
function closeSosConfirm() { if (sosConfirm) sosConfirm.classList.add("hidden"); }
|
||||
|
||||
if (emergencyBtn) {
|
||||
emergencyBtn.addEventListener("click", openSosConfirm);
|
||||
}
|
||||
const sosYesBtn = $("#sos-confirm-yes");
|
||||
const sosNoBtn = $("#sos-confirm-no");
|
||||
if (sosYesBtn) sosYesBtn.addEventListener("click", () => { closeSosConfirm(); runEmergency(); });
|
||||
if (sosNoBtn) sosNoBtn.addEventListener("click", closeSosConfirm);
|
||||
// Klick auf den Hintergrund oder Escape bricht ab (schließt nur den Dialog).
|
||||
if (sosConfirm) {
|
||||
sosConfirm.addEventListener("click", (e) => { if (e.target === sosConfirm) closeSosConfirm(); });
|
||||
}
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && sosConfirm && !sosConfirm.classList.contains("hidden")) closeSosConfirm();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue