Commit Graph

79 Commits

Author SHA1 Message Date
Scarriffle
f88a459e12 Web: Etiketten-CSV gegen Formel-Injektion härten
Zellen, die mit = + - @ (oder Tab/CR) beginnen, werden mit einem vorangestellten
Apostroph zu Text entschaerft. So fuehrt ein Produktname wie "=HYPERLINK(...)"
beim Oeffnen der Etiketten-CSV in Excel/P-touch nicht mehr als Formel aus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 15:58:54 +02:00
Scarriffle
b49546b297 Lagerorte umhängen (Backend + Web)
Bestehende Lagerorte lassen sich jetzt nachträglich einem anderen Elternort
zuordnen oder auf die oberste Ebene holen.
- Backend: LocationUpdate um parent_id (Name jetzt optional); update_location
  haengt um, mit Schutz gegen Ringe (weder auf sich selbst noch auf einen
  eigenen Unterort) und Existenzpruefung des Ziels. 6 Tests.
- Web: Lagerorte-Seite bekommt je Ort Bearbeiten (Name + Elternort ueber den
  CategorySelect-Baum, eigene Unterorte ausgeschlossen); api.updateLocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 15:58:54 +02:00
Scarriffle
272609e70a Web: eigene Felder nachträglich bearbeiten
Backend und API-Client konnten Felddefinitionen laengst aendern (PATCH), nur die
Oberflaeche bot bisher nur Anlegen und Loeschen. In der Felder-Verwaltung einer
Kategorie gibt es jetzt je Feld einen Bearbeiten-Knopf: er laedt Name, Typ,
Einheit, Auswahlmoeglichkeiten und Pflicht ins Formular; Speichern aktualisiert
das Feld (oder Abbrechen). Geerbte und eingebaute Felder bleiben unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 13:11:29 +02:00
Scarriffle
dc66a6d7ff Web: alle Kategorie-Auswahlen als aufklappbarer Baum
Statt der nativen "— —"-Dropdowns nutzen jetzt auch Produktformular
(Kategorie + Inline-"Neue Kategorie"-Elternwahl), Einlagern-Maske und
Produkte-Filter den CategorySelect-Baum. CategorySelect um optionale
Zusatz-Eintraege (extraOptions) erweitert, damit der Produkte-Filter
"Alle Kategorien"/"Ohne Kategorie" weiter anbietet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 13:07:36 +02:00
Scarriffle
2a507851d9 Web: "Entfernen" nur zeigen, wenn ein Artikelbild vorliegt
Der Entfernen-Knopf stand auch ohne Bild da. ProduktBild meldet jetzt via
onLoaded, ob ein Bild geladen wurde; das Produktformular blendet "Entfernen"
entsprechend ein oder aus. "Foto machen" und "Galerie" bleiben immer sichtbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 12:28:02 +02:00
Scarriffle
dbdd977bad Web: Stammdaten geschlossen unter Verwaltung (wie iOS)
Kategorien und Gruppen standen in der Alltags-Navigation, obwohl sie – wie
Lagerorte, Shops, Einheiten und Gebinde – nur von Admins gepflegte Katalog-
Stammdaten sind. Jetzt sind alle Stammdaten zusammen im Admin-Bereich, der in
zwei Abschnitte gegliedert ist:
- Stammdaten: Kategorien, Gruppen, Lagerorte, Shops, Einheiten, Gebinde
- System: Benutzer, Import/Export, Einstellungen
Normale Nutzer sehen nur noch den Alltag (Uebersicht, Ein-/Auslagern, Produkte,
Einkaufsliste, Verlauf). /categories und /groups sind jetzt Admin-Routen. Das
entspricht der Gliederung der iOS-App.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 12:25:25 +02:00
Scarriffle
a70a99b387 Dashboard zaehlt Einzelstuecke mit; Baum-Dropdown scrollbar
Auswertungen (by_category, expiry_split, stats) iterierten nur Lots. Gegenstaende
mit Menge-je-Lagerort liegen als Lots und tauchten auf – Einzelstuecke liegen
aber als Items und fehlten dadurch komplett (Dashboard zeigte z.B. nur die
Powerbank, nicht Kamera/Kabel). Neuer Helfer _bestand_beitraege deckt beide
Speicherformen ab: jedes Item zaehlt als ein Stueck ohne MHD. Regressionstest
ergaenzt; volle Suite gruen (120).

CategorySelect: Scrollen IM aufklappbaren Menue schloss es, weil jeder Scroll als
Seiten-Scroll gewertet wurde. Jetzt schliesst nur ein Scroll ausserhalb des
Menues; die lange Liste laesst sich scrollen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 12:09:25 +02:00
Scarriffle
f35126cb7b Web: Kategorie-Elternwahl als aufklappbarer Baum statt "— —"-Select
Die uebergeordnete Kategorie wird nicht mehr ueber ein <select> mit
"— —"-Einrueckung gewaehlt, sondern ueber einen aufklappbaren Baum-Picker
(CategorySelect): echte Einrueckung, Aeste per Pfeil auf-/zuklappbar wie in der
Liste. Das Menue haengt per Portal am <body>, damit der horizontal scrollende
Tabellenrahmen es nicht abschneidet. Ersetzt beide Stellen (Zeilen-Elternwahl
und Anlege-Formular); die kompakten Knoepfe machen die Zeile schmaler, sodass
die Tabelle nicht mehr seitwaerts ueber den Block hinauslaeuft.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 11:44:03 +02:00
Scarriffle
b4b203a117 Etiketten-Export: Trennzeichen waehlbar (Komma/Semikolon/Tab), gemerkt
P-touch will Komma, wir hatten fest Semikolon - jetzt einstellbar und in
localStorage gemerkt, sodass man es nur einmal setzt. Standard ist Komma.
Anfuehrungszeichen werden passend zum gewaehlten Trennzeichen gesetzt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 09:54:35 +02:00
Scarriffle
2ebdf94b16 Web: QR-Etiketten-Export (CSV für P-touch & Co.)
Auf "Import / Export" neue Karte: Kategorien auswaehlen (inkl. Unterkategorien)
und je Einzelstueck eine CSV-Zeile herunterladen - Spalten QR-Inhalt (…/i/<UID>),
UID, Produkt, Marke, Kategorie, Lagerort. In der Etiketten-Software als Datenbank
verknuepfen, dann entsteht QR + Text automatisch - kein einzelnes Bild-Download
und Zuordnen mehr.

Backend: GET /export/labels?category_ids=... liefert die Zeilen (JSON); den
QR-Inhalt setzt das Web mit window.location.origin dazu.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 09:22:03 +02:00
Scarriffle
e8471bd951 Universal Links: gescannter Einzelstück-Link öffnet die App
Server liefert /.well-known/apple-app-site-association (nur /i/* beansprucht;
/l/* bleibt im Browser). iOS: Associated-Domains-Entitlement, Universal Link
(https://…/i/<UID>) wird abgefangen (onContinueUserActivity) und öffnet das
Einzelstück direkt.

Hinweis: Damit das Signieren durchläuft, muss die Capability "Associated
Domains" einmalig im Apple-Developer-Portal für die App-ID aktiv sein.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 08:12:45 +02:00
Scarriffle
7f9895245c Lagerort-QR + Zuordnen per Scan
Web: druckbare QR-Etiketten fuer Lagerorte (Locations-Seite) und Route /l/<id>
(zeigt den Ort im Browser). Geteilter QR-Helfer (web/src/qr.jsx).

iOS: neuer Ablauf "Ort zuordnen" - erst Einzelstueck-QR (…/i/<UID>) scannen, dann
Lagerort-QR (…/l/<ID>); das Stueck wird dem Ort zugewiesen, danach gleich das
naechste. Plus die zuvor gebaute "Nachschlagen"-Kachel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 08:09:00 +02:00
Scarriffle
6fb5c47de6 Web: Einzelstücke mit QR – anlegen, je-Stück-Felder, Etiketten, /i/<uid>
Gegenstands-Produkte haben jetzt den Umschalter "Menge je Lagerort / Einzelstücke".
Bei Einzelstücken erscheint (volle Breite) eine Liste je Stueck mit eigener UID,
QR-Code, Lagerort, Kaufdatum, Garantie, Bezugsquelle und Notiz - direkt inline
aenderbar, mit Entfernen (Grund) und druckbaren QR-Etiketten. Ein Scan oeffnet
ueber die Route /i/<uid> das passende Stueck. QR via qrcode (gebundelt, offline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 00:36:26 +02:00
Scarriffle
68e27c1868 Web: Kategorie-Tabelle bekommt mehr Breite (kein Seitwaerts-Scrollen)
Durch die neue Spalte "Uebergeordnet" wurde die Kategorie-Tabelle im halben Grid
zu breit und scrollte seitwaerts. Die Kategorien-Seite nutzt jetzt das
wide-aside-Layout; dessen Block daneben ist zudem schmal gedeckelt (clamp), damit
die Tabelle den restlichen Platz bekommt. Gilt auch fuer die Gruppen-Seite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 00:07:23 +02:00
Scarriffle
928342d352 Web: Unterkategorien umsortieren (uebergeordnete Kategorie aenderbar)
In der Kategorien-Verwaltung gibt es je Zeile eine Spalte "Uebergeordnet" mit
Auswahl - so laesst sich eine (Unter-)Kategorie unter eine andere haengen oder
auf die oberste Ebene holen. Zielauswahl auf denselben Typ beschraenkt und um die
eigenen Unterkategorien bereinigt; das Backend verhindert Ringe zusaetzlich.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 00:01:32 +02:00
Scarriffle
5edb830afd Web: Produkte und Kategorien nach Typ filtern (Lebensmittel/Gegenstand)
Zum leichteren Unterscheiden gibt es in der Produkte-Liste und in der
Kategorien-Verwaltung einen Umschalter Alle/Lebensmittel/Gegenstaende. Er filtert
die jeweilige Liste und in der Produktsuche auch die Kategorieauswahl auf den Typ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:55:27 +02:00
Scarriffle
8d75ad63ce Web-Artikelformular: Umschalter Lebensmittel/Gegenstand
Statt die Verwaltungsart nur implizit ueber die Kategorie zu waehlen (und dabei
Werkzeug & Co. zwischen 50 Lebensmittel-Kategorien zu suchen), gibt es jetzt oben
einen klaren Umschalter. Die Kategorieauswahl zeigt nur Kategorien des gewaehlten
Typs; neu angelegte Kategorien erben den Typ automatisch. Neuanlage startet als
Gegenstand, beim Bearbeiten kommt der Modus aus dem Artikel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:37:16 +02:00
Scarriffle
5c8b46a0d5 Foto-Upload: 413 beheben und Kamera stabilisieren
- nginx client_max_body_size auf 12m (Standard 1 MB loeste beim Bild-Upload
  einen 413 aus, bevor das Backend den Upload sah).
- Backend-Bildlimit auf 8 MB angehoben, bleibt unter dem nginx-Limit, damit
  Uebergroesse eine klare 400-Meldung statt eines nackten 413 ergibt.
- Web: Foto vor dem Upload clientseitig verkleinern (max 1600px, JPEG) -
  schneller, kleiner in der DB, zuverlaessig unter dem Limit.
- iOS: Kamera ueber fullScreenCover statt sheet und Schliessen deterministisch
  ueber ein Binding - behebt das "Kamera geht direkt wieder zu"-Flackern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 23:11:59 +02:00
Scarriffle
a664410ac0 Vergleichs-Beschriftung folgt dem Modus (OFF vs. Open Products Facts)
Bei Gegenstaenden fragt das Backend Open Products Facts ab, bei Lebensmitteln
Open Food Facts. Die sichtbaren Texte (Vergleichs-Button, Toasts, das
Gegenueberstellungs-Panel) nannten aber immer "Open Food Facts". Jetzt richtet
sich die Beschriftung nach der Verwaltungsart der Kategorie.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 21:50:59 +02:00
Scarriffle
044f63446f Inline-Anlage (Kategorien/Felder), Artikelfoto und CSV-Spalte "art"
- Web: Kategorien/Unterkategorien und eigene Felder direkt im Artikelformular
  anlegen (ohne Umweg über die Kategorien-Seite).
- Artikelfoto per Kamera oder Galerie hochladen – Backend-Endpunkt
  (PUT/DELETE /products/{id}/image), Web (Foto machen / Galerie) und iOS
  (Kamera + PhotosPicker, inline in der Produktansicht angezeigt).
- CSV-Import: neue Spalte "art" (Gegenstand/Lebensmittel) steuert den Modus
  automatisch angelegter Kategorien; Export schreibt sie mit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 16:18:36 +02:00
Scarriffle
5b952524d7 Gegenstands-Verwaltung (Non-Food) neben Lebensmitteln
Die Kategorie bestimmt die Verwaltungsart (food/object). Gegenstaende:
Menge je Lagerort statt Chargen/MHD, Umlagern (ohne Grund) und Entfernen
mit Pflicht-Grund samt Entnahme-Statistik. Beliebig viele eigene Felder
je Kategorie (vererbt an Unterkategorien), "gekauft bei" ueber eine
verwaltbare Shop-Liste und ein Produktlink. Barcode-Lookup zusaetzlich
ueber Open Products Facts.

Der Lebensmittel-Teil (Chargen/MHD/FEFO) bleibt unveraendert. Umgesetzt in
Backend (FastAPI, +Tests gruen), Web (React) und iOS (SwiftUI).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 15:33:56 +02:00
Scarriffle
85ac3dfa50 Schnellbuchungs-Karten: gueltige Einheit statt "article"
Die Ein-/Auslagern-Karten schickten unit: "article" - eine Einheit, die der
Server nicht kennt (services/units.py akzeptiert nur package/packung bzw. die
Basiseinheiten g/ml/stück). Jede Buchung ueber die Karte scheiterte damit an
"Unbekannte Einheit: article", in der Web-Oberflaeche wie in der App.

Jetzt wird die Menge in Artikeleinheiten gebucht: gibt es ein Gebinde, ist das
eine Packung, sonst die Basiseinheit des Produkts. Gegen ein laufendes Backend
geprueft (package und Basiseinheit je 200).

Aufgefallen beim Testen der Ablauf-Benachrichtigungen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 21:14:50 +02:00
Scarriffle
89598147ed Web: Dashboards in der Seitenleiste, Karten mehrfach, Buchen ohne Seitenwechsel
Drei Dinge in einem, weil sie dieselbe Umstellung brauchen.

Karten sind jetzt Instanzen: Die Anordnung merkt sich neben der Kennung auch
die Art und ein "props" je Karte. Dadurch laesst sich dieselbe Art mehrfach
auflegen, und "Verlauf eines Artikels" merkt sich endlich, welcher Artikel
gemeint ist - vorher lag diese Wahl nur in useState und war nach jedem
Neuladen wieder weg, was die Karte zum Beobachten unbrauchbar machte. Wer
mehrere Artikel im Blick haben will, legt jetzt einfach mehrere Karten an.

Mehrere Dashboards stehen als eingerueckte Eintraege unter "Übersicht" in der
Seitenleiste, anlegen und umbenennen laeuft ueber die Kopfzeile. Ab dem
zweiten werden sie aufgezaehlt; bei einem einzigen waere die Liste nur eine
Verdopplung.

Neu sind die Karten "Einlagern" und "Auslagern": Artikel suchen, Menge
eintragen, gebucht. MHD und Lagerort bleiben bewusst weg - wer die braucht,
ist auf der vollen Seite besser aufgehoben. Die alte Karte "Schnellzugriff"
bleibt als reine Verlinkung bestehen.

Der Bestaetigungsdialog kann jetzt auch nach Text fragen (prompt: true). Das
war noetig, weil Dashboards benannt werden - und window.prompt sieht aus wie
der Browser, nicht wie diese Anwendung; derselbe Grund, aus dem es hier schon
kein window.confirm gibt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 19:18:51 +02:00
Scarriffle
fd9045226b Gebinde verwalten - mit Einzahl und Mehrzahl
Bisher war die Auswahl eine fest im Frontend verdrahtete Liste und es gab nur
eine Form: ueberall stand "3 Glas".

Neu unter Verwaltung > Gebinde: anlegen, umbenennen, loeschen, jeweils mit
Einzahl und Mehrzahl. Die eingebauten Gebinde lassen sich in der Schreibweise
aendern, aber nicht loeschen; ein Gebinde, das ein Artikel verwendet, ebenfalls
nicht.

Der Artikel speichert weiterhin nur die Einzahl als Text - so bleiben
vorhandene Artikel, Sicherungen und CSV-Dateien gueltig, und eine unbekannte
Bezeichnung faellt schlicht auf die Einzahl zurueck. Deshalb zieht ein
Umbenennen die Artikel mit; sonst zeigten sie auf eine Bezeichnung, die es
nicht mehr gibt.

Die Mehrzahl greift jetzt in Artikelliste, Artikelseite (Chargen und Gebinde-
Auswahl), Auslagern und in allen Ablauf- und Einkaufslisten samt Startseite.
Einheiten wie Gramm oder Liter bleiben unveraendert - die haben im Deutschen
keine Mehrzahl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:24:42 +02:00
Scarriffle
0ba968985f Bild uebernehmen: sichtbare Rueckmeldung und Nachholen bei fehlender Kopie
Zwei Ursachen dafuer, dass der Knopf folgenlos wirkte.

Sichtbar: Uebernehmen schreibt nur ins Formular, das Artikelbild wechselt erst
beim Speichern - es gab aber nichts, woran man das erkannt haette. Die Spalte
"Bisher" zeigt jetzt sofort das uebernommene Bild mit Kennzeichnung, der Knopf
heisst danach "Uebernommen", und eine Meldung erklaert den naechsten Schritt.

Tatsaechlich folgenlos war der haeufigste Fall: Stimmte die Bildadresse
laengst und nur der Abruf war damals fehlgeschlagen, aenderte sich beim
Speichern nichts - und die Bedingung fragte genau nach einer Aenderung. Sie
richtet sich jetzt danach, woher die vorhandene Kopie stammt. Fehlt sie, wird
geholt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:03:28 +02:00
Scarriffle
8766e35e91 Artikeldaten von Open Food Facts vergleichen und einzeln uebernehmen
Neuer Knopf unter "Nachschlagen" auf der Artikelseite. Er fragt OFF erneut und
stellt die Antwort den eigenen Daten gegenueber: Name, Marke, Packungsgroesse,
Kategorie und Bild, jeweils mit eigenem Uebernehmen-Knopf und einem
"Alle uebernehmen". Uebernommen wird nur ins Formular - gespeichert erst mit
"Speichern", damit man bis zuletzt bei den eigenen Daten bleiben kann.

Dafuer noetig: GET /products/{id}/off. /lookup taugt hier nicht, weil es bei
einem bekannten Barcode den eigenen Artikel meldet und OFF gar nicht erst
fragt. Der neue Endpunkt probiert auch die zusaetzlichen EAN-Codes durch - oft
ist nur einer davon bei OFF hinterlegt.

Damit ist das Bild unabhaengig davon nachholbar, ob ein Backup eine
Bildadresse enthielt.

Nebenbei: Der Nachschlagen-Knopf uebergab die Gruppenliste, wo die
Einheitenliste erwartet wird - die Einheit eines Vorschlags wurde deshalb nie
richtig zugeordnet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 14:00:30 +02:00
Scarriffle
3763f2f114 Artikelbild: nur noch an einer Stelle, hochkant, und beim Import beruecksichtigt
Das alte Bild in der Chargen-Karte entfaellt - seit dem neuen Bild neben den
Kennfeldern stand es doppelt auf der Seite.

Der Rahmen ist jetzt hochkant (3:4) statt quadratisch. Lebensmittelverpackungen
sind fast immer hoeher als breit; im Quadrat blieben links und rechts leere
Streifen.

Beim Import wurde image_url komplett verworfen, obwohl das JSON-Backup sie
enthaelt - eingelesene Artikel blieben deshalb dauerhaft ohne Bild. Die Adresse
wird jetzt uebernommen (bei bestehenden Artikeln nur ergaenzend, nie
ueberschreibend); die lokale Kopie holt der Bildabruf beim ersten Ansehen.
Bewusst nicht waehrend des Imports: Ein Backup mit 200 Artikeln wuerde sonst
200 Downloads in einer Anfrage abarbeiten.

Ausserdem holen jetzt auch Artikelliste, Ein- und Auslagern ihr Vorschaubild
aus der eigenen Datenbank statt direkt von Open Food Facts. Einzige Ausnahme
bleibt der OFF-Vorschlag beim Scannen: Dort existiert der Artikel noch nicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:48:20 +02:00
Scarriffle
c155116b4e Artikelbilder lokal speichern und auf der Artikelseite anzeigen
Open Food Facts liefert nur eine Bildadresse. Direkt dorthin zu verlinken hiesse:
Das Bild verschwindet, wenn OFF es austauscht, die Installation braucht Internet,
und jeder Seitenaufruf verraet OFF, welche Artikel jemand ansieht.

Das Bild wird daher einmal geholt und in einer eigenen Tabelle product_images
abgelegt - eigene Tabelle, damit Artikellisten die Blobs nicht mitziehen.
Geholt wird beim Anlegen, bei geaenderter Bildadresse und beim ersten Abruf
(so bekommen auch bestehende Artikel ihre Kopie, ohne Wanderung ueber alle
Datensaetze).

GET /products/{id}/image verlangt eine Anmeldung - aus den Bildern liesse sich
sonst ohne Konto ablesen, was im Vorrat liegt. Da ein <img src> keinen
Authorization-Header schickt, laedt die Oberflaeche das Bild ueber fetch und
zeigt es als Objekt-URL.

Angezeigt wird es rechts neben Barcode, Name und Marke. Fehlt ein Bild, rendert
die Komponente nichts und die Felder nehmen die volle Breite ein.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:35:10 +02:00
Scarriffle
dc1750d23a Umbenennung in Vorrania vollstaendig durchziehen
Auf ausdruecklichen Wunsch jetzt auch die internen Bezeichner, weil die
Installation noch keine echten Daten enthaelt: POSTGRES_USER/PASSWORD/DB, der
Volume-Name, die Standard-DATABASE_URL, der localStorage-Schluessel und die
iOS-Zeichenketten inklusive Keychain-Konto.

Das setzt eine leere Datenbank voraus: Der neue Volume-Name legt eine neue,
leere Datenbank an. Die alte bleibt als verwaistes Volume liegen und muss von
Hand entfernt werden.

Die Warnung in docker-compose.yml bleibt stehen, damit eine spaetere
Umbenennung nicht versehentlich mit Daten im Volume passiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:13:11 +02:00
Scarriffle
2423e74cae Umbenennung in Vorrania
Sichtbarer Name ueberall: Kopfzeichen der Oberflaeche, Seitentitel, Favicon,
API-Titel, User-Agent zu Open Food Facts, Dateinamen der Sicherungen, README,
Roadmap, Installer- und Update-Skript, npm-Paketname.

Bewusst NICHT umbenannt, jeweils mit Begruendung im Code:
- POSTGRES_USER/DB und der Volume-Name bleiben "project_good". Ein neuer
  Volume-Name legt eine leere Datenbank an, ein neuer Benutzername scheitert am
  bestehenden Volume - vorhandene Bestaende waeren verloren.
- Der localStorage-Schluessel bleibt, sonst waeren alle Browser einmal
  ausgesperrt.
- ios/ bleibt unangetastet (wird getrennt bearbeitet).

Die Klon-Adresse im README zeigt jetzt auf .../vorrania.git; das Repo in Gitea
muss dafuer umbenannt werden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 13:05:32 +02:00
Scarriffle
26f7ed21d8 Ein- und Auslagerungen als Wasserfall- bzw. Brueckendiagramm
Bisher waren es zwei Linien mit der Anzahl der Vorgaenge. Gemeint war eine
Bruecke: die Bestandslinie, und schwebende gruene/rote Balken, die jeden ihrer
Spruenge erklaeren.

Damit Balken und Linie ueberhaupt zusammen in ein Bild duerfen, zaehlen die
Balken jetzt Mengen in Artikeleinheiten statt Vorgaengen - beides liegt so auf
derselben Achse. Zwei Skalen in einem Diagramm bleiben ausgeschlossen.

Neuer Endpunkt GET /dashboard/flow liefert je Abschnitt Anfangsbestand, Zugang
und Abgang aus einer Abfrage. Getrennt abgefragt koennten die Abschnitte von
timeline und activity um eine Schrittweite auseinanderliegen - dann stuende ein
Balken neben dem Sprung, den er erklaert.

/dashboard/activity bleibt fuer externe Zugriffe erhalten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:45:59 +02:00
Scarriffle
d6a717e39c Ablaufkarten trennen: "Bald ablaufend" zeigt kein Abgelaufenes mehr
Eine Karte mit dieser Ueberschrift, in der bereits Ueberfaelliges steht,
ist irrefuehrend. KarteAblauf bekommt daher einen Modus soon/expired/alle
statt des bisherigen Schalters "nurAbgelaufen".

Neu ist die Karte "Ablauf-Uebersicht" (alle), die beides zusammen zeigt.
Sie steht auch in der eingebauten Vorgabe, damit dort ohne Zutun weiterhin
Ueberfaelliges sichtbar ist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:24:30 +02:00
Scarriffle
1fd1e89872 Zeitraum im Kartenkopf; Verlauf feiner als ein Punkt pro Tag
Zeitraum-Auswahl:
Sie stand ueber dem Diagramm und nahm ihm Hoehe weg. Jetzt sitzt sie klein im
Kartenkopf neben dem Titel - die ganze Kartenhoehe gehoert dem Diagramm. Die
Wahl wird in der Anordnung mitgespeichert, bleibt also nach dem Neuladen
erhalten; ausserhalb des Bearbeitungsmodus geschieht das still im Hintergrund.

Feinere Aufloesung:
Ein Punkt je Tag verbarg, WANN ein- und ausgelagert wurde. Die Abtastrate
richtet sich jetzt nach dem Zeitraum:
  bis  2 Tage  -> stuendlich
  bis 14 Tage  -> alle 6 Stunden
  bis 120 Tage -> taeglich
  darueber     -> woechentlich
So bleibt die Punktzahl immer zwischen etwa 25 und 105 - fein genug zum
Erkennen, grob genug zum Zeichnen. Ein Test haelt diese Spanne fest.

Die Zeitachse traegt entsprechend Uhrzeit statt Datum, wenn stuendlich
abgetastet wird. Punkte heissen jetzt "at" (Zeitpunkt) statt "date".
Zeitstempel aus SQLite kommen ohne Zeitzone zurueck und werden vereinheitlicht,
sonst schluege die Differenzbildung fehl.

Auswahl erweitert: 24 Stunden, 2 Tage, 7, 30, 90 Tage, 6 Monate, 1 Jahr.

Geprueft: "npm run build" laeuft durch. pytest weiterhin nicht ausfuehrbar -
kein Python auf diesem Rechner; die Tests wurden an die neue Aufloesung
angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:36:33 +02:00
Scarriffle
97a6e94ea1 Kurzmeldungen als Overlay, Liniendiagramm fuellt die Karte, Zeitraum ab 1 Tag
Meldungen:
"Gespeichert", "Anordnung gespeichert" und die uebrigen Erfolgsmeldungen standen
als Leiste mitten in der Seite - beim Speichern sprang dadurch das ganze Layout
ein Stueck nach unten. Sie erscheinen jetzt als Overlay am unteren Rand und
verschwinden nach fuenf Sekunden von selbst; ein Kreuz blendet sie sofort aus.
Umgestellt in Uebersicht, Einstellungen, Gefahrenbereich, Benutzer,
Produktformular sowie Ein- und Auslagern.

Bewusst NICHT umgestellt: Fehlermeldungen. Ein Fehler, der nach fuenf Sekunden
verschwindet, ist genau dann weg, wenn man ihn noch braucht - der bleibt an
seiner Stelle stehen.

Rollbalken beim Liniendiagramm:
Den hatte ich mir mit der Zeitraum-Auswahl selbst eingebaut. Das Diagramm hatte
eine feste Hoehe von 200 px; zusammen mit der Auswahl darueber passte das nicht
mehr in die Karte. Jetzt wird auch die Hoehe gemessen, das Diagramm fuellt den
verbleibenden Platz.

Zeitraum "1 Tag":
Ein einzelner Tag ergibt genau einen Messpunkt - eine Linie aus einem Punkt
waere unsinnig. Das Diagramm zeigt in dem Fall die blanke Zahl. Die
Backend-Begrenzung laesst jetzt einen Tag zu.

Geprueft: "npm run build" laeuft durch; kein Verweis mehr auf die entfernten
Meldungszustaende.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:23:53 +02:00
Scarriffle
aa58914374 Kartengroessen korrigiert, Vorschau im Menue, Zeitraum-Auswahl bei Verlaeufen
Zu kleine Karten (Konstruktionsfehler):
Bei Schnellzugriff, Status und den Kennzahlen war die Mindesthoehe gleich der
Standardhoehe - und beide zu klein. Bei 96 px Zeilenhoehe blieben nach Kartenkopf
und Innenabstand nur 23 px fuer den Inhalt, eine Kennzahl braucht allein rund
45 px. Deshalb erschien schon in der Grundeinstellung ein Rollbalken, und
kleiner ging es nicht.
- Zeilenhoehe von 96 auf 40 px: die Hoehe laesst sich jetzt fein abstufen.
- Jede Karte hat eine Mindesthoehe, in die ihr Inhalt nachweislich passt
  (Rechnung steht als Kommentar am Verzeichnis).
- Gespeicherte Anordnungen aus der alten Fassung werden beim Laden auf die
  Mindestgroesse angehoben, statt zu flach zu bleiben.

Vorschau im Menue "Karte hinzufuegen":
Der Name allein verriet nicht, was eine Karte zeigt. Jede Karte hat jetzt eine
kleine Skizze (Tabelle, Ring, Balken, Linie, Kennzahl) und eine Beschreibung.
Bewusst gezeichnete Skizzen statt echter Vorschau - sonst muesste das Menue fuer
jede Karte Daten laden.

Zeitraum bei den Verlaufskarten:
Bestandsverlauf, Artikelverlauf und Ein-/Auslagerungen haben eine Auswahl
7 Tage bis 1 Jahr.

Ringdiagramme:
Der Ring klebte oben in der Karte, darunter blieb viel Leerraum. Er fuellt jetzt
die Karte und sitzt mittig.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 10:09:05 +02:00
Scarriffle
84ce596446 Modulare Startseite: Kartenraster, eigene Anordnung je Benutzer, Auswertungen
Die Uebersicht war fest verdrahtet und fuer alle gleich. Jetzt besteht sie aus
Karten in einem 12-Spalten-Raster, die sich ziehen, in der Groesse aendern und
einrasten lassen (react-grid-layout). "Anordnen" schaltet das frei, gespeichert
wird ausdruecklich - Probieren bleibt folgenlos.

Anordnung:
- Neue Tabelle dashboard_layouts; user_id = NULL ist die Admin-Vorgabe.
- Jeder Benutzer hat seine eigene Anordnung. Admins speichern eine Vorgabe fuer
  alle und koennen sie ueber die Einstellung dashboard_enforced verbindlich
  machen; dann lehnt das Backend das Speichern eigener Anordnungen ab.
- "Auf Vorgabe zuruecksetzen" verwirft die persoenliche Anordnung.

15 Karten (Verzeichnis web/src/dashboard/cards.jsx, neue Karte = ein Eintrag):
Schnellzugriff, Status, Artikelzahl, Artikeleinheiten, Bald ablaufend,
Abgelaufen, Einkaufsliste, letzte Bewegungen sowie sechs Auswertungen
(Ablauf-Ring, Kategorien-Ring, Kategorien nach Zustand, Bestandsverlauf,
Verlauf eines Artikels, Ein-/Auslagerungen).

Diagramme als eigene SVG-Komponenten statt Diagramm-Paket:
- Zustandsfarben (ok/bald/abgelaufen) sind Statusangaben aus den Tokens und
  stehen nie ohne Beschriftung; Kategorien nutzen eine gepruefte Farbreihe in
  fester Ordnung, ab sieben Kategorien wird zu "Andere" gebuendelt.
- Eine Werteachse, duenne Marken, zurueckhaltendes Gitter, Fadenkreuz mit
  Kurzinfo. Hell und Dunkel haben eigene Farbstufen.

Mengen durchgehend in Artikeleinheiten (Glaeser, Packungen, Stueck): Gramm und
Stueck lassen sich nicht addieren, Gebinde schon. Der bisher dreifach
vorhandene Helfer liegt jetzt einmal in services/conversion.py::article_unit.

Der Verlauf wird rueckwaerts vom heutigen Bestand aus den Bewegungen
rekonstruiert. Bekannte Ungenauigkeit, im Code und in der Roadmap vermerkt:
Die Umrechnung nutzt die heutige Packungsgroesse.

Geprueft: "npm run build" laeuft durch. Die neuen pytest-Tests
(backend/tests/test_dashboard.py) konnten hier nicht laufen - auf diesem
Rechner ist kein Python installiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 09:48:16 +02:00
Scarriffle
4448718d8d Logo-Upload sichtbar machen; empfohlene Abmessungen angeben
Fehler: Ein hochgeladenes Logo blieb unsichtbar, auch nach dem Neuladen.
Die Oberflaeche prueft per Anfrage, ob ein Bild hinterlegt ist, und nutzte dafuer
HEAD. FastAPI registriert fuer eine GET-Route aber - anders als Starlette - kein
HEAD; die Anfrage lief in ein 405, und jedes hinterlegte Bild galt als "nicht
vorhanden". Der Upload selbst war die ganze Zeit in Ordnung.

- Neuer Endpunkt GET /branding liefert nur den Status ({"logo": true, ...}),
  ohne Bilddaten zu uebertragen. Die Oberflaeche fragt jetzt diesen ab.
- Die Bild-Route beantwortet zusaetzlich HEAD, damit sie sich erwartungsgemaess
  verhaelt.

Ausserdem:
- Die empfohlenen Abmessungen stehen jetzt sichtbar ueber der Vorschau
  (Logo etwa 400x72 px, Favicon 64x64 px, jeweils hoechstens 512 KB).
- Nach dem Hochladen wird die tatsaechliche Bildgroesse angezeigt, damit man
  sie mit der Empfehlung vergleichen kann.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:55:17 +02:00
Scarriffle
903392e953 Kategorien ein-/ausklappbar, Badge-Darstellung in Tabellenzellen korrigiert
Kategorien:
- Oberkategorien lassen sich per Pfeil zuklappen; ihre Unterkategorien
  verschwinden dann aus der Liste. Ausgeblendet wird ueber die gesamte
  Vorfahrenkette, nicht nur eine Ebene, damit auch mehrstufige Baeume stimmen.
- Ist eine Kategorie zugeklappt, zeigt ein Badge, wie viele Eintraege darunter
  liegen - sonst waere nicht erkennbar, dass dort noch etwas ist.
- Zeilen ohne Unterkategorien bekommen einen Platzhalter, damit alle Namen
  buendig stehen.

Badges in Zellen:
- In der Karten-Ansicht verteilte das Raster mehrere Elemente einer Zelle auf
  zwei Rasterzellen - deshalb rutschte "eingebaut" unter die Beschriftung.
  Jetzt Flexfluss, alle Teile stehen hinter der Beschriftung.
- Einheiten-Tabelle: Name und "eingebaut" liegen in einer Zeile zusammen.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:46:39 +02:00
Scarriffle
546a6bfc90 Tabellen bleiben Tabellen; Karten erst auf schmalen Bildschirmen
Uebersicht, Einheiten und Kategorien zeigten am Rechner die Karten-Ansicht
(Beschriftung links, Wert daneben), weil die Umschaltgrenze bei 700px lag und
neben diesen Bloecken noch etwas steht. Drei bis vier Spalten passen dort aber
locker nebeneinander.

- Grenze auf 480px gesenkt: Karten praktisch nur noch am Handy hochkant.
- .table-wrap faengt Zwischengroessen mit waagerechtem Scrollen ab, statt
  Inhalte abzuschneiden.
- Tooltip-Beschriftung ohne Fragezeichen-Cursor.

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 08:07:28 +02:00
Scarriffle
e4e22c7822 Karten-Darstellung der Tabellen entzerrt, Erklaerungen als Tooltip
Tabellen (Uebersicht, Einheiten, Kategorien):
Wird der Container schmal, stellen sich Tabellen auf Karten um. Dort trennte
"space-between" Beschriftung und Wert an die beiden Zeilenenden - der Wert stand
weit rechts und wirkte durch die unterschiedlichen Zeilenhoehen nicht auf einer
Linie. Jetzt zwei feste Spalten (Beschriftung, Wert direkt daneben) mit
Ausrichtung an der Schriftlinie. Zahlenspalten stehen in der Karte linksbuendig.

Artikelseite:
Die Erklaerungen unter "MHD-Angabe", "Gruppe" und "Kategorie" standen dauerhaft
im Formular und machten es lang. Sie haengen jetzt als Tooltip an der
Beschriftung; eine gepunktete Linie zeigt an, dass dort eine Erklaerung steht.
(Kategorie hatte denselben Text-Block - der Einheitlichkeit halber mit
umgestellt.)

Geprueft: "npm run build" laeuft durch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 07:59:52 +02:00
Scarriffle
32d49b47bd Tabellen entzerrt, EAN-Codes mit Marke und Artikelname, Codes-Block nach unten
EAN-Codes:
- Das Kennzeichen "Artikel" ist weg; stattdessen stehen Marke und Artikelname
  zusammen ("Zweifel - Moutarde Original Chips"). Die Marke allein reichte nicht,
  um einen Code wiederzuerkennen. Hinweistext entsprechend angepasst.

Produktseite:
- "Weitere EAN-Codes" steht jetzt unterhalb von Formular und Chargen statt als
  dritte Spalte dazwischen. Die Codes braucht man selten und sie schoben die
  beiden wichtigen Bloecke auseinander.

Tabellen:
- "overflow-wrap: anywhere" galt fuer ALLE Zellen und zerriss Woerter mitten
  drin ("PROD UKTE", "555 Gram m"). Jetzt nur noch gezielt per Klasse.
- Kopfzeilen und Zahl-plus-Einheit brechen nicht mehr um.
- Eingaben in Tabellen sind kompakter (kleinere Polsterung, Zahlenfelder 8ch),
  Auswahlfelder richten sich nach ihrem Text statt 132px Mindestbreite -
  dadurch sind die Zeilen deutlich schlanker.
- Kennzeichen wie "niedrig" oder "abgelaufen" sitzen ueber .cell-row in einer
  Zeile mit dem Wert und rutschen nicht mehr je nach Zeilenhoehe herum.

Geprueft: "npm run build" laeuft durch. Backend unveraendert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 07:50:42 +02:00
Scarriffle
d804666a95 Keine Notiz mehr bei Gruppen-Codes, die zu einem Artikel gehoeren
Seit am Code die Marke steht, ist die Notiz dort ueberfluessig - sie sagte
dasselbe noch einmal und nahm nur Platz weg. Die Zeile zeigt jetzt Code,
Kennzeichnung "Artikel" und Marke, sonst nichts.

Bewusst nicht ueberall entfernt: Codes, die noch zu keinem Artikel gehoeren -
also die von Hand hinterlegten, damit ein kuenftiger Scan in dieser Gruppe
landet - haben gar keinen Artikel und damit auch keine Marke. Dort ist die
Notiz die einzige Angabe, woran man den Code wiedererkennt ("Mehl bei Aldi");
sie bleibt deshalb erhalten. Der Hinweistext erklaert den Unterschied.

Der Endpunkt zum Setzen der Notiz bleibt unveraendert - er wird fuer die Codes
ohne Artikel weiterhin gebraucht.

Getestet: Web-Build laeuft durch. Die Darstellung im Browser habe ich nicht
selbst angesehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:04:30 +02:00
Scarriffle
22b6cdffc5 Tabellen werden bei wenig Platz zu Karten statt seitwaerts zu scrollen
Waagerechtes Scrollen in einem Block war die falsche Antwort auf zu wenig
Platz - man sieht nicht, dass es weitergeht, und auf dem Trackpad ist es
muehsam. Das Scrollen ist ersatzlos weg.

Stattdessen stellt sich jede Tabelle unter 700px verfuegbarer Breite auf
Karten um: eine Karte je Zeile, jede Angabe mit ihrer Beschriftung davor.
Die Beschriftung kommt aus data-label an der Zelle, die Kopfzeile wird
ausgeblendet. Zellen ohne Beschriftung (Vorschaubild, Aktionsknoepfe) bleiben
ohne eigene Zeile.

Ausgeloest wird das per Container-Abfrage, nicht per Bildschirmbreite. Damit
richtet sich die Darstellung nach dem Platz, den die Tabelle wirklich hat -
sie stellt sich also auch um, wenn das Fenster nur die halbe Bildschirmbreite
einnimmt oder ein Block danebensteht.

Dazu die Aufteilung neben dem Block: Die Tabelle hat mehr Spalten als der
Block und bekommt jetzt den groesseren Anteil (1,5 zu 1, Block mindestens
340px). Vorher waren beide gleich breit, wodurch die Tabelle unnoetig frueh in
die Kartenansicht gerutscht waere, obwohl daneben Platz frei war.

Durchgerechnet: Bei 1920 voller Breite bleibt es eine Tabelle (802px) mit
komfortablem Block daneben (534px). Bei halber Bildschirmbreite stapelt sich
die Seite und die Tabelle wird zur Karte. In keinem Fall wird etwas
abgeschnitten und nirgends muss seitwaerts gescrollt werden.

Lange Zeichenketten in Zellen brechen jetzt um (overflow-wrap), da kein
Scrollen mehr auffaengt, was hinauslaeuft.

Dabei aufgefallen: In der Produkttabelle stand nach dem Ergaenzen der
Kategorie-Spalte noch colSpan={7} statt 8 - die Zeile "Keine Produkte
gefunden" ueberspannte die Tabelle nicht mehr ganz. Alle colSpan-Angaben sind
jetzt gegen die Spaltenzahl gegengeprueft.

Getestet: Web-Build laeuft durch, Spaltenzahl und colSpan stimmen in allen
fuenf betroffenen Tabellen ueberein, die Umbruchpunkte sind durchgerechnet.
Die Darstellung im Browser habe ich nicht selbst angesehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:00:44 +02:00
Scarriffle
8f549636a8 Marke statt Produktname am EAN-Code, Layout bricht um statt abzuschneiden
Am Gruppen-Code stand der Produktname. Innerhalb einer Gruppe hilft der aber
nicht weiter: In der Gruppe "Zucker" heisst jeder Artikel irgendwie "Zucker",
in "Mehl" irgendwie "…mehl". Unterscheiden laesst sich das nur ueber die Marke.
Der Endpunkt liefert jetzt beides; angezeigt wird die Marke, faellt bei
fehlender Marke auf den Namen zurueck und zeigt den vollen Namen im Tooltip.
Nebenbei ist die Marke meist deutlich kuerzer als der Name.

Abgeschnittene Tabelle: Die Spalte "verwalten" wurde gekappt. Ursache war nicht
die Breite an sich, sondern dass Grid-Kinder standardmaessig nicht unter ihre
Inhaltsbreite schrumpfen (min-width: auto). Dadurch lief die Tabelle aus ihrer
Karte heraus und wurde beschnitten, statt dass das vorhandene overflow-x
gegriffen haette. Mit min-width: 0 an den Grid-Kindern scrollt sie jetzt.
Zusaetzlich hat die Tabelle eine Mindestbreite von 640px - lieber waagerecht
scrollen als Spalten so weit quetschen, bis Text verschwindet.

Umbruch statt Quetschen: Die feste Umbruchbreite von 820px passte nicht zur
tatsaechlichen Lage - entscheidend ist, wie viel Platz die Spalten wirklich
brauchen, nicht wie breit der Bildschirm ist. Jetzt auto-fit mit Mindestbreite
je Spalte: Zwei Spalten, solange beide genug Platz haben, sonst automatisch
untereinander. Seiten mit inhaltsreichem Seitenblock (EAN-Codes) verlangen mehr
und stapeln frueher.

Durchgerechnet: Bei 1920 voller Breite zwei Spalten zu je 668px, Tabelle passt.
Bei halber Breite (960) eine Spalte zu 676px, Tabelle passt weiterhin. Erst bei
sehr schmalen Fenstern (unter etwa 900px Gesamtbreite) scrollt die Tabelle
waagerecht. In keinem Fall wird noch etwas abgeschnitten.

Getestet: 58 pytest-Tests gruen, Web-Build laeuft durch. Gegen die laufende API
geprueft, dass Marke und Name am Code mitkommen und die fehlende Marke sauber
auf den Namen zurueckfaellt. Die Breiten sind rechnerisch geprueft, die
Darstellung im Browser habe ich nicht selbst angesehen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:53:29 +02:00
Scarriffle
e20a61bf3b Notizfeld bei EAN-Codes bekommt Platz, Seite nutzt mehr Breite
Bei einem Code mit langem Artikelnamen war das Notizfeld auf wenige Pixel
zusammengequetscht ("Zucker - Feinkristallzucker" brach auf zwei Zeilen um und
nahm dem Feld den Raum), waehrend es beim Nachbarcode normal breit war.

Drei Ursachen, alle behoben:

Der Inhalt lag als Inline-Stil in einer Flex-Zeile ohne Regeln fuer den
Umgang mit Platzmangel. Jetzt eigene Klassen: Der Artikelname kuerzt mit
Auslassungspunkten statt umzubrechen, das Notizfeld hat eine Mindestbreite von
150px und waechst mit, die Zeile darf bei echtem Platzmangel umbrechen. Auch die
Zeile zum Hinzufuegen bricht jetzt um, statt "EAN scannen oder eingeben" auf
"EAN scani" zu stutzen.

Die Seite war auf 1080px begrenzt. Auf breiten Bildschirmen blieb dadurch viel
Flaeche leer, obwohl Tabelle und Seitenblock sie gut gebrauchen koennen; jetzt
1400px.

Der Seitenblock war exakt halb so breit wie die Tabelle. Auf der Gruppenseite
bekommt er nun eine Mindestbreite von 360px, damit Code, Kennzeichnung,
Artikelname und Notizfeld nebeneinander Platz haben. Unterhalb von 820px stapelt
sich weiterhin alles untereinander.

Getestet: Web-Build laeuft durch. Die Darstellung im Browser habe ich nicht
selbst angesehen - die Breiten sind rechnerisch gesetzt, nicht ausprobiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:44:50 +02:00
Scarriffle
5293810529 Gruppen-Codes: Dublette behoben und Altbestand nachgezogen
Zwei Fehler in der EAN-Liste einer Gruppe, beide gemeldet und nachgestellt.

Derselbe Code stand zweimal in der Liste: einmal schreibgeschuetzt mit der
Kennzeichnung "Artikel", einmal darunter mit Notizfeld. Grund war, dass
_group_to_out die Codes der Artikel unabhaengig von den Gruppen-Codes
zusammengestellt hat - seit die Zuordnung automatisch einen Gruppen-Code
anlegt, trifft beides auf denselben Code zu. Die schreibgeschuetzte Zeile stand
oben, deshalb war das Notizfeld darunter leicht zu uebersehen.

Jetzt gibt es eine Zeile je Code. Der Gruppen-Code fuehrt den Artikel mit, ueber
den er dazugehoert (neues Feld product_name in BarcodeOut), zeigt weiterhin die
Kennzeichnung "Artikel" - und hat trotzdem ein Notizfeld. Der Muelleimer
entfaellt bei diesen Codes, denn sie kaemen beim naechsten Speichern des
Artikels sofort zurueck; dafuer muss der Artikel die Gruppe wechseln.

Zweitens fehlte fuer bestehende Daten der Code ganz. Die automatische Pflege
greift nur beim Anlegen und Aendern eines Artikels; Zuordnungen, die es vorher
schon gab, hatten nie einen Gruppen-Code bekommen. In der Verwaltung stand der
Code deshalb ausschliesslich als schreibgeschuetzte Artikel-Zeile - genau die
Stelle, an der sich keine Notiz hinterlegen liess. Neu holt backfill() das beim
Start nach: fuer jeden Artikel mit Gruppe und Barcode wird der Gruppen-Code
angelegt, sofern er fehlt. Gefahrlos wiederholbar.

Getestet: 58 pytest-Tests gruen, einer neu (Backfill legt den fehlenden Code an
und beim zweiten Lauf nichts doppelt). Der gemeldete Fall wurde vorher gegen die
laufende API nachgestellt - Altbestand ohne Gruppen-Code und ein doppelt
gelisteter Code nach einer Neuanlage - und danach als behoben bestaetigt: eine
Zeile je Code, mit Artikelnamen und Notizfeld. Web-Build laeuft durch.
Die Oberflaeche habe ich nicht selbst bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:41:43 +02:00
Scarriffle
dfbc62398d Eigener Rueckfrage-Dialog statt Browsermeldung, Zeitstempel im Export, Notizen an Gruppen-Codes
Rueckfragen liefen bisher ueber window.confirm. Das reisst aus der Oberflaeche,
zeigt den Hostnamen, laesst sich nicht gestalten und die Knoepfe heissen immer
"OK/Abbrechen". Neu ist ein eigener Dialog (src/confirm.jsx) als Provider mit
dem Hook useConfirm. Der Aufruf bleibt so einfach wie vorher, weil er eine
Zusage zurueckgibt:

    if (!(await confirm({ title: "…?", message: "…" }))) return;

Damit sind alle neun Stellen umgestellt: Gruppen, Kategorien, Lagerorte,
Einheiten, Benutzer, API-Tokens, Produkt und Charge loeschen sowie das Ersetzen
beim Import. Jede Rueckfrage hat jetzt einen sprechenden Titel, einen Satz zu
den Folgen und einen benannten Knopf ("Loeschen", "Widerrufen", "Ersetzen")
statt eines nichtssagenden OK. Unwiderrufliche Schritte sind rot.
Kuenftig gilt: keine Browserdialoge mehr.

Export: Dateinamen tragen jetzt Datum und Uhrzeit
(bestand_2026-07-22_2130.csv, project-good-backup_2026-07-22_2130.json). Ohne
Zeitstempel hiessen mehrere Ausleitungen alle gleich und der Browser haengte
(1), (2) an - dann war nicht mehr erkennbar, welche die aktuelle ist. Der
Zeitstempel entsteht an beiden Enden gleich: im Content-Disposition-Kopf des
Backends und im Dateinamen, den der Browser setzt. Im JSON steht zusaetzlich
die Ortszeit neben dem bereits vorhandenen UTC-Zeitpunkt. Die CSV bleibt
inhaltlich unveraendert - eine zusaetzliche Spalte oder Kopfzeile wuerde die
Datei in Excel nur stoeren.

Notizen an Gruppen-Codes: Codes, die beim Zuordnen eines Artikels automatisch
entstehen, hatten bisher keine Moeglichkeit, eine Notiz zu bekommen - die liess
sich nur beim Anlegen von Hand mitgeben. Neu PATCH /groups/{id}/barcodes/{code}
und ein Notizfeld in der Liste, das beim Verlassen speichert. Leeren entfernt
die Notiz.

Getestet: 57 pytest-Tests unveraendert gruen, Web-Build laeuft durch. Gegen die
laufende API geprueft: Zeitstempel in beiden Content-Disposition-Koepfen und im
JSON-Inhalt; Notiz an einem automatisch angelegten Code setzen, aendern und
leeren, unbekannter Code antwortet mit 404. Die Dialoge selbst habe ich nicht
im Browser angeklickt.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:31:45 +02:00
Scarriffle
48cc2646e8 Favicon aus dem Seitenleisten-Zeichen und Gefahrenbereich in den Einstellungen
Favicon: Bisher diente der Barcode-Glyph des App-Icons als Standard. Jetzt
zeigt der Browser-Tab dasselbe Zeichen wie die Seitenleiste - das "box"-Icon
aus components/Icon.jsx auf der Akzentflaeche. Die Farbe folgt der Oberflaeche:
im dunklen Modus der hellere Akzent, damit das Symbol auf beiden Untergruenden
sitzt. Ein eigenes Favicon laesst sich weiterhin hochladen und hat Vorrang.

Gefahrenbereich (Einstellungen, nur Admin) mit drei Stufen, damit man nicht
mehr wegwirft als noetig:

1. Alle Bestaende loeschen - Chargen und Bewegungsverlauf. Der Katalog bleibt
   vollstaendig erhalten, der Bestand steht ueberall auf null.
2. Alle Artikel loeschen - zusaetzlich der Artikelkatalog samt seiner
   EAN-Codes. Gruppen, Kategorien, Lagerorte und Einheiten bleiben. Die Codes,
   die an Gruppen haengen, bleiben ebenfalls.
3. Alles zuruecksetzen - zusaetzlich Gruppen, Kategorien, Lagerorte, Einheiten.

Benutzer, API-Tokens, Einstellungen und das eigene Logo bleiben in allen drei
Stufen erhalten. Anders liesse man sich aussperren und muesste externe Zugriffe
wie Home Assistant neu einrichten. Nach Stufe 3 werden die eingebauten
Einheiten und die Kategorie-Startliste sofort neu angelegt - ohne Einheiten
liesse sich nichts mehr einlagern, die Installation waere unbrauchbar.

Absicherung: Jede Aktion nennt vorher mit Zahlen, was genau verschwindet
("Betrifft 43 Chargen und 12 Bewegungen"), und laeuft erst, wenn das Wort
LOESCHEN eingetippt wurde. Ein Fehlklick kann nichts anrichten. Der Abschnitt
ist rot abgesetzt und weist auf das JSON-Backup hin.

Getestet: 57 pytest-Tests gruen, 3 davon neu (Bestaende loeschen laesst den
Katalog stehen, Artikel loeschen laesst Gruppen, Lagerorte und Einheiten sowie
den Gruppen-Code stehen, Startkategorien kommen zurueck). Gegen die laufende
API alle drei Stufen nacheinander durchgespielt und die Zahlen nach jedem
Schritt geprueft; Benutzer bleiben, Einheiten sind danach wieder da, ohne
Adminrechte antwortet der Endpunkt mit 401. Das Favicon wurde gerendert und
mit dem Zeichen der Seitenleiste verglichen. Web-Build laeuft durch.
Die Oberflaeche habe ich nicht selbst bedient - insbesondere habe ich keine
der Loeschaktionen ueber die Web-Oberflaeche ausgeloest.

Nebenbei die deutschen Anfuehrungszeichen im ganzen Projekt vereinheitlicht
(schliessendes Zeichen typografisch statt gerade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:21:52 +02:00
Scarriffle
158196f67b Gruppen und Kategorien getrennt, EAN-Codes an der Gruppe automatisch gefuehrt
Die Umbenennung von Gruppen in "Kategorien" war falsch: Es sind zwei
verschiedene Dinge. Sie ist zurueckgenommen, Kategorien kommen als eigene Ebene
dazu.

GRUPPE zaehlt Bestaende mehrerer Marken zusammen - Mehl von Rewe, Aldi und
Migros ergeben "5 kg Mehl". Dafuer Mindestbestand mit Einheit und EAN-Codes.
KATEGORIE ordnet allein die Artikelliste ("zeig mir alle Suesswaren"), ist
verschachtelbar wie ein Lagerort und hat weder Bestand noch EAN-Codes. Ein
Artikel kann beides, eines oder keines haben.

Die Vermischung war aelter als die Umbenennung: guessGroup in offUtils.js hat
aus der Open-Food-Facts-KATEGORIE eine GRUPPE geraten. Das ist entfernt. Die
OFF-Einordnung steuert jetzt die Kategorie, wo sie hingehoert; eine Gruppe
entsteht nur ueber einen hinterlegten Gruppen-Code oder bewusste Auswahl.

EAN-Codes an der Gruppe: Das war kein Anzeigefehler. Beim Anlegen eines Artikels
wurde ausschliesslich group_id gesetzt - ein Gruppen-Code entstand nie, die
Liste war tatsaechlich leer. Die Meldung "bereits vergeben" kam daher, dass der
Code am Artikel hing. Jetzt pflegt services/group_codes.py den Code mit: beim
Zuordnen kommt er hinzu, beim Gruppenwechsel wandert er mit, beim Entfernen der
Gruppe oder Loeschen des Artikels verschwindet er. Beim Scannen aendert sich
nichts an der Reihenfolge - der Artikel wird weiterhin zuerst gefunden; der
Gruppen-Eintrag ist Beleg in der Verwaltung und Rueckfall. Traegt man denselben
Code von Hand nach, ist das kein Fehler mehr, sondern die Auskunft, dass er ueber
den Artikel bereits dort steht.

Kategorien im Backend: neue Tabelle mit parent_id (Muster von Location),
products.category_id per ADD COLUMN IF NOT EXISTS nachgezogen, deutsche
zweistufige Startliste analog zu den eingebauten Einheiten. Die Startliste wird
nur angelegt, wenn ueberhaupt noch keine Kategorie existiert - wer sie bewusst
leerraeumt, findet sie nicht wieder. Beim Setzen einer Oberkategorie wird
geprueft, dass keine Kategorie sich selbst oder einem eigenen Nachfahren
untergeordnet wird; sonst entstuende ein Ring und jede Baumdarstellung liefe
endlos. Der Produktfilter schliesst Unterkategorien ein, category_id=0 liefert
die Artikel ohne Kategorie. Export und Import fuehren die Kategorie als Pfad
("Suesswaren & Snacks > Schokolade") in einer Spalte, damit die CSV in Excel
bedienbar bleibt.

Web: neue Seite Kategorien mit Baumdarstellung, Filter ueber der Produktliste,
getrennte Auswahlfelder im Produktformular mit je einer Zeile Erklaerung, und
beim Einlagern laesst sich eine Gruppe samt Einheit und Mindestbestand direkt
anlegen, ohne den Vorgang zu verlassen.

iOS: Kategorie-Filter ueber der Produktliste, Unterkategorien eingerueckt. Die
Artikelzeile nennt jetzt das Gebinde und warnt, wenn der Mindestbestand
unterschritten ist (rot) oder weniger als ein Viertel Luft bleibt (orange).
Getrennte Auswahlfelder fuer Kategorie und Gruppe, Gruppe direkt anlegbar.

Ausserdem die Eingabefelder in der App: .textFieldStyle(.roundedBorder) zeichnet
in der dunklen Darstellung einen fast schwarzen Kasten. Ersetzt durch eine
Systemfuellung, die sich Hell und Dunkel anpasst und zurueckhaltend bleibt.

Getestet: 54 pytest-Tests gruen, 14 davon neu (Nachfahren-Sammler, OFF-Zuordnung
inklusive Vorrang der Unterkategorie, und die komplette Codepflege an der
Gruppe). Gegen die laufende API geprueft: Startliste ohne Dubletten, Filter auf
Ober- und Unterkategorie, Ringschutz, Loeschen einer Kategorie laesst Artikel
und Unterkategorien bestehen, Export/Import-Rundlauf mit Kategoriepfad, und der
Durchlauf aus der Meldung - Artikel mit Gruppe anlegen, Code steht danach in der
Gruppe. Web-Build und iOS-Geraetebuild fehler- und warnungsfrei.
Die Oberflaechen habe ich nicht selbst bedient.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:59:14 +02:00
Scarriffle
8dcc17b22f EAN-Codes einer Kategorie nachvollziehbar gemacht
Eine Kategorie mit Artikeln zeigte "0 EANs", und der Versuch, den Code eines
eigenen Artikels einzutragen, scheiterte mit "Dieser Code ist bereits vergeben".
Beides war fachlich richtig, aber nirgends erklaert.

Hintergrund: Die Code-Liste einer Kategorie ist eine Vorratsliste fuer Artikel,
die es noch nicht gibt. Sie greift nur, wenn beim Einlagern ein unbekannter Code
gescannt wird - dann landet der neu angelegte Artikel in dieser Kategorie
(routers/products.lookup). Haengt ein Code bereits an einem Artikel, findet die
Suche immer zuerst den Artikel; ein gleichlautender Kategorie-Eintrag koennte
nie wirken. Die Spalte zaehlte bisher ausschliesslich diese Vorratscodes, nie
die Codes der enthaltenen Artikel - daher die irritierende 0.

Die Kategorie liefert jetzt zusaetzlich die Codes ihrer Artikel mit
(product_barcodes, rein informativ). Beide Herkuenfte stehen in einer Liste:
Artikel-Codes sind als solche gekennzeichnet, nennen den Artikel und lassen
sich hier nicht loeschen, weil sie am Artikel haengen. Bewusst ohne Dublette in
der Datenbank - ein zweiter Datensatz koennte nie greifen und beim Loeschen des
Artikels verwaisen. Die Spalte zaehlt beide Herkuenfte.

Die Fehlermeldung sagt jetzt, wem ein Code gehoert: beim Artikel mit Namen und
dem Hinweis, dass Artikel-Codes hier nicht eingetragen werden muessen; bei einer
anderen Kategorie mit deren Namen.

Signal beim Einlagern: Wird ein Code erkannt, der einer Kategorie zugeordnet
ist, steht jetzt deutlich sichtbar "Wird automatisch der Kategorie X zugeordnet"
samt Begruendung - sowohl im Web als auch in der App, und in beiden Faellen
(Open-Food-Facts-Treffer und voellig unbekannter Code). Nach dem Anlegen meldet
die Web-Oberflaeche zurueck, welche Kategorie es geworden ist. Vorher stand die
Zuordnung nur als Nebensatz in grauer Kleinschrift.

Getestet: Gegen die laufende API geprueft, dass eine Kategorie die Codes ihrer
Artikel meldet (Haupt-Barcode und zusaetzliche Alias-Codes), dass das Eintragen
eines Artikel-Codes und eines fremden Kategorie-Codes mit der jeweils richtigen
Begruendung abgewiesen wird und dass ein echter Vorratscode weiterhin angelegt
werden kann. iOS-Geraetebuild und Web-Build fehlerfrei, 40 pytest-Tests gruen.

Dabei zwei Uebersetzungsfehler durch deutsche Anfuehrungszeichen gefunden: Das
schliessende Zeichen war ein gerades ", das den String vorzeitig beendete.
Korrigiert und die uebrigen Vorkommen im Projekt gleich mit vereinheitlicht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:52:34 +02:00