Untere Leiste fest am unteren Rand [deploy]
All checks were successful
Deploy baustelle-pwa / deploy (push) Successful in 13s

Eddy: „ja mach die Leiste unten mal fest". Sie stand seit v1.0.0 am
Seitenende — in einem Auftrag unter allen Fotos und Dokumenten, also
praktisch nie im Bild.

- #bottom-nav: position sticky, bottom 0, feste Höhe --nav-h (56 px).
- „Geteiltes ablegen" klebt genau darüber, der FAB sitzt darüber.
- Banner mit Teilen-Symbol statt Emoji und „2 Fotos und 1 Video"
  statt „3 Bilder".

Im Android-Emulator gemessen: Leiste oben wie gescrollt am Rand,
Banner und FAB ohne Überlappung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Eddy 2026-10-20 19:32:00 +02:00
parent afc6ca7046
commit 47cd91193e
4 changed files with 29 additions and 9 deletions

View file

@ -20,7 +20,7 @@ Mobile Progressive Web App für die Baustellen-Doku — Foto-Upload, Sprach- und
- ✅ Auftragsdetail mit Kunde, Adresse, Telefon (Click-to-Call) - ✅ Auftragsdetail mit Kunde, Adresse, Telefon (Click-to-Call)
- ✅ **Neuer Auftrag** (FAB): Kunde per Suche/Zuletzt-verwendet wählen, dann **Ihr Zeichen** (Pflicht), **Auftragsbeschreibung** (optional), **Geplanter Liefertermin** (Datum + Uhrzeit) und Checkbox **Auftrag direkt freigeben**. Kunden-Defaults (Zahlungsbedingung/Zahlart, Ansprechpartner) werden übernommen. Bei „direkt freigeben" wird der Auftrag **ohne Position** validiert — die Leistungen kommen aus dem Stundenzettel. (Bis Bericht 1.3.1 hängte das Backend hier eine Std-Lohn-Zeile mit Menge 1 an; die verfälschte den Auftragswert, stand als offene Restmenge in der Lieferauflistung und im Kunden-PDF.) - ✅ **Neuer Auftrag** (FAB): Kunde per Suche/Zuletzt-verwendet wählen, dann **Ihr Zeichen** (Pflicht), **Auftragsbeschreibung** (optional), **Geplanter Liefertermin** (Datum + Uhrzeit) und Checkbox **Auftrag direkt freigeben**. Kunden-Defaults (Zahlungsbedingung/Zahlart, Ansprechpartner) werden übernommen. Bei „direkt freigeben" wird der Auftrag **ohne Position** validiert — die Leistungen kommen aus dem Stundenzettel. (Bis Bericht 1.3.1 hängte das Backend hier eine Std-Lohn-Zeile mit Menge 1 an; die verfälschte den Auftragswert, stand als offene Restmenge in der Lieferauflistung und im Kunden-PDF.)
**Aussehen:** Knöpfe tragen farbige Symbole statt Emojis (`icon()` in app.js, Material Icons; Farben je Symbol in app.css `.ikon-<name>`) — Emojis erscheinen je nach Handy als Kästchen. Große Knöpfe: weißes Symbol im farbigen runden Feld, linksbündig (Symbole in einer Spalte, Text darunter bündig); kleine Knöpfe: farbiges Symbol. Untere Leiste: farbiges Symbol über dem Wort. Überschriften fett in normaler Schreibweise, ohne Versalien und ohne Emoji (ROADMAP Punkt 12). **Aussehen:** Knöpfe tragen farbige Symbole statt Emojis (`icon()` in app.js, Material Icons; Farben je Symbol in app.css `.ikon-<name>`) — Emojis erscheinen je nach Handy als Kästchen. Große Knöpfe: weißes Symbol im farbigen runden Feld, linksbündig (Symbole in einer Spalte, Text darunter bündig); kleine Knöpfe: farbiges Symbol. Untere Leiste: farbiges Symbol über dem Wort, klebt fest am unteren Rand (wie die Kopfzeile oben). Überschriften fett in normaler Schreibweise, ohne Versalien und ohne Emoji (ROADMAP Punkt 12).
**Foto- und Video-Upload** **Foto- und Video-Upload**
- ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung (nur bei zwei Kameras), Fehlschuss löschbar - ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung (nur bei zwei Kameras), Fehlschuss löschbar

View file

@ -80,9 +80,12 @@ Anlass: der 🔄-Wechselknopf erschien als blaues Kästchen („den hässlichen
- [x] ✅ Titel der Vollbild-Ansichten und Abschnitts-Überschriften ohne Emoji (reiner Text). - [x] ✅ Titel der Vollbild-Ansichten und Abschnitts-Überschriften ohne Emoji (reiner Text).
- Bewusst gelassen: Zustandsanzeige 🟢/🟡/🔴 (Farbe ist dort die Aussage), Stempel im Skizzen-Editor (werden so ins Bild - Bewusst gelassen: Zustandsanzeige 🟢/🟡/🔴 (Farbe ist dort die Aussage), Stempel im Skizzen-Editor (werden so ins Bild
gezeichnet), Bilder in leeren Listen, Emojis in Meldungen. gezeichnet), Bilder in leeren Listen, Emojis in Meldungen.
- [ ] Offen, nicht angefasst: Die untere Leiste steht seit v1.0.0 am **Seitenende** statt fest unten (`#app` nur - [x] ✅ 20.10.2026 (Eddy: „ja mach die Leiste unten mal fest"): Die untere Leiste stand seit v1.0.0 am **Seitenende**
`min-height: 100dvh`, `#main` wächst mit) — bei langen Listen erst nach Scrollen erreichbar. Wenn gewünscht: (`#app` nur `min-height`, die Seite wächst mit) — in einem Auftrag unter allen Fotos und Dokumenten, Eddy kannte sie
Leiste fest unten, dabei FAB und „Geteiltes ablegen"-Leiste (Punkt 8) mit hochrücken. gar nicht. Jetzt `position: sticky; bottom: 0` wie die Kopfzeile oben, feste Höhe `--nav-h: 56px`; „Geteiltes
ablegen" klebt genau darüber (`bottom: var(--nav-h)`), der FAB (fixed 68 px) darüber. Im Emulator gemessen: Leiste
729–786 bei 785 px Fensterhöhe, oben wie gescrollt; Banner 688–730, FAB 618–674 — nichts überlappt. Banner mit
Teilen-Symbol statt 📤 und „2 Fotos und 1 Video" statt „3 Bilder".
## 11. Videos aufnehmen (gewünscht 2026-10-19) ## 11. Videos aufnehmen (gewünscht 2026-10-19)

18
app.css
View file

@ -63,20 +63,31 @@ body {
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
} }
/* Untere Leiste klebt am unteren Rand wie die Kopfzeile oben (Eddy 20.10.2026: „mach die Leiste
unten mal fest"). Bis dahin stand sie seit v1.0.0 am Seitenende — in einem Auftrag unter allen
Fotos und Dokumenten, praktisch nie im Bild. Feste Hoehe (--nav-h), damit die Leiste
„Geteiltes ablegen" genau darueber kleben kann; der FAB (fixed, 68px) liegt schon darueber. */
:root { --nav-h: 56px; }
#bottom-nav { #bottom-nav {
display: flex; display: flex;
background: #25252b; background: #25252b;
border-top: 1px solid #333; border-top: 1px solid #333;
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
position: sticky;
bottom: 0;
z-index: 30;
} }
#bottom-nav button { #bottom-nav button {
flex: 1; flex: 1;
background: transparent; background: transparent;
border: none; border: none;
color: #888; color: #888;
padding: 8px 2px; height: var(--nav-h);
box-sizing: border-box;
padding: 6px 2px;
font-size: 11px; font-size: 11px;
cursor: pointer; cursor: pointer;
justify-content: center;
/* Symbol ueber dem Wort, wie in Handy-Apps ueblich (vorher bunte Emojis) */ /* Symbol ueber dem Wort, wie in Handy-Apps ueblich (vorher bunte Emojis) */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1698,7 +1709,7 @@ body {
* (fixed, bottom 68px) liegt darüber und wird nicht mehr verdeckt. */ * (fixed, bottom 68px) liegt darüber und wird nicht mehr verdeckt. */
.shared-banner { .shared-banner {
position: sticky; position: sticky;
bottom: 0; bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px)); /* direkt ueber der festen Leiste */
z-index: 20; z-index: 20;
width: 100%; width: 100%;
margin: 0; margin: 0;
@ -1713,6 +1724,9 @@ body {
cursor: pointer; cursor: pointer;
} }
.shared-banner:active { filter: brightness(0.95); } .shared-banner:active { filter: brightness(0.95); }
/* Symbol in Schriftfarbe (dunkel auf hellblau) — die Teilen-Farbe waere hier unsichtbar */
.shared-banner { display: flex; align-items: center; justify-content: center; gap: 8px; }
.shared-banner .ikon { color: inherit; }
/* Solange die Leiste da ist, sitzt der FAB eine Leistenhöhe höher — sonst ragt er hinein */ /* Solange die Leiste da ist, sitzt der FAB eine Leistenhöhe höher — sonst ragt er hinein */
body.hat-geteiltes .fab { bottom: calc(112px + env(safe-area-inset-bottom, 0px)); } body.hat-geteiltes .fab { bottom: calc(112px + env(safe-area-inset-bottom, 0px)); }

9
app.js
View file

@ -1982,11 +1982,12 @@ router.on('/settings', async () => {
* bleiben in IndexedDB liegen; ohne diesen Hinweis wuerde sie niemand je wiederfinden. */ * bleiben in IndexedDB liegen; ohne diesen Hinweis wuerde sie niemand je wiederfinden. */
async function checkPendingShare() { async function checkPendingShare() {
if (document.getElementById('shared-banner')) return; if (document.getElementById('shared-banner')) return;
let n = 0, notiz = false; let n = 0, notiz = false, dateien = [];
try { try {
const p = await idb.get('shared_payload'); const p = await idb.get('shared_payload');
if (p) { if (p) {
n = (p.files || []).length; dateien = p.files || [];
n = dateien.length;
notiz = !!((p.title || '').trim() || (p.text || '').trim() || (p.url || '').trim()); notiz = !!((p.title || '').trim() || (p.text || '').trim() || (p.url || '').trim());
} else { } else {
const alt = await idb.get('shared_files'); // altes Format const alt = await idb.get('shared_files'); // altes Format
@ -2004,7 +2005,9 @@ async function checkPendingShare() {
const b = document.createElement('button'); const b = document.createElement('button');
b.id = 'shared-banner'; b.id = 'shared-banner';
b.className = 'shared-banner'; b.className = 'shared-banner';
b.textContent = '📤 Geteiltes ablegen' + (n ? ' (' + n + ' Bild' + (n > 1 ? 'er' : '') + ')' : ' (Notiz)'); // Videos koennen seit 19.10.2026 mitkommen — daher „2 Fotos und 1 Video" statt „3 Bilder"
const was = n ? medienText(dateien.length ? dateien.map(f => ({ mime: f.type || '' })) : new Array(n).fill({})) : 'Notiz';
b.innerHTML = icon('teilen', 20) + '<span>Geteiltes ablegen (' + escapeHtml(was) + ')</span>';
b.addEventListener('click', () => { location.href = 'share.html'; }); b.addEventListener('click', () => { location.href = 'share.html'; });
// Als Leiste in die App-Spalte direkt über die Navigation (Eddy: „unten am Rand, nicht auf den // Als Leiste in die App-Spalte direkt über die Navigation (Eddy: „unten am Rand, nicht auf den
// Aufträgen") — vorher schwebte der Knopf fest über der Liste und verdeckte Einträge und den FAB // Aufträgen") — vorher schwebte der Knopf fest über der Liste und verdeckte Einträge und den FAB