Files
audi-app/companion-app/src/screens/BelegPopup.tsx
T
tobias b1411625aa Teilen-Blatt, HIG-Blaetter, geteilte Ansichtseinstellungen (2026.8.31.1)
Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als
Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil
ios/ gitignored ist und npx cap add ios es sonst wieder frisst;
scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt
und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac
erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md
beschreibt dieselben Handgriffe fuer Xcode.

"In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das
download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt
@capacitor/filesystem plus @capacitor/share.

useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der
Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die
Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je
Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den
alten Stand als fehlschlagend nachgewiesen.

Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht
und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach
document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus
fast unsichtbar; Portal zielt jetzt auf .ads-root.

ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem
leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter.
Vier Regressionstests, ebenfalls gegen den alten Stand geprueft.

Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31
Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App
deshalb nicht.

uebersichtsbild: Panel speichert den Namen, die App verglich gegen den
Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name.

Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie
(die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon,
Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im
Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem
Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die
Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 14:24:52 +02:00

130 lines
4.1 KiB
TypeScript

/**
* „Beleg hochladen" — Vorlage: `vBelegPopup()` im Panel.
*
* Bis 2026-08-31 öffnete der Beleg-Knopf in der App direkt die Dateiauswahl.
* Auf dem iPhone landete man damit im Dateispeicher, und ein aus der Mail
* kopiertes PDF war überhaupt nicht einzufügen — im Panel gab es diesen Weg
* längst. Geteilt zwischen dem Anlegen (Tanken.tsx) und dem Nachtragen
* (TankDetail.tsx), damit beide nicht auseinanderlaufen.
*
* Ein Auswahlblatt, kein Popup: es ist eine kurze Liste von Wegen zum selben
* Ziel, und genau dafür sieht Apple das Aktionsblatt vor. „Abbrechen" steht
* abgesetzt in eigener Karte — das macht es zum Ausweg statt zur vierten
* Wahlmöglichkeit.
*
* Welche Wege angeboten werden, entscheidet das Gerät, nicht der Geschmack:
* am Telefon ist Einfügen der Hauptweg, am Rechner Ziehen bzw. Dateiauswahl.
* Begründung in daten/zwischenablage.ts.
*/
import { useRef, useState } from "react"
import { ActionSheet } from "@audi-dash/ui"
import {
ZwischenablageFehler,
pdfAusDateiliste,
pdfAusZwischenablage,
zwischenablageKnopfSinnvoll,
} from "../daten/zwischenablage"
export function BelegPopup({
offen,
beiAbbruch,
beiDatei,
}: {
offen: boolean
beiAbbruch: () => void
/** Bekommt das PDF. Das Blatt schließt sich vorher selbst. */
beiDatei: (datei: File) => void
}) {
const dateiwahl = useRef<HTMLInputElement | null>(null)
const [fehler, setzeFehler] = useState<string | null>(null)
const [darueber, setzeDarueber] = useState(false)
// Einmal beim Aufbau bestimmt: ein Gerätewechsel mitten im Dialog gibt es
// nicht, und ein bei jedem Rendern neu befragtes matchMedia wäre nur Aufwand.
const [telefon] = useState(zwischenablageKnopfSinnvoll)
const uebernehmen = (datei: File) => {
setzeFehler(null)
beiDatei(datei)
}
const einfuegen = async () => {
try {
uebernehmen(await pdfAusZwischenablage())
} catch (f) {
setzeFehler(
f instanceof ZwischenablageFehler
? f.message
: 'Die Zwischenablage ließ sich nicht lesen - bitte "Datei auswählen".',
)
}
}
const aktionen = [
...(telefon
? [{ label: "Aus Zwischenablage einfügen", onClick: () => void einfuegen() }]
: []),
{ label: "Datei auswählen", onClick: () => dateiwahl.current?.click() },
]
// Nur wenn es wirklich etwas zu zeigen gibt - sonst entstuende im Blatt
// eine leere Zeile samt Trennlinie.
const zusatz =
!telefon || fehler ? (
<>
{!telefon && (
<div
className={`dm-beleg-drop${darueber ? " dm-beleg-drop--darueber" : ""}`}
onDragOver={(e) => {
e.preventDefault()
setzeDarueber(true)
}}
onDragLeave={() => setzeDarueber(false)}
onDrop={(e) => {
e.preventDefault()
setzeDarueber(false)
const datei = pdfAusDateiliste(e.dataTransfer?.files)
if (datei) uebernehmen(datei)
else setzeFehler("Das war kein PDF - der Beleg muss als PDF vorliegen.")
}}
>
<strong>PDF hierher ziehen</strong>
<span>oder unten auswählen</span>
</div>
)}
{fehler && (
<p className="dm-fehler" role="alert" style={{ margin: 0 }}>
{fehler}
</p>
)}
</>
) : undefined
return (
<>
{/* Ausserhalb des Blattes: unsichtbar, muss aber im Baum stehen, damit
der Verweis darauf zeigt. */}
<input
ref={dateiwahl}
type="file"
accept="application/pdf,.pdf"
hidden
onChange={(e) => {
const datei = e.target.files?.[0]
e.target.value = ""
if (datei) uebernehmen(datei)
}}
/>
{/* Ohne Ueberschrift und ohne Erklaerzeile: wer den Knopf 'Beleg
hochladen' gedrueckt hat, weiss bereits, was hier passiert - der
Kopf haette es nur wiederholt (Nutzerhinweis 2026-08-31). Die
beiden Wege benennen sich selbst. */}
<ActionSheet open={offen} onClose={beiAbbruch} actions={aktionen}>
{zusatz}
</ActionSheet>
</>
)
}