QR-Codes-UI zu Einladungen umgebaut mit PDF-Export und Druckanzeige + Customizer
This commit is contained in:
@@ -5,19 +5,18 @@ namespace App\Filament\Resources;
|
||||
use App\Filament\Resources\EventResource\Pages;
|
||||
use App\Filament\Resources\EventResource\RelationManagers\EventPackagesRelationManager;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventJoinTokenEvent;
|
||||
use App\Models\EventType;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\EventJoinTokenEvent;
|
||||
use App\Support\JoinTokenLayoutRegistry;
|
||||
use Carbon\Carbon;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
@@ -116,7 +115,7 @@ class EventResource extends Resource
|
||||
->getStateUsing(function ($record) {
|
||||
$token = $record->joinTokens()->latest()->first();
|
||||
|
||||
return $token ? url('/e/' . $token->token) : __('admin.events.table.no_join_tokens');
|
||||
return $token ? url('/e/'.$token->token) : __('admin.events.table.no_join_tokens');
|
||||
})
|
||||
->description(function ($record) {
|
||||
$total = $record->joinTokens()->count();
|
||||
@@ -185,7 +184,7 @@ class EventResource extends Resource
|
||||
|
||||
$tokens = $tokens->map(function ($token) use ($record, $totals, $recent24h, $lastSeen) {
|
||||
$layouts = JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($record, $token) {
|
||||
return route('tenant.events.join-tokens.layouts.download', [
|
||||
return route('api.v1.tenant.events.join-tokens.layouts.download', [
|
||||
'event' => $record->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
'layout' => $layoutId,
|
||||
@@ -212,7 +211,7 @@ class EventResource extends Resource
|
||||
'id' => $token->id,
|
||||
'label' => $token->label,
|
||||
'token' => $token->token,
|
||||
'url' => url('/e/' . $token->token),
|
||||
'url' => url('/e/'.$token->token),
|
||||
'usage_limit' => $token->usage_limit,
|
||||
'usage_count' => $token->usage_count,
|
||||
'expires_at' => optional($token->expires_at)->toIso8601String(),
|
||||
@@ -220,7 +219,7 @@ class EventResource extends Resource
|
||||
'is_active' => $token->isActive(),
|
||||
'created_at' => optional($token->created_at)->toIso8601String(),
|
||||
'layouts' => $layouts,
|
||||
'layouts_url' => route('tenant.events.join-tokens.layouts.index', [
|
||||
'layouts_url' => route('api.v1.tenant.events.join-tokens.layouts.index', [
|
||||
'event' => $record->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
]),
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Tenant\Pages;
|
||||
|
||||
use App\Models\Event;
|
||||
use App\Models\EventJoinToken;
|
||||
use App\Services\EventJoinTokenService;
|
||||
use App\Support\JoinTokenLayoutRegistry;
|
||||
use App\Support\TenantOnboardingState;
|
||||
use BackedEnum;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use BackedEnum;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
@@ -91,7 +92,7 @@ class InviteStudio extends Page
|
||||
return;
|
||||
}
|
||||
|
||||
$label = $this->tokenLabel ?: 'Einladung ' . now()->format('d.m.');
|
||||
$label = $this->tokenLabel ?: 'Einladung '.now()->format('d.m.');
|
||||
|
||||
$layoutPreference = Arr::get($tenant->settings ?? [], 'branding.preferred_invite_layout');
|
||||
|
||||
@@ -141,7 +142,7 @@ class InviteStudio extends Page
|
||||
protected function mapToken(Event $event, EventJoinToken $token): array
|
||||
{
|
||||
$downloadUrls = JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($event, $token) {
|
||||
return route('tenant.events.join-tokens.layouts.download', [
|
||||
return route('api.v1.tenant.events.join-tokens.layouts.download', [
|
||||
'event' => $event->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
'layout' => $layoutId,
|
||||
@@ -152,7 +153,7 @@ class InviteStudio extends Page
|
||||
return [
|
||||
'id' => $token->getKey(),
|
||||
'label' => $token->label ?? 'Einladungslink',
|
||||
'url' => URL::to('/e/' . $token->token),
|
||||
'url' => URL::to('/e/'.$token->token),
|
||||
'created_at' => optional($token->created_at)->format('d.m.Y H:i'),
|
||||
'usage_count' => $token->usage_count,
|
||||
'usage_limit' => $token->usage_limit,
|
||||
|
||||
@@ -10,16 +10,16 @@ use App\Services\EventJoinTokenService;
|
||||
use App\Services\Tenant\TaskCollectionImportService;
|
||||
use App\Support\JoinTokenLayoutRegistry;
|
||||
use App\Support\TenantOnboardingState;
|
||||
use BackedEnum;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use BackedEnum;
|
||||
use UnitEnum;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Throwable;
|
||||
use UnitEnum;
|
||||
|
||||
class TenantOnboarding extends Page
|
||||
{
|
||||
@@ -234,7 +234,7 @@ class TenantOnboarding extends Page
|
||||
protected function buildInviteDownloads(Event $event, $token): array
|
||||
{
|
||||
return JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($event, $token) {
|
||||
return route('tenant.events.join-tokens.layouts.download', [
|
||||
return route('api.v1.tenant.events.join-tokens.layouts.download', [
|
||||
'event' => $event->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
'layout' => $layoutId,
|
||||
@@ -260,7 +260,7 @@ class TenantOnboarding extends Page
|
||||
public function getEventTypeOptionsProperty(): array
|
||||
{
|
||||
return EventType::query()
|
||||
->orderBy('name->' . app()->getLocale())
|
||||
->orderBy('name->'.app()->getLocale())
|
||||
->get()
|
||||
->mapWithKeys(function (EventType $type) {
|
||||
$name = $type->name[app()->getLocale()] ?? $type->name['de'] ?? Arr::first($type->name);
|
||||
@@ -306,6 +306,6 @@ class TenantOnboarding extends Page
|
||||
|
||||
protected function getDefaultEventTypeId(): ?int
|
||||
{
|
||||
return EventType::query()->orderBy('name->' . app()->getLocale())->value('id');
|
||||
return EventType::query()->orderBy('name->'.app()->getLocale())->value('id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,42 +3,42 @@
|
||||
namespace App\Filament\Tenant\Resources;
|
||||
|
||||
use App\Filament\Tenant\Resources\EventResource\Pages;
|
||||
use App\Filament\Tenant\Resources\EventResource\RelationManagers\EventPackagesRelationManager;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventJoinTokenEvent;
|
||||
use App\Models\EventType;
|
||||
use App\Support\JoinTokenLayoutRegistry;
|
||||
use App\Support\TenantOnboardingState;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventType;
|
||||
use App\Models\EventJoinTokenEvent;
|
||||
use BackedEnum;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use UnitEnum;
|
||||
use BackedEnum;
|
||||
|
||||
use App\Filament\Tenant\Resources\EventResource\RelationManagers\EventPackagesRelationManager;
|
||||
|
||||
class EventResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Event::class;
|
||||
|
||||
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-calendar';
|
||||
|
||||
protected static UnitEnum|string|null $navigationGroup = null;
|
||||
|
||||
public static function getNavigationGroup(): UnitEnum|string|null
|
||||
{
|
||||
return __('admin.nav.platform');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 20;
|
||||
|
||||
public static function shouldRegisterNavigation(): bool
|
||||
@@ -141,7 +141,7 @@ class EventResource extends Resource
|
||||
Actions\Action::make('toggle')
|
||||
->label(__('admin.events.actions.toggle_active'))
|
||||
->icon('heroicon-o-power')
|
||||
->action(fn($record) => $record->update(['is_active' => !$record->is_active])),
|
||||
->action(fn ($record) => $record->update(['is_active' => ! $record->is_active])),
|
||||
Actions\Action::make('join_tokens')
|
||||
->label(__('admin.events.actions.join_link_qr'))
|
||||
->icon('heroicon-o-qr-code')
|
||||
@@ -185,7 +185,7 @@ class EventResource extends Resource
|
||||
|
||||
$tokens = $tokens->map(function ($token) use ($record, $totals, $recent24h, $lastSeen) {
|
||||
$layouts = JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($record, $token) {
|
||||
return route('tenant.events.join-tokens.layouts.download', [
|
||||
return route('api.v1.tenant.events.join-tokens.layouts.download', [
|
||||
'event' => $record->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
'layout' => $layoutId,
|
||||
@@ -220,7 +220,7 @@ class EventResource extends Resource
|
||||
'is_active' => $token->isActive(),
|
||||
'created_at' => optional($token->created_at)->toIso8601String(),
|
||||
'layouts' => $layouts,
|
||||
'layouts_url' => route('tenant.events.join-tokens.layouts.index', [
|
||||
'layouts_url' => route('api.v1.tenant.events.join-tokens.layouts.index', [
|
||||
'event' => $record->slug,
|
||||
'joinToken' => $token->getKey(),
|
||||
]),
|
||||
|
||||
@@ -19,7 +19,7 @@ class EventJoinTokenLayoutController extends Controller
|
||||
$this->ensureBelongsToEvent($event, $joinToken);
|
||||
|
||||
$layouts = JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($event, $joinToken) {
|
||||
return route('tenant.events.join-tokens.layouts.download', [
|
||||
return route('api.v1.tenant.events.join-tokens.layouts.download', [
|
||||
'event' => $event,
|
||||
'joinToken' => $joinToken,
|
||||
'layout' => $layoutId,
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Support\JoinTokenLayoutRegistry;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class EventJoinTokenResource extends JsonResource
|
||||
{
|
||||
@@ -40,13 +41,34 @@ class EventJoinTokenResource extends JsonResource
|
||||
}
|
||||
|
||||
$plainToken = $this->resource->plain_token ?? $this->token;
|
||||
$qrCodeUrl = $plainToken ? url('/e/'.$plainToken) : null;
|
||||
$qrCodeDataUrl = null;
|
||||
|
||||
if ($qrCodeUrl) {
|
||||
try {
|
||||
$svg = QrCode::format('svg')
|
||||
->size(360)
|
||||
->margin(1)
|
||||
->errorCorrection('M')
|
||||
->generate($qrCodeUrl);
|
||||
|
||||
$svgString = (string) $svg;
|
||||
|
||||
if ($svgString !== '') {
|
||||
$qrCodeDataUrl = 'data:image/svg+xml;base64,'.base64_encode($svgString);
|
||||
}
|
||||
} catch (\Throwable $exception) {
|
||||
report($exception);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'label' => $this->label,
|
||||
'token' => $plainToken,
|
||||
'token_preview' => $this->token_preview,
|
||||
'url' => $plainToken ? url('/e/'.$plainToken) : null,
|
||||
'url' => $qrCodeUrl,
|
||||
'qr_code_data_url' => $qrCodeDataUrl,
|
||||
'usage_limit' => $this->usage_limit,
|
||||
'usage_count' => $this->usage_count,
|
||||
'expires_at' => optional($this->expires_at)->toIso8601String(),
|
||||
|
||||
@@ -24,7 +24,7 @@ class JoinTokenLayoutRegistry
|
||||
'accent' => '#6366F1',
|
||||
'secondary' => '#CBD5F5',
|
||||
'badge' => '#0EA5E9',
|
||||
'qr' => ['size_px' => 340],
|
||||
'qr' => ['size_px' => 500],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Scanne den Code und tritt dem Event direkt bei.',
|
||||
@@ -44,7 +44,7 @@ class JoinTokenLayoutRegistry
|
||||
'accent' => '#C08457',
|
||||
'secondary' => '#E6D5C3',
|
||||
'badge' => '#8B5CF6',
|
||||
'qr' => ['size_px' => 300],
|
||||
'qr' => ['size_px' => 460],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'QR-Code scannen oder Link im Browser eingeben.',
|
||||
@@ -68,7 +68,7 @@ class JoinTokenLayoutRegistry
|
||||
'accent' => '#FFFFFF',
|
||||
'secondary' => 'rgba(255,255,255,0.72)',
|
||||
'badge' => '#1E293B',
|
||||
'qr' => ['size_px' => 360],
|
||||
'qr' => ['size_px' => 540],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Sofort scannen – der QR-Code führt direkt zum Event.',
|
||||
@@ -88,7 +88,7 @@ class JoinTokenLayoutRegistry
|
||||
'accent' => '#0EA5E9',
|
||||
'secondary' => '#94A3B8',
|
||||
'badge' => '#334155',
|
||||
'qr' => ['size_px' => 320],
|
||||
'qr' => ['size_px' => 500],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Schritt 1: QR-Code scannen oder Kurzlink nutzen.',
|
||||
@@ -108,7 +108,7 @@ class JoinTokenLayoutRegistry
|
||||
'accent' => '#9333EA',
|
||||
'secondary' => '#E0E7FF',
|
||||
'badge' => '#64748B',
|
||||
'qr' => ['size_px' => 280],
|
||||
'qr' => ['size_px' => 440],
|
||||
'svg' => ['width' => 1080, 'height' => 1520],
|
||||
'instructions' => [
|
||||
'Code scannen, Profil erstellen, Erinnerungen festhalten.',
|
||||
@@ -255,6 +255,7 @@ class JoinTokenLayoutRegistry
|
||||
'background_gradient' => $layout['background_gradient'],
|
||||
'accent' => $layout['accent'],
|
||||
'text' => $layout['text'],
|
||||
'qr_size_px' => $layout['qr']['size_px'] ?? null,
|
||||
],
|
||||
'formats' => $formats,
|
||||
'download_urls' => collect($formats)
|
||||
|
||||
Reference in New Issue
Block a user