From a8b6e5d9c4b0398c7606629c0f25345128bf301f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Sun, 14 Dec 2025 22:14:30 +0100 Subject: [PATCH] qr code layouts im mobile admin perfektioniert. --- package-lock.json | 11 + package.json | 3 +- .../js/admin/i18n/locales/de/management.json | 8 + .../js/admin/i18n/locales/en/management.json | 8 + .../js/admin/mobile/QrLayoutCustomizePage.tsx | 529 +++++++++++------- resources/js/admin/mobile/QrPrintPage.tsx | 32 +- .../mobile/invite-layout/DesignerCanvas.tsx | 8 +- 7 files changed, 369 insertions(+), 230 deletions(-) diff --git a/package-lock.json b/package-lock.json index bce8a07..3fec733 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "lucide-react": "^0.475.0", "pdf-lib": "^1.17.1", "react": "^19.2.1", + "react-colorful": "^5.6.1", "react-dom": "^19.2.1", "react-hot-toast": "^2.6.0", "react-i18next": "^16.4.1", @@ -16276,6 +16277,16 @@ "node": ">=0.10.0" } }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-devtools-core": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", diff --git a/package.json b/package.json index 035b5e8..8a44409 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,8 @@ "i18next-http-backend": "^3.0.2", "laravel-vite-plugin": "^2.0.1", "lucide-react": "^0.475.0", - "pdf-lib": "^1.17.1", + "pdf-lib": "^1.17.1", + "react-colorful": "^5.6.1", "react": "^19.2.1", "react-dom": "^19.2.1", "react-hot-toast": "^2.6.0", diff --git a/resources/js/admin/i18n/locales/de/management.json b/resources/js/admin/i18n/locales/de/management.json index bfcf8b3..d2f9972 100644 --- a/resources/js/admin/i18n/locales/de/management.json +++ b/resources/js/admin/i18n/locales/de/management.json @@ -1847,6 +1847,7 @@ "title": "QR-Code & Druck-Layouts", "heroTitle": "Einlass-QR-Code", "description": "Scannen, um zur Gäste-App zu gelangen.", + "bottomNote": "Unterer Hinweistext", "missing": "Kein QR-Link vorhanden", "download": "Download", "downloadStarted": "Download gestartet", @@ -1859,6 +1860,13 @@ "createLink": "Neuen QR-Link erstellen", "created": "Neuer QR-Link erstellt", "createFailed": "Link konnte nicht erstellt werden.", + "headline": "Headline", + "subtitle": "Untertitel", + "align": "Ausrichtung", + "lineHeight": "Zeilenhöhe", + "fontFamily": "Schriftfamilie", + "exportPdf": "PDF exportieren", + "exportPng": "PNG exportieren", "format": { "poster": "A4 Poster", "posterSubtitle": "Hochformat für Aushänge", diff --git a/resources/js/admin/i18n/locales/en/management.json b/resources/js/admin/i18n/locales/en/management.json index f837ab6..bdf7f2a 100644 --- a/resources/js/admin/i18n/locales/en/management.json +++ b/resources/js/admin/i18n/locales/en/management.json @@ -1868,6 +1868,7 @@ "title": "QR Code & Print Layouts", "heroTitle": "Entrance QR Code", "description": "Scan to access the event guest app.", + "bottomNote": "Bottom note text", "missing": "No QR link available", "download": "Download", "downloadStarted": "Download started", @@ -1880,6 +1881,13 @@ "createLink": "Create new QR link", "created": "New QR link created", "createFailed": "Could not create link.", + "headline": "Headline", + "subtitle": "Subtitle", + "align": "Align", + "lineHeight": "Line height", + "fontFamily": "Font family", + "exportPdf": "Export PDF", + "exportPng": "Export PNG", "format": { "poster": "A4 Poster", "posterSubtitle": "Portrait for posters", diff --git a/resources/js/admin/mobile/QrLayoutCustomizePage.tsx b/resources/js/admin/mobile/QrLayoutCustomizePage.tsx index d68f9cf..c37e043 100644 --- a/resources/js/admin/mobile/QrLayoutCustomizePage.tsx +++ b/resources/js/admin/mobile/QrLayoutCustomizePage.tsx @@ -1,11 +1,14 @@ import React from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { ArrowLeft, RefreshCcw, Plus } from 'lucide-react'; +import { ArrowLeft, RefreshCcw, Plus, ChevronDown } from 'lucide-react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Pressable } from '@tamagui/react-native-web-lite'; import { Input, TextArea } from 'tamagui'; +import { Accordion } from '@tamagui/accordion'; +import { HexColorPicker } from 'react-colorful'; +import { Portal } from '@tamagui/portal'; import { MobileShell } from './components/MobileShell'; import { MobileCard, CTAButton, PillBadge } from './components/Primitives'; import { @@ -394,11 +397,11 @@ function renderEventName(name: TenantEvent['name'] | null | undefined): string | function getDefaultSlots(): Record { return { - headline: { x: 0.08, y: 0.15, w: 0.84, fontSize: 50, fontWeight: 800, fontFamily: 'Playfair Display', align: 'center' }, + headline: { x: 0.08, y: 0.12, w: 0.84, fontSize: 90, fontWeight: 800, fontFamily: 'Playfair Display', align: 'center' }, subtitle: { x: 0.1, y: 0.21, w: 0.8, fontSize: 37, fontWeight: 600, fontFamily: 'Montserrat', align: 'center' }, - description: { x: 0.1, y: 0.66, w: 0.8, fontSize: 13, fontFamily: 'Lora', lineHeight: 1.4, align: 'center' }, - instructions: { x: 0.1, y: 0.34, w: 0.8, fontSize: 14, fontFamily: 'Lora', lineHeight: 1.35 }, - qr: { x: 0.3, y: 0.3, w: 0.28 }, + description: { x: 0.1, y: 0.66, w: 0.8, fontSize: 26, fontFamily: 'Lora', lineHeight: 1.4, align: 'center' }, + instructions: { x: 0.1, y: 0.7, w: 0.8, fontSize: 24, fontFamily: 'Lora', lineHeight: 1.5 }, + qr: { x: 0.39, y: 0.37, w: 0.27 }, }; } @@ -521,26 +524,28 @@ function buildFabricOptions({ const widthPx = localWidth * scaleX; const heightPx = localHeight * scaleY; - const baseCenterX = opts.panelOffset + slot.x * panelWidth + localWidth / 2; - const baseCenterY = slot.y * panelHeight + localHeight / 2; - let targetCenterX = baseCenterX; - let targetCenterY = baseCenterY; + // Default: top-left positioning for non-rotated panels + let targetX = opts.panelOffset + slot.x * panelWidth; + let targetY = slot.y * panelHeight; let rotation = 0; + // Foldable layouts rotate each panel around its own center; Fabric expects center-based coords when rotated. if (isFoldable) { + const baseCenterX = opts.panelOffset + slot.x * panelWidth + localWidth / 2; + const baseCenterY = slot.y * panelHeight + localHeight / 2; const panelCenterX = opts.panelOffset + panelWidth / 2; const panelCenterY = panelHeight / 2; rotation = opts.panelOffset === 0 ? 90 : -90; const rotated = rotatePoint(panelCenterX, panelCenterY, baseCenterX, baseCenterY, rotation); - targetCenterX = rotated.x; - targetCenterY = rotated.y; + targetX = rotated.x; + targetY = rotated.y; } elements.push({ id: `${type}-${opts.panelOffset}-${opts.mirrored ? 'm' : 'n'}`, type, - x: targetCenterX * scaleX, - y: targetCenterY * scaleY, + x: targetX * scaleX, + y: targetY * scaleY, width: widthPx, height: heightPx, fontSize: slot.fontSize ?? (type === 'headline' ? 30 : type === 'subtitle' ? 18 : 16), @@ -557,26 +562,26 @@ function buildFabricOptions({ if (!qrUrl) return; const localSize = slot.w * panelWidth; const sizePx = localSize * scaleX; - const baseCenterX = opts.panelOffset + slot.x * panelWidth + localSize / 2; - const baseCenterY = slot.y * panelHeight + localSize / 2; - let targetCenterX = baseCenterX; - let targetCenterY = baseCenterY; + let targetX = opts.panelOffset + slot.x * panelWidth; + let targetY = slot.y * panelHeight; let rotation = 0; if (isFoldable) { + const baseCenterX = opts.panelOffset + slot.x * panelWidth + localSize / 2; + const baseCenterY = slot.y * panelHeight + localSize / 2; const panelCenterX = opts.panelOffset + panelWidth / 2; const panelCenterY = panelHeight / 2; rotation = opts.panelOffset === 0 ? 90 : -90; const rotated = rotatePoint(panelCenterX, panelCenterY, baseCenterX, baseCenterY, rotation); - targetCenterX = rotated.x; - targetCenterY = rotated.y; + targetX = rotated.x; + targetY = rotated.y; } elements.push({ id: `qr-${opts.panelOffset}-${opts.mirrored ? 'm' : 'n'}`, type: 'qr', - x: targetCenterX * scaleX, - y: targetCenterY * scaleY, + x: targetX * scaleX, + y: targetY * scaleY, width: sizePx, height: sizePx, rotation, @@ -858,7 +863,7 @@ function TextStep({ size="$4" />