Commit graph

67 commits

Author SHA1 Message Date
Eduard Wisch
7c21446f98 Bild-Viewer mit Zoom und Swipe-Navigation [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
- Neuer Lightbox-Viewer für Bilder in der Anhänge-Liste
- Pinch-to-Zoom (Touch) und Mausrad-Zoom (Desktop)
- Doppelklick/Doppeltap für Zoom-Toggle (1x ↔ 2.5x)
- Swipe links/rechts für Navigation zwischen Bildern
- Tastatur: ← → Esc + - 0
- Bilder pro Gruppe navigierbar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 13:00:17 +02:00
Eduard Wisch
b09030afd5 Deploy: Dokumenten-Scanner mit OCR [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 12:21:22 +02:00
Eduard Wisch
aa58b5692c Dokumenten-Scanner mit OCR-Unterstützung
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Neuer "Dokument scannen" Button in der Mobile-Upload-Seite:
- Eigene Scanner-View für mehrseitige Dokumente
- Seiten-Vorschau-Leiste mit Thumbnails
- Seiten hinzufügen/löschen vor dem Upload
- Backend-Verarbeitung zu PDF

OCR-Verarbeitung (wenn verfügbar):
- ocrmypdf für durchsuchbares PDF (deu+eng)
- Automatische Rotation und Schräglagekorrektur
- Fallback auf ImageMagick oder TCPDF

Server-Konfiguration (optional für OCR):
  apt install tesseract-ocr tesseract-ocr-deu ocrmypdf

[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 09:59:14 +02:00
Eduard Wisch
04fdd87ffd Kamera-View: Mehrere Fotos ohne Kamera-Neustart
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Eigene Kamera-View mit getUserMedia() statt nativer Kamera-App:
- Vollbild-Overlay mit Live-Stream
- Auslöser, Vorschau, Speichern/Verwerfen
- Kamera bleibt nach Speichern offen
- Front/Back-Wechsel
- Session-Counter

[deploy]

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 09:18:50 +02:00
5fc6338ced API: DELETE /reports.php?id=X — Bericht löschen (für PWA) [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:32:33 +02:00
7e199a79ad Trigger: Deploy Lösch-Dialog [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:25:06 +02:00
85005e3335 Lösch-Dialog in Bericht-Übersicht: eigener Modal statt fehlender Handler
All checks were successful
Deploy bericht / deploy (push) Has been skipped
In der Übersicht (ohne Editor) wurde data-dolconfirm nicht abgefangen,
weil editor.js dort nicht geladen wird. Inline-Script mit gleichem
bericht-dolmodal-Stil ergänzt — kein browser-confirm().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:23:00 +02:00
9271ec279f Foto-Upload vom Bericht entkoppeln: Fotos landen im Auftragsordner
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
- Token-Tabelle: fk_bericht → fk_element + element_type (generisch)
- Migration: bestehende Tokens auf neue Spalten migrieren
- upload_photo API: Foto direkt nach commande/{ref}/, kein Bericht/BerichtPage mehr
- mobile_upload.php: Upload-Ziel über Token-Methode getUploadDir() ermitteln
- Token-Erstellung: element_id + element_type statt berichtid (abwärtskompatibel)
- QR-Modal: Token für Auftrag statt für Bericht; Polling auf Anhänge-Änderung [deploy]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 13:48:09 +02:00
b9e204f81c Hack-Font deaktiviert, fallback auf Helvetica [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
TCPDF::addTTFfont produziert bei allen 4 Hack-TTFs den gleichen Key 'hack'
(PostScript-Family-Kollision) und erzeugt eine hack.php die sich selbst
überschreibt. Der HTML-Parser löst bei <b>/<i>-Tags dann SetFont-Calls aus,
die auf nicht-existente Style-Varianten fallen zurück und mit
'Could not include font definition file: hack' abbrechen — keine Menge
an SetFont-Overrides fängt das zuverlässig ab.

Lösung: hack_font_key = helvetica. PDF-Generation funktioniert garantiert.
Hack wird später mit vorab-offline-konvertierten .php-Definitionen neu
eingebunden.
2026-04-09 16:01:43 +02:00
3ec303c48e Hack-Font: nur Regular, Styles komplett ignorieren [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Problem: TCPDF_FONTS::addTTFfont leitet den Font-Key aus dem PostScript-
Family-Namen der TTF ab. Alle 4 Hack-TTFs teilen den internen Namen 'Hack'
und erzeugen dieselbe hack.php, die sich gegenseitig überschreibt.
SetFont(hack, 'B') sucht dann hackB.php → existiert nicht →
'Could not include font definition file: hack'.

Lösung: nur Hack-Regular.ttf registrieren, SetFont-Override erzwingt
bei der Hack-Family immer style=''. Header-Titel nutzt nur Schriftgröße
statt echter Bold-Variante.
2026-04-09 15:58:09 +02:00
b338d4e369 BerichtPdf: SetFont-Override routet Hack-Styles auf die richtige Family [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
TCPDF_FONTS::addTTFfont registriert jede TTF als eigene Family ohne
Styles (hackregular, hackbold, hackitalic, hackbolditalic). SetFont(key,'B')
suchte dann nach 'hack.php' und brach mit 'Could not include font
definition file' ab.

Fix: hack_font_styles-Map ['' / B / I / BI] => family, SetFont-Override
mapped den angeforderten Style auf die passende Family mit leerem Style
und fällt kaskadierend zurück (BI→B→'', I→'').
2026-04-09 15:53:02 +02:00
3a7ed278e5 Ajax-Endpoints: Fatal-Handler + Output-Buffer, mysoc defensiv [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- ajax/_inc.php: ob_start() + register_shutdown_function fangen PHP Notices
  und Fatals auf, geben strukturiertes JSON zurück (vorher Server-Fehler
  'kein JSON' weil PHP-Warning mitten im Body stand).
- generate_pdf.php/preview_pdf.php: mysoc, logo-Pfad defensiv geprüft.
2026-04-09 15:50:36 +02:00
d5b89747be Leere Dolibarr-Confirm-Box entfernen (MutationObserver) [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
2026-04-09 15:47:40 +02:00
bf368122aa Keine Browser-Dialoge mehr — eigene dolModal-Helper [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Eddys Browser blockiert alert/confirm/prompt, deshalb sind die Dialoge
unsichtbar. Komplett umgestellt:

- js/editor.js: dolAlert/dolConfirm/dolPrompt als Promise-basierte Modals
  mit passendem Dark-Theme-Styling. Globaler click-Handler für
  data-dolconfirm an Links/Buttons.
- Alle alert()/confirm()/prompt() in editor.js ersetzt (Template speichern,
  Finalize, Upload, Löschen, Signatur-Verify usw.).
- bericht_card.php + admin/setup.php: onclick="return confirm(...)" durch
  data-dolconfirm="..." ersetzt.
- btn-finalize: butActionConfirm → butAction (Dolibarr erzeugt sonst
  automatisch einen leeren jQuery-UI-Confirm-Dialog
  #confirm-dialog-box-btn-finalize, unser eigener Handler macht das jetzt).
- css/bericht.css: Styling für .bericht-dolmodal*.
2026-04-09 15:46:25 +02:00
d40587845f PDF-Header mit Logo+Titel, Footer mit Seitenzahl, Hack-Font beschreibbar [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Neue Klasse BerichtPdf / BerichtPdfFpdi (Trait-basiert):
  * Header: links Bericht-Titel (Bold) + Firmenname, rechts Firmen-Logo (max 40x18mm),
    Trennlinie. Top-Margin jetzt 30mm für den Header-Bereich.
  * Footer: zentriert "Seite X / Y" mit TCPDF-Aliases.
  * berichtInit(): kompiliert Hack-TTFs nach DOL_DATA_ROOT/bericht/tcpdf_fonts/
    (beschreibbar) und bindet sie per AddFont an die PDF-Instance.
    Vorher schlug addTTFfont still fehl weil K_PATH_FONTS read-only war —
    deshalb kam weder Titel noch Notiz in Hack.
- bericht_ensure_hack_font($pdf) zieht den Font-Key jetzt aus der Instance
  (BerichtPdfTrait), sonst Fallback helvetica.
- bericht_write_note_html() wrapped das CKEditor-HTML in
  <span style="font-family:hack...;"> damit writeHTMLCell den Hack-Font
  tatsächlich verwendet.
- Composite-Branch: $mT=30 / $mB=16 damit Bilder nicht unter dem Header
  sitzen.
- ajax/generate_pdf, ajax/preview_pdf, api/pdf, api/reports, bericht_batch:
  alle nutzen jetzt BerichtPdf(Fpdi), setzen SetMargins(10,30,10),
  setPrintHeader(true) und berichtInit() mit Titel, mysoc->name und Logo.
2026-04-09 15:39:42 +02:00
36aad9539a Fix: weiße Seiten nach Reload — bgImage nicht mehr als blob-URL serialisieren [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Die fabric.Image der Quell-Bilder wurde mit toJSON([bgImage]) inkl. ihrer
blob:-URL gespeichert. Nach Reload ist die Blob-URL ungültig, fabric kann
das Bild nicht holen → weißer Canvas.

Fix: bgImage-Objekte beim Speichern aus dem JSON rausfiltern. Beim Laden
immer rerenderCurrent() aufrufen (frisches Quellbild aus page_image.php),
dann nur die Overlay-Shapes via enlivenObjects restoren.
2026-04-09 15:28:45 +02:00
021ef1cbb2 DolEditor für Notiz + Notiz unter Bild + Hack-Font im PDF [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- bericht_card.php: Plain-Textarea durch DolEditor (CKEditor, dolibarr_notes-Toolbar)
  ersetzt, damit Notizen formatiert werden können.
- editor.js: getNoteValue()/setNoteValue() Helper für transparentes
  CKEditor/Textarea-Handling an allen Zugriffsstellen.
- lib/bericht.lib.php:
  * bericht_write_note_html() rendert CKEditor-HTML via TCPDF::writeHTMLCell
  * Composite-Branch: Notiz direkt unter dem Bild statt mit SetY(-20) unten
  * bericht_ensure_hack_font() registriert Hack-TTFs beim ersten PDF-Run
  * Alle helvetica-SetFont-Calls auf Hack umgestellt (Corporate-Font)
- fonts/: Hack-Regular/Bold/Italic/BoldItalic.ttf (Eddys Corporate-Font)
- bericht_batch.php: ebenfalls Hack-Font
2026-04-09 15:25:10 +02:00
9c7ef73061 fix: Image-Load robust + Schutz gegen Speichern ohne Bild
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Diagnose: bei alten Berichten ohne composite_path wurde das bgImage
beim Öffnen nicht geladen (fabric.Image.fromURL mit crossOrigin bei
Blob-URL hing sich auf). Danach hat jeder Save das Composite ohne
Bild gespeichert → PDF-Seite leer.

Fix 1 — renderImage:
- Native Image() statt fabric.Image.fromURL
- Kein crossOrigin bei Blob-URLs (überflüssig, verursachte Hänger)
- Timeout 10s mit console.warn
- onerror mit Debug-Info (mime, bufSize)
- applyTool() nach Einfügen damit Tool-Lock greift

Fix 2 — savePageAnnotations:
- Wenn KEIN Bild im Canvas ist (weder bgImage=true noch type=image),
  wird der Save ABGEBROCHEN statt ein leeres Composite zu speichern.
- Dadurch kann ein alter Bericht nicht versehentlich mit einem
  leeren Composite überschrieben werden wenn der Bild-Load hakt.
- Toast-Warnung für den User, console.warn für Debug.

Fix 3 — finalize-Handler:
- Native confirm() vor dem Finalisieren
- Bessere Fehler-Diagnose: lese Response als text(), parse JSON,
  logge bei JSON-Parse-Fehler den Raw-Body
- toast() statt alert() für Fehler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:58:18 +02:00
01fbd72310 fix: alte Berichte ohne bgImage im JSON laden Quellbild nach
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Wenn ein Bericht vor Phase 6 gespeichert wurde, enthält fabric_json
nur Shapes (Pfeile, Text etc.) — das Quellbild war damals noch
pdfCanvas-Hintergrund. Beim Restore war jsonHasObjects=true und
rerenderCurrent wurde nicht aufgerufen → Canvas weiß.

Fix: Zusätzlicher Check jsonHasBgImage. Wenn kein Image im JSON
(weder bgImage=true noch type=image), wird zusätzlich zu den
geladenen Shapes rerenderCurrent() aufgerufen, das das Quellbild
als fabric.Image hinter die Shapes legt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:42:06 +02:00
e0ae936811 fix: Bericht öffnen — Canvas war weiß obwohl Seiten Bilder hatten
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Drei Probleme behoben:

1. Wenn fabric_json existierte aber leer oder ohne Objekte war, wurde
   rerenderCurrent() NICHT aufgerufen und das Canvas blieb leer.
2. Canvas-Dimensionen wurden nur im fabric_json-Zweig gesetzt — bei
   altem Berichten ohne JSON fehlte der Init und das Canvas war 1x1.
3. loadFromJSON-Callback hat zwar das Promise resolved, aber die
   nachfolgende applyTool() für Tool-Locking fehlte, dadurch waren
   die geladenen Bilder nach Reload immer ziehbar.

Fix:
- Canvas-Dimensionen IMMER zuerst auf A4 setzen
- Nur laden wenn fabric_json tatsächlich Objekte enthält
- Sonst (oder bei Parse-Fehler) Fallback auf rerenderCurrent() das
  das Quell-Bild frisch als fabric.Image lädt
- applyTool() nach loadFromJSON damit Tool-Lock-Status korrekt ist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:39:56 +02:00
9e96063bbc fix: Button-Style-Selector in Anhänge-Spalte beschränken
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Vorheriger Fix '.bericht-attachments button { width:100% }' hat auch
die kleinen Icons in den Anhang-Items (Mülleimer zum Löschen, Buttons
in Listen-Items) erwischt und auf volle Breite gepumpt. Dadurch wurde
der Dateiname nicht mehr sichtbar.

Fix: Selector auf .bericht-add-selected + .bericht-upload beschränken
(die beiden Action-Blöcke am Ende der Spalte). Die Item-Buttons
innerhalb der Liste behalten ihr Default-Styling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:38:16 +02:00
c98fcc829c fix: Anhänge-Buttons einheitlich + Bilder bei Zeichnen-Tools nicht mehr ziehbar
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Anhänge-Spalte:
- Buttons und Labels waren unterschiedlich breit → Scrollbalken querte
  die Box. Fix: alle Controls (.butAction, button, select) in
  .bericht-attachments auf display:block + width:100% + box-sizing
  border-box, einheitliches Padding, ellipsis bei Überlänge.
- overflow-x: hidden verhindert horizontale Scrollbar im Container.
- min-width: 0 damit grid-Container nicht überlaufen.

Zeichen-Tools:
- Problem: Beim Malen wurden die Bilder (fabric.Image) mitbewegt
  weil sie immer selectable/evented waren.
- Fix: applyTool() setzt auf ALLEN Canvas-Objekten selectable=false
  und evented=false sobald ein Zeichen-Tool aktiv ist. Nur bei
  'select' werden sie wieder aktiviert.
- discardActiveObject() nach Tool-Wechsel damit keine Selektion
  aktiv bleibt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:18:49 +02:00
efa94217eb fix: Fabric canvas-container absolut positioniert — weißer Bereich rechts weg
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Fabric.js wickelt #fabric-canvas in einen .canvas-container ein. Meine
bisherige CSS-Regel '#fabric-canvas.fabric-overlay position:absolute'
wirkte nur auf das innere Canvas, nicht auf den Container. Dadurch saß
der Container im normalen Flex-Flow neben dem pdfCanvas und verdrängte
es nach links — der rechte Bereich wurde vom Container als weiße Fläche
überlagert und verdeckte Bild-Inhalte.

Fix: neue CSS-Regel '.bericht-canvas-wrap .canvas-container position:
absolute !important' damit der Container wie vorgesehen absolut
positioniert wird und den pdfCanvas nicht aus dem Flow drückt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 14:11:09 +02:00
bf1ad1bafa fix: Layout-Selector für Mehrfach-Übernahme aus Anhänge-Liste
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Der Layout-Dropdown in der Toolbar bezog sich nur auf die aktuell
angezeigte Seite, nicht auf die Übernahme-Aktion. Ergebnis: User
stellten 2x2 ein, kreuzten 4 Bilder an, klickten 'Übernehmen' — und
bekamen 4 einzelne Seiten statt einer Grid-Seite.

Fix: Neben dem Übernahme-Button eigener Layout-Selector mit Optionen
Einzeln / 2 / 2v / 4 / 6 / Vorher-Nachher. Bei Grid-Layout werden
die ausgewählten Bilder automatisch in Gruppen à slotCount aufgeteilt
und entsprechend viele Seiten angelegt (z.B. 10 Bilder + grid_4 = 3
Seiten mit 4+4+2). Die einzelnen ▭▭/▦/VN-Buttons entfallen damit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:31:26 +02:00
f55da13234 fix: Empty-State-Hinweis nur wenn wirklich keine Seite existiert
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Vorher blieb 'Seite wählen oder Fotos hinzufügen' auch sichtbar wenn
eine Seite geladen wurde — die .empty-Klasse wurde nur beim Seiten-
wechsel per loadPage() entfernt, nicht beim initialen Load. Resultat:
bei einem Bericht mit bestehender Seite erschien der Text mitten im
weißen Canvas.

Fix: beim init() wird die Klasse nur gesetzt wenn überhaupt keine
Page-Thumbs existieren (Bericht wirklich leer).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:29:46 +02:00
195942a2f9 feat: Phase 6 — Client-WYSIWYG via Composite-PNG + Text-BG + Dark-Input-Fix
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Paradigmen-Wechsel: Editor rendert bei jedem Save sein Fabric-Canvas
als PNG und lädt es hoch. PDF nutzt dieses PNG 1:1 statt die Shapes
serverseitig nachzuzeichnen.

Damit ist garantiert: was du im Editor siehst, ist EXAKT das was im PDF
landet. Alle Pfeil/Text/Shape-Rendering-Bugs zwischen Fabric-JSON und
PHP-Nachzeichnung sind Geschichte.

Kernänderungen:

1. DB: Neue Spalte bericht_page.composite_path (Migration im init())
2. ajax/save_annotations.php: nimmt multipart file 'composite' entgegen,
   speichert es unter bericht/work/<fkb>/composite_<pid>.png
3. lib/bericht.lib.php: bericht_render_page_to_pdf prüft composite_path
   zuerst — wenn vorhanden, wird eine Seite mit genau diesem PNG als
   volles Bild gerendert, fertig. Fallback auf alte Logik bei alten
   Berichten ohne Composite.
4. editor.js renderImage: Quellbild wird NICHT mehr auf pdfCanvas
   gezeichnet, sondern als fabric.Image ins Fabric-Canvas geladen —
   ZIEHBAR, SKALIERBAR, ROTIERBAR wie jedes andere Objekt.
   Mehrere Bilder auf einer Seite kein Problem mehr.
5. editor.js savePageAnnotations: nach Shape-State wird toDataURL
   mit multiplier:2 aufgerufen, PNG-Blob hochgeladen zusammen mit
   fabric_json (für spätere Edits) und note.
6. editor.js loadPage: wenn fabric_json existiert, wird dieses
   clientseitig wieder eingeladen (inkl. eingebettete Bilder) — das
   Quell-Bild wird nicht mehr neu aus der Quelle geholt. Bei leerer
   Seite läuft der alte Render-Flow.

Phase 6 Bonus — Text mit Hintergrund:
- Neuer color-picker 'BG:' in der Toolbar + 'Ø'-Button (kein BG)
- Fabric IText bekommt textBackgroundColor + padding:6
- Bei selektiertem Text-Objekt wird BG live angewendet
- Dataset-Flag 'active' toggelt zwischen ein/aus

Dark-Input-Fix:
- Textarea in .bericht-page-note nutzte --inputbackgroundcolor
  (existiert in awl-dark nicht → Fallback #fff = weiße Fläche mit
  schwarzer Schrift auf Dark-Theme)
- Jetzt: --colorbackbody + --colortext + --colorboxbordertitle1
- Generischer Input-Style für alle Text-Eingaben in .bericht-editor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:26:57 +02:00
4dad496788 fix: Leerer Bericht-Editor zeigt DIN A4 Hochformat-Platzhalter
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Wenn noch keine Seite geladen ist, wirkte der Editor mit dem winzigen
1x1-Canvas sehr abgebastelt. Jetzt zeigt der Canvas-Wrap im leeren
Zustand eine echte A4-Hochformat-Fläche (max 900px, aspect-ratio 1:1.414)
mit Hinweistext 'Seite wählen oder Fotos hinzufügen'.

- .bericht-canvas-wrap.empty setzt aspect-ratio auf A4
- ::after Pseudo-Element als Hinweistext
- init() markiert den Wrap als 'empty'
- loadPage() entfernt die Klasse sobald ein Bild geladen wird
- min-height 400px damit auch ohne Content Platz da ist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 13:08:38 +02:00
d043dfaf46 fix: Bericht create() backwards-kompatibel gegen fehlende Migrations-Spalten
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Nach dem Deploy sind die neuen Spalten version/fk_bericht_parent
(Phase 5.3) erst da wenn das Modul einmal reaktiviert wurde.
Bis dahin knallte der INSERT mit 'Unknown column version' und man
konnte keine neuen Berichte anlegen.

Fix: create() prüft via SHOW COLUMNS welche optionalen Spalten
(page_format, page_orientation, is_template, template_label,
version, fk_bericht_parent) tatsächlich existieren und nimmt nur
die vorhandenen in das INSERT-Statement auf.

Damit laufen auch Systeme, bei denen die Migration noch nicht
durchgelaufen ist, ohne Fehler weiter. Nach der Reaktivierung
werden automatisch alle Spalten befüllt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:19:17 +02:00
c8f7d7d527 feat: Phase 5.9 Materialliste API + DB + 5.8 Vorbereitung
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Neue Tabelle llx_bericht_material (element_type, fk_element, label,
  qty, unit, note, fk_user_creat, datec) via Migration
- api/materials.php: GET list, POST anlegen, DELETE löschen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:18:29 +02:00
dbddee7791 feat: Block C — Editor-Polish, Vorher/Nachher, Versionierung, Batch-Modus
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Desktop-Editor Polish:
- Aktives Seiten-Thumbnail deutlich markiert (scale + shadow + blauer
  Label-Hintergrund)
- Titel-Feld pro Seite in der DB (llx_bericht_page.title)

Phase 5.2 Vorher/Nachher-Layout:
- Neuer layout-Typ 'before_after' in bericht_render_page_to_pdf
- Zwei Bilder nebeneinander, Labels 'Vorher' / 'Nachher', Titel oben
- 'VN'-Button in der Anhänge-Grid-Leiste
- create_grid_page akzeptiert before_after
- Layout-Dropdown hat before_after + title_only Option

Phase 5.2b Title-Only Seiten:
- Reine Titel/Zwischentitel-Seiten ohne Bild
- 32pt Titel zentriert, optional Notiz darunter
- bericht_render_page_to_pdf behandelt title_only separat

Phase 5.3 Bericht-Versionierung:
- Neue Spalten version + fk_bericht_parent in llx_bericht
- Bericht::duplicateAsNewVersion() kopiert alles inkl. Seiten
- '🔀 Neue Version'-Button im Editor-Footer
- Versions-Links in der Meta-Zeile (v1, v2, v3 …) mit Sprungmöglichkeit
- Alte Versionen bleiben unverändert erhalten

Phase 5.6 Batch-Modus:
- Neue Seite bericht_batch.php mit Filter (Datum von/bis, Suche)
- Checkbox-Liste aller finalisierten Berichte
- 'Ausgewählte als Sammel-PDF herunterladen' → FPDI merged alle
  final_pdf_path in ein neues PDF mit Inhaltsverzeichnis-Seite
- Link im Admin-Setup

api/pages.php:
- POST-Update akzeptiert jetzt auch title und layout zusätzlich zu
  note und rotation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 09:10:56 +02:00
1705744809 fix: Cronjob entfernt, Cleanup opportunistisch beim Token-Create
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Der Dolibarr-Cron-Scheduler ist auf Prod nicht aktiv, deshalb hing
der tägliche Cleanup-Job 'Expired Upload-Tokens bereinigen' auf
'Geplant' und GlobalNotify meldete ihn als hängenden Job.

Lösung: Cronjob komplett aus dem Modul entfernt. Das Cleanup läuft
jetzt opportunistisch bei jedem neuen Token-Insert:

BerichtUploadToken::create() führt vor dem INSERT ein
DELETE FROM llx_bericht_upload_token WHERE expires_at < NOW()
aus. Das ist minimal teurer (1 Query extra pro Token-Create,
~0ms bei leerer Tabelle), aber vollständig cron-unabhängig.

WICHTIG für Prod: Nach Deploy muss der bestehende Cron-Eintrag
manuell aus llx_cronjob gelöscht werden (oder das Modul einmal
deaktiviert + reaktiviert werden, dann wird er mit remove() bzw.
init() neu gesetzt — ohne Eintrag). GlobalNotify ist danach ruhig.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:58:39 +02:00
344f884a0f feat: Bericht-Vorlagen + Whisper-Transkription + Cron-Fix
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Bericht-Vorlagen (Phase 5.5):
- DB: is_template, template_label in llx_bericht
- Bericht::fetchAllTemplates() und createFromTemplate()
- fetchAllForElement() blendet Vorlagen aus
- ajax/save_as_template.php erzeugt Vorlage aus aktuellem Bericht
- Desktop-Editor: '📋 Als Vorlage' Button im Action-Bereich
- Bericht-Übersicht: Vorlagen-Dropdown beim + Neu Button
- api/templates.php: GET list + POST create_from_template

Schnell-Bericht (Phase 4.a/4.i):
- api/reports.php?action=create POST-Endpoint: Titel, Format,
  Orientation, ODT-Template, optional template_id
- api/odt_templates.php: Liste der Deckblatt-Vorlagen

Whisper-Transkription (Phase 5.7):
- api/transcribe.php: POST mit relpath, nutzt externen Whisper-
  HTTP-Endpoint (whisper.cpp server ODER OpenAI-kompatibel)
- Konfiguration im Admin: BERICHT_WHISPER_URL/MODE/API_KEY/LANG
- Sprache default 'de'

Cron-Fix:
- BerichtUploadToken::cleanupExpired() ist jetzt Instanz-Methode
  (Dolibarr ruft new Klasse($db) bei jobtype=method auf)
- Returnwert für Cron-Success/Failure
- Statische Variante als cleanupExpiredStatic() für direkte Aufrufe
- Damit läuft der tägliche Cron 'Expired Upload-Tokens bereinigen'
  nicht mehr hängend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:27:45 +02:00
f37445ac9c feat: Unterschriften-Verifikation + Seiten-Reorder API
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
ajax/verify_signature.php:
- Liest Metadaten aus .meta.json neben einer Unterschrift-Seite
- Rechnet SHA256 über die zum Signaturzeitpunkt existierenden Seiten
  (erste N laut meta.page_count_at_signing)
- Vergleicht mit dem gespeicherten Hash → verified true/false
- Liefert reason bei Fehlschlag (Seitenanzahl oder Inhalt geändert)

bericht_card.php:
- Seiten mit .meta.json kriegen 🔒-Badge + grünen Rand
- 🔍-Button neben dem Löschen-Button öffnet Verifikations-Modal

editor.js:
- showSignatureVerifyResult-Modal zeigt alle Metadaten,
  beide Hashes, GPS als OpenStreetMap-Link, IP, User, Zeit
- Grün/Rot je nach Verifikationsergebnis

api/pages.php:
- Neuer Action reorder: POST mit {order:[ids]} schreibt neue
  page_order-Werte als Transaktion

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:18:51 +02:00
5db126210c feat: Unterschriften-Härtung + Kundenkarten-API
All checks were successful
Deploy bericht / deploy (push) Successful in 2s
Unterschrift (api/pages.php?action=signature):
- Metadaten einbrennen: Überschrift, Bericht-Ref, Parent-Ref, Kunde,
  Datum/Zeit (Server), Unterzeichner-Name, Bestätigungstext, GPS
- SHA256 Hash-Verkettung über alle vorherigen Seiten + Bericht-Ref
  → nachträgliches Austauschen von Seiten fällt auf
- Composite-Canvas: Header mit Metadaten, Unterschrift mittig,
  Footer mit Hash + Server + Zeitstempel
- Metadaten-JSON neben der PNG (für spätere Verifikation)
- signer_name ist Pflicht, gps_lat/gps_lon optional
- Page-Note zeigt Unterzeichner + Zeit statt nur 'Unterschrift Kunde'

Kundenkarten-API (api/customers.php):
- GET ohne id = Liste mit Suche (q-Param), zeigt Stammdaten + Bericht-Count
- GET mit id = Detail: Stammdaten, letzten 50 Aufträge, 50 Rechnungen,
  100 Berichte über UNION aus commande/facture JOIN
- Nur echte Kunden (client IN (1,2,3)), keine reinen Lieferanten

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 08:06:21 +02:00
44a86fa63d feat: API-Endpoints für Phase 4 Block 1
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- api/pages.php:
  - DELETE (oder POST ?delete=1) — Seite aus Bericht entfernen
    (source_path wird nur gelöscht wenn unter bericht/work/, damit
     Anhänge von Auftrag/Rechnung nicht mit rausfliegen)
  - POST {note, rotation} — Meta einer Seite updaten
  - POST ?action=signature&bericht_id=X + multipart file= — PNG
    Unterschrift als neue Seite am Bericht anhängen mit
    note='Unterschrift Kunde'
- api/pdf.php — liefert finales PDF oder on-the-fly Preview mit
  JWT-Auth (damit PWA das PDF direkt im <iframe> anzeigen kann)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 00:54:56 +02:00
046b26665f fix: Foto-Upload hängt an neuestem Entwurf, nicht blind an [0]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Wenn mehrere Berichte zum Auftrag existieren, wird der erste
  gefundene ENTWURF (status=0) wiederverwendet
- Finalisierte Berichte werden NICHT mehr mit neuen Seiten überschrieben
- Optional ?bericht_id=X um gezielt in einen bestimmten Bericht hochzuladen
- Wenn alle Berichte final sind, wird automatisch ein neuer Entwurf angelegt

So kann die PWA mehrere Fotos hintereinander in denselben Bericht
packen, statt pro Upload einen neuen anzulegen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 00:44:28 +02:00
bcf48ccddc feat: Phase 4 API — Bericht-Liste, Finalize, Photo-Delete, Voice-Upload
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- reports.php: GET ohne id listet alle Berichte des Users
  (Multi-User-Filter über fk_user_creat + Parent fk_user_*),
  mit parent_ref, page_count, status
- reports.php action=finalize: generiert jetzt wirklich das PDF
  (TCPDF+FPDI + bericht_render_page_to_pdf), schreibt ECM-Eintrag,
  setzt Status auf Final
- api/delete_photo.php: JWT-Version von delete_attachment
- api/voice.php: Audio-Upload pro Auftrag (webm/mp4/mp3/ogg)
  in das Auftrags-Anhang-Verzeichnis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-09 00:31:38 +02:00
6ae5babc46 fix: photo.php liest Authorization-Header robuster (Apache-kompatibel)
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Manche Apache-Setups (Prod!) leiten den Authorization-Header nicht
als HTTP_AUTHORIZATION in $_SERVER weiter. Jetzt wird zusätzlich
REDIRECT_HTTP_AUTHORIZATION und apache_request_headers() geprüft.

Fallback: ?jwt=<token> als Query-Param akzeptieren (wird von der
PWA jetzt standardmäßig mitgesendet für <img>-kompatible URLs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 23:27:26 +02:00
606ffae1fe fix: photo.php eigener Init ohne _inc.php JSON-Header
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
_inc.php setzt standardmäßig Content-Type: application/json — das
hat photo.php beim Bildversand blockiert (die Blob kam als JSON rein
und der Browser konnte sie nicht als Bild darstellen).

Jetzt lädt photo.php Dolibarr direkt, dekodiert JWT manuell (mit
Query-Param-Support für Fallback ohne Header-Auth) und sendet NUR
image/jpeg-Header beim Ausliefern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 23:25:24 +02:00
95701ed2d6 feat: api/photo.php — Foto-Auslieferung mit JWT-Auth für PWA
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Neuer Endpoint liefert Dateien aus DOL_DATA_ROOT aus, geschützt per
JWT statt Dolibarr-Session. Whitelist auf facture/commande/propal/
bericht. Optional size=small/mini für Thumbs (Dolibarr _small Variante).
CORS-Header damit PWA direkt zugreifen kann.

Die PWA kann damit Anhang-Bilder in der Auftrags-Detail-Ansicht
rendern — vorher nutzte sie document.php was nur mit Session ging
und deshalb im Standalone-Mode leer blieb.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 23:18:08 +02:00
3069453823 fix: PWA-QR-Bereich Dark-Theme — dunkler Frame, nur QR-Code selbst weiß [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:13:42 +02:00
42d3190974 fix: QR-Code-Button blau statt weiß im Admin-Setup [deploy]
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:04:18 +02:00
43ac766af2 feat: Admin-Setup zeigt PWA-Link + QR-Code + REST-API-Übersicht
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Im Bericht-Modul-Admin oben prominent:
- Großer 'PWA öffnen'-Button mit Direktlink auf /custom/baustelle/
- 'QR-Code anzeigen'-Button mit qrcodejs (Inline-Render auf Klick)
- Code-Block mit der vollen URL zum Kopieren
- Sektion 'REST-API Status' listet alle Endpoints für die Doku

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:58:26 +02:00
bed611cd8b feat: Phase 2.3 + 2.4 — REST-API mit JWT-Auth
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- api/_jwt.php: HS256 JWT encode/decode/from_request, Secret aus
  dolibarr_main_instance_unique_id, 7 Tage TTL
- api/_inc.php: gemeinsamer API-Init mit CORS, JSON-Helpers,
  api_authenticate() lädt User aus JWT und prüft bericht/read
- api/auth.php: POST { login, password } → JWT mit user + perms
- api/orders.php:
  - GET /api/orders.php — Liste der Aufträge des Users (Multi-User
    Filter über fk_user_*, Admin sieht alle)
  - GET /api/orders.php?id=X — Auftrags-Detail mit Kunde + Berichten
  - GET /api/orders.php?id=X&action=photos — Anhänge
  - POST /api/orders.php?id=X&action=upload_photo — Foto hochladen,
    Bericht wird automatisch angelegt falls nicht vorhanden
- api/reports.php:
  - GET /api/reports.php?id=X — Bericht-Detail + Seiten
  - POST /api/reports.php?id=X&action=finalize — Status auf final

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:40:52 +02:00
3d84f7e0be feat: Phase 2.1 + 2.2 — Mobile-Upload mit QR-Code
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Phase 2.1 Token-System:
- Neue Tabelle llx_bericht_upload_token (token, fk_bericht, expires_at,
  uploads_count, max_uploads)
- BerichtUploadToken-Klasse mit create/fetchValid/incrementCount/cleanupExpired
- Cronjob 'Bericht: Expired Upload-Tokens bereinigen' täglich
- 64-Hex random_bytes-Tokens, 1h Lifetime, 100 Uploads max

Phase 2.2 QR-Upload Lite:
- mobile_upload.php — Mobile-optimierte Page ohne Dolibarr-Login,
  Auth nur über Token in URL/Form
- 📷 Foto aufnehmen (capture=environment) und 📂 Galerie
- Clientseitiges Resize auf max 2000px (Canvas, JPEG q=0.85)
- Upload-Status mit Toast-Notifications
- Liste der hochgeladenen Bilder live in der Page
- ajax/create_upload_token.php — generiert Token für aktiven Bericht
- ajax/list_pages.php — Polling-Endpoint für Editor
- 📱 Mobil hochladen-Button im Editor → QR-Modal mit qrcodejs
- Polling alle 5s nach neuen Pages, auto-reload bei Änderung
- QR-Modal styled für Dark-Theme

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:38:33 +02:00
06cd70d4a3 feat: Phase 1.4 + 1.5 — Multi-Image Grids und Bildgröße komplett
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
DB-Klasse:
- BerichtPage::getImages() liest llx_bericht_page_image
- BerichtPage::setSlotImage() / clearImages()
- BerichtPage::slotCountForLayout()
- BerichtPage::slotRects() berechnet Slot-Positionen für 1/2/2v/4/6
- create()/update()/fetchAllForBericht() inkludieren layout/scale/align

Endpoints (alle im Bericht-Modul):
- ajax/save_page_options.php — speichert layout, image_scale, image_align
- ajax/create_grid_page.php — erstellt Multi-Image-Seite mit gewähltem Layout
- ajax/set_slot_image.php — setzt einzelnes Bild eines Slots
- ajax/page_meta.php liefert layout/scale/align mit
- ajax/page_image.php rendert Composite-PNG (GD) für Multi-Image-Seiten

UI:
- Layout-Dropdown in 3. Toolbar-Zeile (Single/Grid 2/2v/4/6)
- Bildgröße-Dropdown (100/70/50/30%) — single-only
- Position-Dropdown (Anpassen/Zentriert/Ecken) — single-only
- 'Als Grid hinzufügen'-Buttons in der Anhänge-Liste (▭▭ ▯▯ ▦ ▦▦)
- Auto-Sync der single-only-Felder beim Layout-Wechsel

Rendering:
- bericht_render_page_to_pdf() in lib/bericht.lib.php — zentrale
  Render-Funktion für Single + Grid + PDF-Quelle + image_scale + align
- bericht_align_position() für die 6 Align-Modi
- generate_pdf + preview_pdf nutzen die gemeinsame Funktion (DRY)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:33:44 +02:00
0fbfb1bf27 feat: Phase 1.3 + 1.7 + Schema 1.4/1.5 — Format/Orient + Kunden-Tab
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Phase 1.3 Seitenformat A4/A3/A5/Letter + Hoch/Quer:
- Neue Spalten page_format, page_orientation in llx_bericht
- Bericht-Meta zeigt Format + Orientation Selects
- Auto-Save via neuem ajax/save_meta.php
- generate_pdf + preview_pdf nutzen die gewählten Werte
- Bilder werden dynamisch via getPageWidth/getPageHeight skaliert
  (statt hardcoded 210x297 für A4)

Phase 1.4 + 1.5 Schema-Vorbereitung:
- Neue Tabelle llx_bericht_page_image für Multi-Image-Seiten
- Spalten layout, image_scale, image_align in llx_bericht_page
- DB-Migrationen im init() für bestehende Installationen
  (ALTER TABLE mit Error-Suppress)
- Grid-Rendering im Editor/PDF folgt im nächsten Commit
  (siehe CLAUDE.md TODO)

Phase 1.7 Tab "Berichte" auf Kundenkarte:
- Neue Konstante BERICHT_TAB_ON_THIRDPARTY (default 1)
- Tab-Definition in modBericht für 'thirdparty' Element
- Neue Datei bericht_thirdparty.php
- UNION-SQL über bericht JOIN commande/facture/propal mit fk_soc
- Read-only flache Tabelle sortiert nach Datum
- Pro Bericht: Quelle (Symbol + Ref-Link), Status, Öffnen/Zur Quelle

Version-Bump 1.0.0 → 1.1.0, ChangeLog ergänzt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:20:09 +02:00
a7bf3929a4 feat: Phase 1.6 + 1.1 + 1.2 — verknüpfte Sicht, PDF-Vorschau, Anhänge löschen
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
Phase 1.6 Verknüpfte Sicht Auftrag↔Rechnung:
- Bericht::fetchLinkedForElement liest llx_element_element-Verknüpfungen
- linkToElement/unlinkFromElement n:m-API
- Bericht-Übersicht zeigt drei Sektionen: direkt zugeordnet,
  zusätzlich verknüpft, aus verknüpften Aufträgen (read-only)
- 'Übernehmen'-Button erstellt llx_element_element-Eintrag
- 'Lösen'-Button entfernt Verknüpfung
- generate_pdf legt das fertige PDF auch unter den verknüpften
  Elementen ab + ECM-Eintrag

Phase 1.1 Live-PDF-Vorschau:
- Neuer Endpoint ajax/preview_pdf.php — wie generate_pdf, aber:
  schreibt nicht in ECM, ändert nicht den Status, streamt direkt
- 👁️ Vorschau-Button im Editor öffnet Modal mit iframe (PDF.js
  Viewer des Browsers)
- bericht_burn_annotations und bericht_render_cover_internal in
  lib/bericht.lib.php verschoben (gemeinsam genutzt)
- ESC-Key + Backdrop-Click schließen das Modal

Phase 1.2 Anhänge löschen:
- Neuer Endpoint ajax/delete_attachment.php mit Path-Whitelist
  (nur facture/, commande/, propal/), löscht Datei + thumbs +
  llx_ecm_files-Eintrag
- 🗑️-Button in jeder Anhang-Zeile, Confirm-Dialog mit
  Quell-Auftrag/Rechnung im Text
- Inline-Remove ohne Page-Reload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 22:13:46 +02:00
a7a533f3b8 feat: Toolbar-Einstellungen merken (localStorage) + Tooltips überall
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Farbe, Strichstärke, Schriftart, Schriftgröße, Bold/Italic und Zoom
  werden in localStorage unter bericht.editor.settings.v1 gespeichert
- Beim nächsten Öffnen werden alle Werte wiederhergestellt
- Alle Toolbar-Buttons und Inputs haben jetzt deutsche Tooltips
  (Farbe, Strichstärke, Schriftart, Größe, Fett, Kursiv, Zoom -/+/Reset,
   Rotation links/rechts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 16:35:08 +02:00
a6cb8ade44 ui: Toolbar auf zwei Zeilen, einheitliche Element-Höhe 30px
All checks were successful
Deploy bericht / deploy (push) Successful in 1s
- Zeile 1: Tools (Select/Draw/Rect/Kreis/Pfeil/Text), Farbe, Strich,
  Undo/Redo, Löschen
- Zeile 2: Schrift (Family/Size/Bold/Italic), Zoom, Rotation
- .row-break als flex-basis:100% Element für sauberen Umbruch
- Alle Buttons + Inputs (color, range, number, select) auf height:30px
- Doppelte undo/redo/delete-Buttons entfernt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[deploy]
2026-04-08 16:32:55 +02:00