Compare commits

...

5 commits

Author SHA1 Message Date
Anna-Sara Sélea
0b1a3ae96f New features with Mail and SMS 2026-05-21 18:40:00 +02:00
Anna-Sara Sélea
ee718a3a7d Possibility to set Mailtemplates to draft 2026-05-18 06:50:12 +02:00
Anna-Sara Sélea
c5bfb28eb8 Update to tailwind 4 2026-05-17 19:23:14 +02:00
Anna-Sara Sélea
3821073132 Update to filament 5 2026-05-17 19:13:15 +02:00
Anna-Sara Sélea
5c773a684c Added SSN to participant table and added toggles for paid and memeber in particpant table 2026-05-17 19:01:22 +02:00
67 changed files with 2152 additions and 3844 deletions

View file

@ -1,10 +1,11 @@
FROM php:8.2-apache-buster FROM php:8.2-apache-bookworm
RUN usermod -u 1000 www-data RUN usermod -u 1000 www-data
RUN a2enmod rewrite RUN a2enmod rewrite
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y gnupg2 zlib1g-dev libzip-dev zlib1g-dev libpng-dev libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libxml2-dev && apt-get install -y gnupg2 zlib1g-dev libzip-dev libpng-dev libfreetype6-dev libjpeg62-turbo-dev libxml2-dev \
&& rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-install zip mysqli pdo pdo_mysql && docker-php-ext-enable mysqli pdo pdo_mysql sodium RUN docker-php-ext-install zip mysqli pdo pdo_mysql && docker-php-ext-enable mysqli pdo pdo_mysql sodium
RUN docker-php-ext-configure gd RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN docker-php-ext-install gd RUN docker-php-ext-install gd
RUN docker-php-ext-configure intl RUN docker-php-ext-configure intl
RUN docker-php-ext-install intl RUN docker-php-ext-install intl
@ -14,4 +15,4 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
ENV APACHE_DOCUMENT_ROOT /var/www/html/public ENV APACHE_DOCUMENT_ROOT /var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf
RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf

View file

@ -0,0 +1,77 @@
<?php
namespace App\Filament\Resources\EmailLogs;
use App\Filament\Resources\EmailLogs\Pages\ListEmailLogs;
use App\Models\EmailLog;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class EmailLogResource extends Resource
{
protected static ?string $model = EmailLog::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedClipboardDocumentList;
protected static ?string $navigationLabel = 'Email log';
protected static \UnitEnum|string|null $navigationGroup = 'Logs';
protected static ?int $navigationSort = 100;
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('created_at')
->label('Sent at')
->dateTime()
->sortable(),
TextColumn::make('lan_id')
->label('LAN ID')
->sortable()
->searchable(),
TextColumn::make('guardian_email')
->label('Email')
->searchable(),
TextColumn::make('mailtemplate.title')
->label('Mail template')
->searchable(),
TextColumn::make('smstemplate.title')
->label('SMS template')
->default('—'),
TextColumn::make('participant.first_name')
->label('First name')
->searchable(),
TextColumn::make('participant.surname')
->label('Surname')
->searchable(),
TextColumn::make('error')
->label('Error')
->wrap()
->color('danger')
->default('—')
->toggleable(isToggledHiddenByDefault: true),
])
->defaultSort('created_at', 'desc')
->filters([])
->recordActions([])
->toolbarActions([]);
}
public static function getPages(): array
{
return [
'index' => ListEmailLogs::route('/'),
];
}
public static function canCreate(): bool
{
return false;
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace App\Filament\Resources\EmailLogs\Pages;
use App\Filament\Resources\EmailLogs\EmailLogResource;
use Filament\Resources\Pages\ListRecords;
class ListEmailLogs extends ListRecords
{
protected static string $resource = EmailLogResource::class;
protected function getHeaderActions(): array
{
return [];
}
}

View file

@ -18,7 +18,13 @@ class MailtemplateResource extends Resource
{ {
protected static ?string $model = Mailtemplate::class; protected static ?string $model = Mailtemplate::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedRectangleStack; protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedEnvelope;
protected static ?string $navigationLabel = 'Mail templates';
protected static \UnitEnum|string|null $navigationGroup = 'Templates';
protected static ?int $navigationSort = 99;
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {

View file

@ -2,9 +2,10 @@
namespace App\Filament\Resources\Mailtemplates\Schemas; namespace App\Filament\Resources\Mailtemplates\Schemas;
use App\Models\Smstemplate;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\RichEditor;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
use Filament\Forms\Components\MarkdownEditor; use Filament\Forms\Components\MarkdownEditor;
use DiscoveryDesign\FilamentGaze\Forms\Components\GazeBanner; use DiscoveryDesign\FilamentGaze\Forms\Components\GazeBanner;
@ -21,13 +22,18 @@ class MailtemplateForm
TextInput::make('title') TextInput::make('title')
->required() ->required()
->columnSpanFull(), ->columnSpanFull(),
TextInput::make('type')
->label('Greeting')
->required()
->columnSpanFull(),
MarkdownEditor::make('content') MarkdownEditor::make('content')
->required() ->required()
->columnSpanFull(), ->columnSpanFull(),
Select::make('smstemplate_id')
->label('SMS template')
->options(Smstemplate::where('draft', false)->pluck('title', 'id'))
->nullable()
->columnSpanFull(),
Toggle::make('draft')
->default(false)
->onColor('warning')
->columnSpanFull(),
]); ]);
} }
} }

View file

@ -7,6 +7,7 @@ use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Filament\Tables\Columns\ToggleColumn;
class MailtemplatesTable class MailtemplatesTable
{ {
@ -16,6 +17,8 @@ class MailtemplatesTable
->columns([ ->columns([
TextColumn::make('title') TextColumn::make('title')
->searchable(), ->searchable(),
ToggleColumn::make('draft')
->sortable(),
TextColumn::make('created_at') TextColumn::make('created_at')
->dateTime() ->dateTime()
->sortable() ->sortable()

View file

@ -2,6 +2,7 @@
namespace App\Filament\Resources\Participants\Schemas; namespace App\Filament\Resources\Participants\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
@ -10,6 +11,7 @@ use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Grid;
use DiscoveryDesign\FilamentGaze\Forms\Components\GazeBanner; use DiscoveryDesign\FilamentGaze\Forms\Components\GazeBanner;
use Illuminate\Support\HtmlString;
class ParticipantForm class ParticipantForm
{ {
@ -68,6 +70,11 @@ class ParticipantForm
'reserv' => 'Reserv', 'reserv' => 'Reserv',
'besök' => 'Besök', 'besök' => 'Besök',
]), ]),
TextInput::make('ssn')
->label('SSN')
->default(null)
->length(12)
->columnSpan('full'),
TextInput::make('first_name') TextInput::make('first_name')
->required(), ->required(),
TextInput::make('surname') TextInput::make('surname')
@ -96,6 +103,29 @@ class ParticipantForm
->columnSpan('full'), ->columnSpan('full'),
Textarea::make('comment') Textarea::make('comment')
->columnSpan('full'), ->columnSpan('full'),
Placeholder::make('email_log')
->label('Emails sent')
->columnSpan('full')
->hidden(fn ($record) => $record === null)
->content(function ($record) {
if (!$record) {
return '—';
}
$logs = $record->emailLogs()->with(['mailtemplate', 'smstemplate'])->latest()->get();
if ($logs->isEmpty()) {
return new HtmlString('<span class="text-gray-400 text-sm">No emails sent</span>');
}
$rows = $logs->map(function ($log) {
$date = $log->created_at->format('Y-m-d H:i');
$mail = e($log->mailtemplate?->title ?? '—');
$sms = $log->smstemplate ? ' + SMS: ' . e($log->smstemplate->title) : '';
$error = $log->error
? ' <span class="text-red-500 font-medium">&#9888; ' . e($log->error) . '</span>'
: '';
return "<li class=\"text-sm py-1 border-b border-gray-100 last:border-0\"><span class=\"text-gray-400\">{$date}</span> &mdash; {$mail}{$sms}{$error}</li>";
})->join('');
return new HtmlString("<ul class=\"divide-y divide-gray-100\">{$rows}</ul>");
}),
]); ]);
} }
} }

View file

@ -2,12 +2,15 @@
namespace App\Filament\Resources\Participants\Tables; namespace App\Filament\Resources\Participants\Tables;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup; use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction; use Filament\Actions\DeleteBulkAction;
use Illuminate\Database\Eloquent\Collection;
use Filament\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use App\Models\EmailLog;
use App\Models\Participant; use App\Models\Participant;
use App\Models\Mailtemplate; use App\Models\Mailtemplate;
use Filament\Tables\Columns\SelectColumn; use Filament\Tables\Columns\SelectColumn;
@ -20,8 +23,11 @@ use Filament\Support\Icons\Heroicon;
use App\Filament\Exports\ParticipantExporter; use App\Filament\Exports\ParticipantExporter;
use Filament\Actions\ExportAction; use Filament\Actions\ExportAction;
use Filament\Tables\Columns\TextInputColumn; use Filament\Tables\Columns\TextInputColumn;
use Illuminate\Validation\Rule;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Grouping\Group; use Filament\Tables\Grouping\Group;
use Filament\Tables\Columns\Summarizers\Count; use Filament\Tables\Columns\Summarizers\Count;
use Filament\Tables\Columns\ToggleColumn;
class ParticipantsTable class ParticipantsTable
{ {
@ -29,10 +35,12 @@ class ParticipantsTable
{ {
return $table return $table
->paginated([10, 25, 50, 100, 'all']) ->paginated([10, 25, 50, 100, 'all'])
->defaultPaginationPageOption(100)
->groups([ ->groups([
Group::make('status') Group::make('status')
->label('Status') ->label('Status')
->collapsible(), ->getTitleFromRecordUsing(fn ($record) => ucfirst($record->status))
->collapsible(),
]) ])
->defaultGroup('status') ->defaultGroup('status')
//->groupsOnly() //->groupsOnly()
@ -45,26 +53,32 @@ class ParticipantsTable
TextInputColumn::make('lan_id') TextInputColumn::make('lan_id')
->label('ID') ->label('ID')
->searchable() ->searchable()
->sortable(), ->sortable()
->toggleable(isToggledHiddenByDefault: true)
->rules(fn ($record) => [
Rule::unique('participants', 'lan_id')->ignore($record->id),
])
->validationMessages([
'unique' => 'LAN ID is already assigned to another participant.',
]),
TextColumn::make('status') TextColumn::make('status')
->label('Status') ->label('Status')
->badge() ->badge()
->summarize(Count::make()->label(''))
->color(fn (string $state): string => match ($state) { ->color(fn (string $state): string => match ($state) {
'lan' => 'success', 'lan' => 'success',
'reserv' => 'warning', 'reserv' => 'warning',
'besök' => 'gray' 'besök' => 'gray'
}) })
->formatStateUsing(fn (string $state): string => __(ucfirst($state))), ->formatStateUsing(fn (string $state): string => __(ucfirst($state))),
IconColumn::make('paid') TextColumn::make('ssn')
->boolean() ->label('SSN')
->sortable(), ->searchable()
IconColumn::make('emailed') ->sortable()
->boolean() ->toggleable(isToggledHiddenByDefault: true),
->sortable(),
TextColumn::make('first_name') TextColumn::make('first_name')
->searchable() ->searchable()
->sortable(), ->sortable()
->summarize(Count::make()->label('')),
TextColumn::make('surname') TextColumn::make('surname')
->searchable() ->searchable()
->sortable(), ->sortable(),
@ -99,11 +113,22 @@ class ParticipantsTable
->badge() ->badge()
->color('gray') ->color('gray')
->toggleable(isToggledHiddenByDefault: true), ->toggleable(isToggledHiddenByDefault: true),
ToggleColumn::make('paid')
IconColumn::make('member') ->sortable(),
ToggleColumn::make('member')
->sortable(),
IconColumn::make('emailed')
->boolean() ->boolean()
->sortable() ->sortable()
->toggleable(isToggledHiddenByDefault: true), ->tooltip(function ($record) {
$logs = $record->emailLogs()->with(['mailtemplate', 'smstemplate'])->latest()->get();
if ($logs->isEmpty()) {
return 'No emails sent';
}
return $logs->map(fn($log) =>
' Mail: ' . ($log->mailtemplate?->title ?? '—')
)->join("\n");
}),
TextColumn::make('comment') TextColumn::make('comment')
->sortable() ->sortable()
->toggleable(isToggledHiddenByDefault: true), ->toggleable(isToggledHiddenByDefault: true),
@ -129,7 +154,13 @@ class ParticipantsTable
->toggleable(isToggledHiddenByDefault: true), ->toggleable(isToggledHiddenByDefault: true),
]) ])
->filters([ ->filters([
// SelectFilter::make('status')
->options([
'lan' => 'Ordinarie',
'reserv' => 'Reserv',
'besök' => 'Besök',
])
->multiple(),
]) ])
->recordActions([ ->recordActions([
EditAction::make() EditAction::make()
@ -141,37 +172,75 @@ class ParticipantsTable
->schema([ ->schema([
Select::make('mailtemplate') Select::make('mailtemplate')
->label('Mailtemplate') ->label('Mailtemplate')
->options(Mailtemplate::all()->pluck('title', 'id')) ->options(Mailtemplate::where('draft', false)->pluck('title', 'id'))
]) ])
->action(function (array $data, Participant $record) { ->action(function (array $data, Participant $record) {
$mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get(); $mailtemplate = Mailtemplate::with('smstemplate')->find($data['mailtemplate']);
Mail::to($record->guardian_email) if ($record->guardian_email) {
->send(new LanMail($mailContent, $record)); $error = null;
Participant::where('id', $record->id)->update(['emailed' => true]); try {
Mail::to(config('app.smsUrl')) Mail::to($record->guardian_email)
->send(new SmsMail($record)); ->send(new LanMail(collect([$mailtemplate]), $record));
Participant::where('id', $record->id)->update(['emailed' => true]);
}) if ($mailtemplate->smstemplate && config('app.smsUrl')) {
->hidden(fn($record) => $record->emailed), Mail::to(config('app.smsUrl'))
Action::make('sendRemindEmail') ->send(new SmsMail($record, $mailtemplate->smstemplate->content));
->label('Send remind email') }
->icon(Heroicon::Envelope) } catch (\Throwable $e) {
->schema([ $error = $e->getMessage();
Select::make('mailtemplate') }
->label('Mailtemplate') EmailLog::create([
->options(Mailtemplate::all()->pluck('title', 'id')) 'participant_id' => $record->id,
]) 'lan_id' => $record->lan_id,
->action(function (array $data, Participant $record) { 'guardian_email' => $record->guardian_email,
$mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get(); 'mailtemplate_id' => $mailtemplate->id,
Mail::to($record->guardian_email) 'smstemplate_id' => $mailtemplate->smstemplate?->id,
->queue(new LanMail($mailContent, $record)); 'error' => $error,
Participant::where('id', $record->id)->update(['emailed' => true]); ]);
}) }
->hidden(fn($record) => !$record->emailed), }),
]) ])
->toolbarActions([ ->toolbarActions([
BulkActionGroup::make([ BulkActionGroup::make([
DeleteBulkAction::make(), DeleteBulkAction::make(),
BulkAction::make('bulkSendEmail')
->label('Send email')
->icon(Heroicon::Envelope)
->schema([
Select::make('mailtemplate')
->label('Mailtemplate')
->options(Mailtemplate::where('draft', false)->pluck('title', 'id'))
->required(),
])
->action(function (Collection $records, array $data) {
$mailtemplate = Mailtemplate::with('smstemplate')->find($data['mailtemplate']);
foreach ($records as $record) {
if (!$record->guardian_email) {
continue;
}
$error = null;
try {
Mail::to($record->guardian_email)
->queue(new LanMail(collect([$mailtemplate]), $record));
if ($mailtemplate->smstemplate && config('app.smsUrl')) {
Mail::to(config('app.smsUrl'))
->queue(new SmsMail($record, $mailtemplate->smstemplate->content));
}
} catch (\Throwable $e) {
$error = $e->getMessage();
}
EmailLog::create([
'participant_id' => $record->id,
'lan_id' => $record->lan_id,
'guardian_email' => $record->guardian_email,
'mailtemplate_id' => $mailtemplate->id,
'smstemplate_id' => $mailtemplate->smstemplate?->id,
'error' => $error,
]);
}
$records->toQuery()->whereDoesntHave('emailLogs', fn ($q) => $q->whereNotNull('error'))->update(['emailed' => true]);
})
->deselectRecordsAfterCompletion(),
]), ]),
]); ]);
} }

View file

@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Smstemplates\Pages;
use App\Filament\Resources\Smstemplates\SmstemplatesResource;
use Filament\Resources\Pages\CreateRecord;
class CreateSmstemplates extends CreateRecord
{
protected static string $resource = SmstemplatesResource::class;
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Smstemplates\Pages;
use App\Filament\Resources\Smstemplates\SmstemplatesResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditSmstemplates extends EditRecord
{
protected static string $resource = SmstemplatesResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Smstemplates\Pages;
use App\Filament\Resources\Smstemplates\SmstemplatesResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListSmstemplates extends ListRecords
{
protected static string $resource = SmstemplatesResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Filament\Resources\Smstemplates\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Schema;
use Filament\Forms\Components\MarkdownEditor;
use DiscoveryDesign\FilamentGaze\Forms\Components\GazeBanner;
class SmstemplatesForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
GazeBanner::make()
->pollTimer(10)
->hideOnCreate(),
TextInput::make('title')
->required()
->columnSpanFull(),
Toggle::make('draft')
->default(false)
->onColor('warning')
->columnSpanFull(),
]);
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Filament\Resources\Smstemplates;
use App\Filament\Resources\Smstemplates\Pages\CreateSmstemplates;
use App\Filament\Resources\Smstemplates\Pages\EditSmstemplates;
use App\Filament\Resources\Smstemplates\Pages\ListSmstemplates;
use App\Filament\Resources\Smstemplates\Schemas\SmstemplatesForm;
use App\Filament\Resources\Smstemplates\Tables\SmstemplatesTable;
use App\Models\Smstemplate;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
class SmstemplatesResource extends Resource
{
protected static ?string $model = Smstemplate::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChatBubbleLeftRight;
protected static ?string $navigationLabel = 'SMS templates';
protected static \UnitEnum|string|null $navigationGroup = 'Templates';
protected static ?int $navigationSort = 99;
public static function form(Schema $schema): Schema
{
return SmstemplatesForm::configure($schema);
}
public static function table(Table $table): Table
{
return SmstemplatesTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListSmstemplates::route('/'),
'create' => CreateSmstemplates::route('/create'),
'edit' => EditSmstemplates::route('/{record}/edit'),
];
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace App\Filament\Resources\Smstemplates\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\ToggleColumn;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
class SmstemplatesTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->searchable()
->sortable(),
ToggleColumn::make('draft')
->sortable(),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
TernaryFilter::make('draft')
->label('Draft'),
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
}

View file

@ -33,10 +33,8 @@ class VolunteersTable
TextInputColumn::make('lan_id') TextInputColumn::make('lan_id')
->label('ID') ->label('ID')
->searchable() ->searchable()
->sortable(), ->sortable()
IconColumn::make('emailed') ->toggleable(isToggledHiddenByDefault: true),
->boolean()
->sortable(),
TextColumn::make('first_name') TextColumn::make('first_name')
->searchable() ->searchable()
->sortable(), ->sortable(),
@ -76,36 +74,22 @@ class VolunteersTable
EditAction::make() EditAction::make()
->modalWidth() ->modalWidth()
->slideOver(), ->slideOver(),
Action::make('sendEmail') //Action::make('sendEmail')
->label('Send email') //->label('Send email')
->icon(Heroicon::Envelope) //->icon(Heroicon::Envelope)
->schema([ //->schema([
Select::make('mailtemplate') // Select::make('mailtemplate')
->label('Mailtemplate') // ->label('Mailtemplate')
->options(Mailtemplate::all()->pluck('title', 'id')) // ->options(Mailtemplate::all()->pluck('title', 'id'))
]) //])
->action(function (array $data, Volunteer $record) { //->action(function (array $data, Volunteer $record) {
$mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get(); // if ($record->email) {
Mail::to($record->email) // $mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get();
->queue(new LanMail($mailContent, $record)); // Mail::to($record->email)
Volunteer::where('id', $record->id)->update(['emailed' => true]); // ->queue(new LanMail($mailContent, $record));
}) // Volunteer::where('id', $record->id)->update(['emailed' => true]);
->hidden(fn($record) => $record->emailed), // }
Action::make('sendRemindEmail') //}),
->label('Send remind email')
->icon(Heroicon::Envelope)
->schema([
Select::make('mailtemplate')
->label('Mailtemplate')
->options(Mailtemplate::all()->pluck('title', 'id'))
])
->action(function (array $data, Volunteer $record) {
$mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get();
Mail::to($record->email)
->send(new LanMail($mailContent, $record));
Volunteer::where('id', $record->id)->update(['emailed' => true]);
})
->hidden(fn($record) => !$record->emailed)
]) ])
->toolbarActions([ ->toolbarActions([
BulkActionGroup::make([ BulkActionGroup::make([

View file

@ -19,7 +19,7 @@ class ParticipantController extends Controller
if ($permission === "key_5") { if ($permission === "key_5") {
$participants = Participant::whereNotNull('lan_id') $participants = Participant::whereNotNull('lan_id')
->select('id','lan_id', 'first_name', 'surname','grade','phone','email', 'guardian_name', 'guardian_phone', 'guardian_email', 'is_visiting','friends', 'special_diet', 'status','created_at', 'updated_at') ->select('id','lan_id', 'ssn','first_name', 'surname','grade','phone','email', 'guardian_name', 'guardian_phone', 'guardian_email', 'is_visiting','friends', 'special_diet', 'status','created_at', 'updated_at')
->get(); ->get();
$volunteers = Volunteer::whereNotNull('lan_id') $volunteers = Volunteer::whereNotNull('lan_id')
->select('id','lan_id', 'first_name', 'surname','phone','email', 'areas', 'created_at', 'updated_at') ->select('id','lan_id', 'first_name', 'surname','phone','email', 'areas', 'created_at', 'updated_at')
@ -51,7 +51,7 @@ class ParticipantController extends Controller
if ($permission === "key_3") { if ($permission === "key_3") {
$participants = Participant::whereNotNull('lan_id') $participants = Participant::whereNotNull('lan_id')
->select('id','lan_id', 'first_name', 'surname','grade','phone','email', 'guardian_name', 'guardian_phone', 'guardian_email', 'is_visiting','friends', 'special_diet', 'status','created_at', 'updated_at') ->select('id','lan_id', 'ssn', 'first_name', 'surname','grade','phone','email', 'guardian_name', 'guardian_phone', 'guardian_email', 'is_visiting','friends', 'special_diet', 'status','created_at', 'updated_at')
->get(); ->get();
return $dataArr = [ return $dataArr = [
@ -96,6 +96,7 @@ class ParticipantController extends Controller
$request->validate([ $request->validate([
'member' => 'required', 'member' => 'required',
'first_name' => 'required', 'first_name' => 'required',
'ssn' => 'nullable',
'surname' => 'required', 'surname' => 'required',
'grade' => 'required', 'grade' => 'required',
'phone' => 'nullable', 'phone' => 'nullable',
@ -116,7 +117,7 @@ class ParticipantController extends Controller
$status = "lan"; $status = "lan";
} }
else if (! $request->is_visiting) { else if ($request->is_visiting) {
$status = "besök"; $status = "besök";
} }
@ -126,6 +127,7 @@ class ParticipantController extends Controller
Participant::create([ Participant::create([
'member' => $request->member, 'member' => $request->member,
'ssn' => $request->ssn,
'first_name' => $request->first_name, 'first_name' => $request->first_name,
'surname' => $request->surname, 'surname' => $request->surname,
'grade' => $request->grade, 'grade' => $request->grade,

View file

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use App\Models\Smstemplate;
use Illuminate\Http\Request;
class SmstemplateController extends Controller
{
public function index()
{
//
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show(Smstemplate $smstemplate)
{
//
}
public function edit(Smstemplate $smstemplate)
{
//
}
public function update(Request $request, Smstemplate $smstemplate)
{
//
}
public function destroy(Smstemplate $smstemplate)
{
//
}
}

View file

@ -15,13 +15,9 @@ class SmsMail extends Mailable
public $phone; public $phone;
public $name; public $name;
public $smsContent;
public function __construct($participant, ?string $smsContent = null)
/**
* Create a new message instance.
*/
public function __construct($participant)
{ {
function formatToSwedenPrefix($phoneNumber) { function formatToSwedenPrefix($phoneNumber) {
@ -50,6 +46,7 @@ class SmsMail extends Mailable
$this->name = $participant->first_name; $this->name = $participant->first_name;
$this->phone = formatToSwedenPrefix($participant->guardian_phone); $this->phone = formatToSwedenPrefix($participant->guardian_phone);
$this->smsContent = $smsContent;
} }
/** /**
@ -68,7 +65,8 @@ class SmsMail extends Mailable
public function content(): Content public function content(): Content
{ {
return new Content( return new Content(
view: 'mail.sms', view: 'mail.sms',
with: ['smsContent' => $this->smsContent],
); );
} }

32
app/Models/EmailLog.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class EmailLog extends Model
{
protected $fillable = [
'participant_id',
'lan_id',
'guardian_email',
'mailtemplate_id',
'smstemplate_id',
'error',
];
public function participant()
{
return $this->belongsTo(Participant::class);
}
public function mailtemplate()
{
return $this->belongsTo(Mailtemplate::class);
}
public function smstemplate()
{
return $this->belongsTo(Smstemplate::class);
}
}

View file

@ -9,6 +9,13 @@ class Mailtemplate extends Model
protected $fillable = [ protected $fillable = [
'title', 'title',
'content', 'content',
'type' 'type',
'draft',
'smstemplate_id',
]; ];
public function smstemplate()
{
return $this->belongsTo(Smstemplate::class);
}
} }

View file

@ -25,9 +25,15 @@ class Participant extends Model
'status', 'status',
'emailed', 'emailed',
'comment', 'comment',
'paid' 'paid',
'ssn'
]; ];
public function emailLogs()
{
return $this->hasMany(EmailLog::class);
}
protected static function booted() protected static function booted()
{ {
static::created(function ($post) { static::created(function ($post) {

View file

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Smstemplate extends Model
{
protected $fillable = [
'title',
'content',
'type',
'draft',
];
}

View file

@ -40,6 +40,18 @@ class AdminPanelProvider extends PanelProvider
->colors([ ->colors([
'primary' => Color::Amber, 'primary' => Color::Amber,
]) ])
->renderHook(
'panels::head.end',
fn () => '<style>.tippy-content { white-space: pre-line; } .fi-ta-cell.fi-ta-summary-header-cell.fi-align-start { color: transparent; } .fi-topbar-start { padding-top: 0.5rem !important; padding-bottom: 1rem !important; }</style>',
)
->navigationGroups([
\Filament\Navigation\NavigationGroup::make('Templates'),
\Filament\Navigation\NavigationGroup::make('Logs'),
])
->brandName('vBytes LAN')
->favicon(asset('images/vbytes-logo.png'))
->brandLogo(asset('images/vbytes-logo.png'))
->brandLogoHeight('5rem')
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources') ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
->pages([ ->pages([

View file

@ -8,7 +8,7 @@
"require": { "require": {
"php": "^8.2", "php": "^8.2",
"discoverydesign/filament-gaze": "^2.0", "discoverydesign/filament-gaze": "^2.0",
"filament/filament": "^4.0", "filament/filament": "^5.0",
"inertiajs/inertia-laravel": "^2.0", "inertiajs/inertia-laravel": "^2.0",
"laravel/framework": "^12.0", "laravel/framework": "^12.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",

2035
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('participants', function (Blueprint $table) {
$table->string('ssn')->nullable()->after('status');
$table->unique('lan_id');
});
}
public function down(): void
{
Schema::table('participants', function (Blueprint $table) {
$table->dropUnique(['lan_id']);
$table->dropColumn('ssn');
});
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('mailtemplates', function (Blueprint $table) {
$table->boolean('draft')->default(false)->after('content');
});
}
public function down(): void
{
Schema::table('mailtemplates', function (Blueprint $table) {
$table->dropColumn('draft');
});
}
};

View file

@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('smstemplates', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('type');
$table->longText('content');
$table->boolean('draft')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('smstemplates');
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('smstemplates', function (Blueprint $table) {
$table->string('type')->nullable()->change();
});
}
public function down(): void
{
Schema::table('smstemplates', function (Blueprint $table) {
$table->string('type')->nullable(false)->change();
});
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('mailtemplates', function (Blueprint $table) {
$table->foreignId('smstemplate_id')->nullable()->constrained('smstemplates')->nullOnDelete()->after('draft');
});
}
public function down(): void
{
Schema::table('mailtemplates', function (Blueprint $table) {
$table->dropForeignIdFor(\App\Models\Smstemplate::class);
});
}
};

View file

@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('email_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('participant_id')->constrained()->cascadeOnDelete();
$table->integer('lan_id')->nullable();
$table->string('guardian_email')->nullable();
$table->foreignId('mailtemplate_id')->nullable()->constrained('mailtemplates')->nullOnDelete();
$table->foreignId('smstemplate_id')->nullable()->constrained('smstemplates')->nullOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('email_logs');
}
};

View file

@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('email_logs', function (Blueprint $table) {
$table->text('error')->nullable()->after('smstemplate_id');
});
}
public function down(): void
{
Schema::table('email_logs', function (Blueprint $table) {
$table->dropColumn('error');
});
}
};

2704
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -7,19 +7,13 @@
"dev": "vite" "dev": "vite"
}, },
"devDependencies": { "devDependencies": {
"@headlessui/react": "^2.0.0", "@tailwindcss/forms": "^0.5.11",
"@inertiajs/react": "^2.0.0", "@tailwindcss/postcss": "^4.3.0",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/vite": "^4.0.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.12",
"axios": "^1.11.0", "axios": "^1.11.0",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"react": "^18.2.0", "tailwindcss": "^4.3.0",
"react-dom": "^18.2.0",
"tailwindcss": "^3.2.1",
"vite": "^7.0.4" "vite": "^7.0.4"
}, },
"dependencies": { "dependencies": {

View file

@ -1,6 +1,5 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, '@tailwindcss/postcss': {},
autoprefixer: {},
}, },
}; };

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
(()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex)})},syncActionModals(t){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}if(this.actionNestingIndex!==null&&this.closeModal(),this.actionNestingIndex=t,this.actionNestingIndex!==null){if(!this.$el.querySelector(`#${this.generateModalId(t)}`)){this.$nextTick(()=>this.openModal());return}this.openModal()}},generateModalId(t){return`fi-${e}-action-`+t},openModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:t}}))},closeModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:t}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",n)});})(); (()=>{var n=({livewireId:e})=>({actionNestingIndex:null,init(){window.addEventListener("sync-action-modals",t=>{t.detail.id===e&&this.syncActionModals(t.detail.newActionNestingIndex,t.detail.shouldOverlayParentActions??!1)})},syncActionModals(t,i=!1){if(this.actionNestingIndex===t){this.actionNestingIndex!==null&&this.$nextTick(()=>this.openModal());return}let s=this.actionNestingIndex!==null&&t!==null&&t>this.actionNestingIndex;if(this.actionNestingIndex!==null&&!(i&&s)&&this.closeModal(),this.actionNestingIndex=t,this.actionNestingIndex!==null){if(!this.$el.querySelector(`#${this.generateModalId(t)}`)){this.$nextTick(()=>this.openModal());return}this.openModal()}},generateModalId(t){return`fi-${e}-action-`+t},openModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("open-modal",{bubbles:!0,composed:!0,detail:{id:t}}))},closeModal(){let t=this.generateModalId(this.actionNestingIndex);document.dispatchEvent(new CustomEvent("close-modal-quietly",{bubbles:!0,composed:!0,detail:{id:t}}))}});document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentActionModals",n)});})();

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),Livewire.hook("commit",({component:e,commit:t,succeed:i,fail:o,respond:h})=>{i(({snapshot:r,effect:l})=>{this.$nextTick(()=>{e.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))}}}export{c as default}; function c({livewireId:s}){return{areAllCheckboxesChecked:!1,checkboxListOptions:[],search:"",unsubscribeLivewireHook:null,visibleCheckboxListOptions:[],init(){this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.$nextTick(()=>{this.checkIfAllCheckboxesAreChecked()}),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{e.component.id===s&&(this.checkboxListOptions=Array.from(this.$root.querySelectorAll(".fi-fo-checkbox-list-option")),this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked())})})}),this.$watch("search",()=>{this.updateVisibleCheckboxListOptions(),this.checkIfAllCheckboxesAreChecked()})},checkIfAllCheckboxesAreChecked(){this.areAllCheckboxesChecked=this.visibleCheckboxListOptions.length===this.visibleCheckboxListOptions.filter(e=>e.querySelector("input[type=checkbox]:checked, input[type=checkbox]:disabled")).length},toggleAllCheckboxes(){this.checkIfAllCheckboxesAreChecked();let e=!this.areAllCheckboxesChecked;this.visibleCheckboxListOptions.forEach(t=>{let i=t.querySelector("input[type=checkbox]");i.disabled||i.checked!==e&&(i.checked=e,i.dispatchEvent(new Event("change")))}),this.areAllCheckboxesChecked=e},updateVisibleCheckboxListOptions(){this.visibleCheckboxListOptions=this.checkboxListOptions.filter(e=>["",null,void 0].includes(this.search)||e.querySelector(".fi-fo-checkbox-list-option-label")?.innerText.toLowerCase().includes(this.search.toLowerCase())?!0:e.querySelector(".fi-fo-checkbox-list-option-description")?.innerText.toLowerCase().includes(this.search.toLowerCase()))},destroy(){this.unsubscribeLivewireHook?.()}}}export{c as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
function h({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{h as default}; function a({state:r}){return{state:r,rows:[],init(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(e,t)=>{if(!Array.isArray(e))return;let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(e)===0&&s(t)===0||this.updateRows()})},addRow(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow(e){this.rows.splice(e,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows(e){let t=Alpine.raw(this.rows);this.rows=[];let s=t.splice(e.oldIndex,1)[0];t.splice(e.newIndex,0,s),this.$nextTick(()=>{this.rows=t,this.updateState()})},updateRows(){let t=Alpine.raw(this.state).map(({key:s,value:i})=>({key:s,value:i}));this.rows.forEach(s=>{(s.key===""||s.key===null)&&t.push({key:"",value:s.value})}),this.rows=t},updateState(){let e=[];this.rows.forEach(t=>{t.key===""||t.key===null||e.push({key:t.key,value:t.value})}),JSON.stringify(this.state)!==JSON.stringify(e)&&(this.state=e)}}}export{a as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default}; function n({initialHeight:e,shouldAutosize:i,state:h}){return{state:h,wrapperEl:null,init(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=e+"rem")},resize(){if(this.$el.scrollHeight<=0)return;let t=this.$el.style.height;this.$el.style.height="0px";let r=this.$el.scrollHeight;this.$el.style.height=t;let l=parseFloat(e)*parseFloat(getComputedStyle(document.documentElement).fontSize),s=Math.max(r,l)+"px";this.wrapperEl.style.height!==s&&(this.wrapperEl.style.height=s)},setUpResizeObserver(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{n as default};

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
var i=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});export{i as default}; var i=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let e=this.$el.parentElement;e&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(e),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let e=this.$el.parentElement;if(!e)return;let t=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=e.offsetWidth+parseInt(t.marginInlineStart,10)*-1+parseInt(t.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});export{i as default};

View file

@ -1 +1 @@
function u({activeTab:a,isTabPersistedInQueryString:e,livewireId:h,tab:o,tabQueryStringKey:s}){return{tab:o,init(){let t=this.getTabs(),i=new URLSearchParams(window.location.search);e&&i.has(s)&&t.includes(i.get(s))&&(this.tab=i.get(s)),this.$watch("tab",()=>this.updateQueryString()),(!this.tab||!t.includes(this.tab))&&(this.tab=t[a-1]),Livewire.hook("commit",({component:r,commit:f,succeed:c,fail:l,respond:b})=>{c(({snapshot:d,effect:m})=>{this.$nextTick(()=>{if(r.id!==h)return;let n=this.getTabs();n.includes(this.tab)||(this.tab=n[a-1]??this.tab)})})})},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!e)return;let t=new URL(window.location.href);t.searchParams.set(s,this.tab),history.replaceState(null,document.title,t.toString())}}}export{u as default}; function x({activeTab:h,isScrollable:m,isTabPersisted:T,isTabPersistedInQueryString:u,livewireId:g,schemaKey:D,tab:W,tabQueryStringKey:r}){return{boundResizeHandler:null,boundResetHandler:null,isScrollable:m,resizeDebounceTimer:null,tab:W,unsubscribeLivewireHook:null,withinDropdownIndex:null,withinDropdownMounted:!1,init(){let t=this.getTabs(),e=new URLSearchParams(window.location.search);u&&e.has(r)&&t.includes(e.get(r))&&(this.tab=e.get(r)),(!this.tab||!t.includes(this.tab))&&(this.tab=t[h-1]),this.$watch("tab",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:i,onSuccess:a})=>{a(()=>{this.$nextTick(()=>{if(i.component.id!==g)return;let l=this.getTabs();l.includes(this.tab)||(this.tab=l[h-1]??this.tab)})})}),this.boundResetHandler=i=>{i.detail.livewireId!==g||i.detail.schemaKey!==D||T||u||this.$nextTick(()=>{this.tab=this.getTabs()[h-1]??this.tab})},window.addEventListener("reset-schema-component-state",this.boundResetHandler),m||(this.boundResizeHandler=this.debouncedUpdateTabsWithinDropdown.bind(this),window.addEventListener("resize",this.boundResizeHandler),this.updateTabsWithinDropdown())},calculateAvailableWidth(t){let e=window.getComputedStyle(t);return Math.floor(t.clientWidth)-Math.ceil(parseFloat(e.paddingLeft))*2},calculateContainerGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap))},calculateDropdownIconWidth(t){let e=t.querySelector(".fi-icon");return Math.ceil(e.clientWidth)},calculateTabItemGap(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.columnGap)||8)},calculateTabItemPadding(t){let e=window.getComputedStyle(t);return Math.ceil(parseFloat(e.paddingLeft))+Math.ceil(parseFloat(e.paddingRight))},findOverflowIndex(t,e,i,a,l,b){let p=t.map(n=>Math.ceil(n.clientWidth)),w=t.map(n=>{let d=n.querySelector(".fi-tabs-item-label"),s=n.querySelector(".fi-badge"),o=Math.ceil(d.clientWidth),c=s?Math.ceil(s.clientWidth):0;return{label:o,badge:c,total:o+(c>0?a+c:0)}});for(let n=0;n<t.length;n++){let d=p.slice(0,n+1).reduce((f,I)=>f+I,0),s=n*i,o=w.slice(n+1),c=o.length>0,v=c?Math.max(...o.map(f=>f.total)):0,y=c?l+v+a+b+i:0;if(d+s+y>e)return n}return-1},get isDropdownButtonVisible(){return this.withinDropdownMounted?this.withinDropdownIndex===null?!1:this.getTabs().findIndex(e=>e===this.tab)<this.withinDropdownIndex:!0},getTabs(){return this.$refs.tabsData?JSON.parse(this.$refs.tabsData.value):[]},updateQueryString(){if(!u)return;let t=new URL(window.location.href);t.searchParams.set(r,this.tab),history.replaceState(null,document.title,t.toString())},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$el.querySelectorAll(".fi-sc-tabs-tab.fi-active [autofocus]");for(let i of e)if(i.focus(),document.activeElement===i)break})},debouncedUpdateTabsWithinDropdown(){clearTimeout(this.resizeDebounceTimer),this.resizeDebounceTimer=setTimeout(()=>this.updateTabsWithinDropdown(),150)},async updateTabsWithinDropdown(){this.withinDropdownIndex=null,this.withinDropdownMounted=!1,await this.$nextTick();let t=this.$el.querySelector(".fi-tabs"),e=t.querySelector(".fi-tabs-item:last-child"),i=Array.from(t.children).slice(0,-1),a=i.map(s=>s.style.display);i.forEach(s=>s.style.display=""),t.offsetHeight;let l=this.calculateAvailableWidth(t),b=this.calculateContainerGap(t),p=this.calculateDropdownIconWidth(e),w=this.calculateTabItemGap(i[0]),n=this.calculateTabItemPadding(i[0]),d=this.findOverflowIndex(i,l,b,w,n,p);i.forEach((s,o)=>s.style.display=a[o]),d!==-1&&(this.withinDropdownIndex=d),this.withinDropdownMounted=!0},destroy(){this.unsubscribeLivewireHook?.(),this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler),this.boundResizeHandler&&window.removeEventListener("resize",this.boundResizeHandler),clearTimeout(this.resizeDebounceTimer)}}}export{x as default};

View file

@ -1 +1 @@
function o({isSkippable:s,isStepPersistedInQueryString:i,key:r,startStep:h,stepQueryStringKey:n}){return{step:null,init(){this.$watch("step",()=>this.updateQueryString()),this.step=this.getSteps().at(h-1),this.autofocusFields()},async requestNextStep(){await this.$wire.callSchemaComponentMethod(r,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.autofocusFields(),this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(){this.$nextTick(()=>this.$refs[`step-${this.step}`].querySelector("[autofocus]")?.focus())},getStepIndex(t){let e=this.getSteps().findIndex(p=>p===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return s||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!i)return;let t=new URL(window.location.href);t.searchParams.set(n,this.step),history.replaceState(null,document.title,t.toString())}}}export{o as default}; function l({isSkippable:i,isStepPersistedInQueryString:n,key:o,livewireId:h,schemaKey:p,startStep:r,stepQueryStringKey:d}){return{boundResetHandler:null,step:null,init(){this.step=this.getSteps().at(r-1),this.$watch("step",()=>{this.updateQueryString(),this.autofocusFields()}),this.autofocusFields(!0),this.boundResetHandler=t=>{t.detail.livewireId!==h||t.detail.schemaKey!==p||n||this.$nextTick(()=>{this.step=this.getSteps().at(r-1)??this.step})},window.addEventListener("reset-schema-component-state",this.boundResetHandler)},async requestNextStep(){await this.$wire.callSchemaComponentMethod(o,"nextStep",{currentStepIndex:this.getStepIndex(this.step)})},goToNextStep(){let t=this.getStepIndex(this.step)+1;t>=this.getSteps().length||(this.step=this.getSteps()[t],this.scroll())},goToPreviousStep(){let t=this.getStepIndex(this.step)-1;t<0||(this.step=this.getSteps()[t],this.scroll())},goToStep(t){let e=this.getStepIndex(t);e<=-1||!i&&e>this.getStepIndex(this.step)||(this.step=t,this.scroll())},scroll(){this.$nextTick(()=>{this.$refs.header?.children[this.getStepIndex(this.step)].scrollIntoView({behavior:"smooth",block:"start"})})},autofocusFields(t=!1){this.$nextTick(()=>{if(t&&document.activeElement&&document.activeElement!==document.body&&this.$el.compareDocumentPosition(document.activeElement)&Node.DOCUMENT_POSITION_PRECEDING)return;let e=this.$refs[`step-${this.step}`]?.querySelectorAll("[autofocus]")??[];for(let s of e)if(s.focus(),document.activeElement===s)break})},getStepIndex(t){let e=this.getSteps().findIndex(s=>s===t);return e===-1?0:e},getSteps(){return JSON.parse(this.$refs.stepsData.value)},isFirstStep(){return this.getStepIndex(this.step)<=0},isLastStep(){return this.getStepIndex(this.step)+1>=this.getSteps().length},isStepAccessible(t){return i||this.getStepIndex(this.step)>this.getStepIndex(t)},updateQueryString(){if(!n)return;let t=new URL(window.location.href);t.searchParams.set(d,this.step),history.replaceState(null,document.title,t.toString())},destroy(){this.boundResetHandler&&window.removeEventListener("reset-schema-component-state",this.boundResetHandler)}}}export{l as default};

View file

@ -1 +1 @@
(()=>{var d=()=>({isSticky:!1,enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1}});var m=function(n,e,i){let t=n;if(e.startsWith("/")&&(i=!0,e=e.slice(1)),i)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},u=n=>{let e=Alpine.findClosest(n,i=>i.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:n})=>({handleFormValidationError(e){e.detail.livewireId===n&&this.$nextTick(()=>{let i=this.$el.querySelector("[data-validation-error]");if(!i)return;let t=i;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>i.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})}})),window.Alpine.data("filamentSchemaComponent",({path:n,containerPath:e,isLive:i,$wire:t})=>({$statePath:n,$get:(r,l)=>t.$get(m(e,r,l)),$set:(r,l,a,o=null)=>(o??(o=i),t.$set(m(e,r,a),l,o)),get $state(){return t.$get(n)}})),window.Alpine.data("filamentActionsSchemaComponent",d),Livewire.hook("commit",({component:n,commit:e,respond:i,succeed:t,fail:r})=>{t(({snapshot:l,effects:a})=>{a.dispatches?.forEach(o=>{if(!o.params?.awaitSchemaComponent)return;let s=Array.from(n.el.querySelectorAll(`[wire\\:partial="schema-component::${o.params.awaitSchemaComponent}"]`)).filter(c=>u(c)===n);if(s.length!==1){if(s.length>1)throw`Multiple schema components found with key [${o.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${n.id}-${o.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(o.name,{detail:o.params}))},{once:!0})}})})})});})(); (()=>{var o=()=>({isSticky:!1,width:0,resizeObserver:null,boundUpdateWidth:null,init(){let i=this.$el.parentElement;i&&(this.updateWidth(),this.resizeObserver=new ResizeObserver(()=>this.updateWidth()),this.resizeObserver.observe(i),this.boundUpdateWidth=this.updateWidth.bind(this),window.addEventListener("resize",this.boundUpdateWidth))},enableSticky(){this.isSticky=this.$el.getBoundingClientRect().top>0},disableSticky(){this.isSticky=!1},updateWidth(){let i=this.$el.parentElement;if(!i)return;let e=getComputedStyle(this.$root.querySelector(".fi-ac"));this.width=i.offsetWidth+parseInt(e.marginInlineStart,10)*-1+parseInt(e.marginInlineEnd,10)*-1},destroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.boundUpdateWidth&&(window.removeEventListener("resize",this.boundUpdateWidth),this.boundUpdateWidth=null)}});var a=function(i,e,n){let t=i;if(e.startsWith("/")&&(n=!0,e=e.slice(1)),n)return e;for(;e.startsWith("../");)t=t.includes(".")?t.slice(0,t.lastIndexOf(".")):null,e=e.slice(3);return["",null,void 0].includes(t)?e:["",null,void 0].includes(e)?t:`${t}.${e}`},d=i=>{let e=Alpine.findClosest(i,n=>n.__livewire);if(!e)throw"Could not find Livewire component in DOM tree.";return e.__livewire};document.addEventListener("alpine:init",()=>{window.Alpine.data("filamentSchema",({livewireId:i})=>({handleFormValidationError(e){e.detail.livewireId===i&&this.$nextTick(()=>{let n=this.$el.querySelector("[data-validation-error]");if(!n)return;let t=n;for(;t;)t.dispatchEvent(new CustomEvent("expand")),t=t.parentNode;setTimeout(()=>n.closest("[data-field-wrapper]").scrollIntoView({behavior:"smooth",block:"start",inline:"start"}),200)})},isStateChanged(e,n){if(e===void 0)return!1;try{return JSON.stringify(e)!==JSON.stringify(n)}catch{return e!==n}}})),window.Alpine.data("filamentSchemaComponent",({path:i,containerPath:e,$wire:n})=>({$statePath:i,$get:(t,r)=>n.$get(a(e,t,r)),$set:(t,r,s,l=!1)=>n.$set(a(e,t,s),r,l),get $state(){return n.$get(i)}})),window.Alpine.data("filamentActionsSchemaComponent",o),Livewire.interceptMessage(({message:i,onSuccess:e})=>{e(({payload:n})=>{n.effects?.dispatches?.forEach(t=>{if(!t.params?.awaitSchemaComponent)return;let r=Array.from(i.component.el.querySelectorAll(`[wire\\:partial="schema-component::${t.params.awaitSchemaComponent}"]`)).filter(s=>d(s)===i.component);if(r.length!==1){if(r.length>1)throw`Multiple schema components found with key [${t.params.awaitSchemaComponent}].`;window.addEventListener(`schema-component-${component.id}-${t.params.awaitSchemaComponent}-loaded`,()=>{window.dispatchEvent(new CustomEvent(t.name,{detail:t.params}))},{once:!0})}})})})});})();

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:d,respond:u})=>{n(({snapshot:f,effect:h})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||this.getNormalizedState()===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e}}}export{o as default}; function a({name:i,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let r=this.getServerState();r===void 0||this.getNormalizedState()===r||(this.state=r)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||this.getNormalizedState()===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(i,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.getNormalizedState()),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[null,void 0].includes(this.$refs.serverState.value)?"":this.$refs.serverState.value.replaceAll('\\"','"')},getNormalizedState(){let e=Alpine.raw(this.state);return[null,void 0].includes(e)?"":e},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

View file

@ -1 +1 @@
function o({name:i,recordKey:s,state:a}){return{error:void 0,isLoading:!1,state:a,init(){Livewire.hook("commit",({component:e,commit:r,succeed:n,fail:h,respond:u})=>{n(({snapshot:f,effect:d})=>{this.$nextTick(()=>{if(this.isLoading||e.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let t=this.getServerState();t===void 0||Alpine.raw(this.state)===t||(this.state=t)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let r=await this.$wire.updateTableColumnState(i,s,this.state);this.error=r?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)}}}export{o as default}; function a({name:r,recordKey:s,state:n}){return{error:void 0,isLoading:!1,state:n,unsubscribeLivewireHook:null,init(){this.unsubscribeLivewireHook=Livewire.interceptMessage(({message:e,onSuccess:t})=>{t(()=>{this.$nextTick(()=>{if(this.isLoading||e.component.id!==this.$root.closest("[wire\\:id]")?.attributes["wire:id"].value)return;let i=this.getServerState();i===void 0||Alpine.raw(this.state)===i||(this.state=i)})})}),this.$watch("state",async()=>{let e=this.getServerState();if(e===void 0||Alpine.raw(this.state)===e)return;this.isLoading=!0;let t=await this.$wire.updateTableColumnState(r,s,this.state);this.error=t?.error??void 0,!this.error&&this.$refs.serverState&&(this.$refs.serverState.value=this.state?"1":"0"),this.isLoading=!1})},getServerState(){if(this.$refs.serverState)return[1,"1"].includes(this.$refs.serverState.value)},destroy(){this.unsubscribeLivewireHook?.()}}}export{a as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

21
rector.php Normal file
View file

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
__DIR__ . '/bootstrap',
__DIR__ . '/config',
__DIR__ . '/public',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/tests',
])
// uncomment to reach your current PHP version
// ->withPhpSets()
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0);

View file

@ -1,7 +1,32 @@
@tailwind base; @import 'tailwindcss';
@tailwind components;
@tailwind utilities;
@plugin '@tailwindcss/forms';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@theme {
--font-sans:
Figtree, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
.fi-ta-cell .maybe { .fi-ta-cell .maybe {
background-color: red !important; background-color: red !important;

View file

@ -14,8 +14,7 @@
<!-- Scripts --> <!-- Scripts -->
@routes @routes
@viteReactRefresh @vite(['resources/css/app.css', 'resources/js/app.js'])
@vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
@inertiaHead @inertiaHead
</head> </head>
<body class="font-sans antialiased"> <body class="font-sans antialiased">

View file

@ -1,7 +1,2 @@
Mejlutskick gällande LAN {{ $smsContent }}
Vi vill med detta sms göra dig uppmärksam att vi har skickat ett mejl till den e-postadress som angavs när ditt barn anmäldes till LAN.
Vi ber dig att läsa mejlet noggrant. I det framgår bland annat om ditt barn har fått en plats, vilken plats det är och hur du gör för att anmäla dig till att hjälpa till med evenemanget.
Om du har några funderingar, kontakta oss vbyteslan@gmail.com.
Med vänliga hälsningar, LAN-gruppen Vbytes

View file

@ -1,22 +0,0 @@
import defaultTheme from 'tailwindcss/defaultTheme';
import forms from '@tailwindcss/forms';
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php',
'./storage/framework/views/*.php',
'./resources/views/**/*.blade.php',
'./resources/js/**/*.jsx',
],
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans],
},
},
},
plugins: [forms],
};

View file

@ -1,13 +1,11 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin'; import laravel from 'laravel-vite-plugin';
import react from '@vitejs/plugin-react';
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
laravel({ laravel({
input: 'resources/js/app.js', input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true, refresh: true,
}), }),
react(),
], ],
}); });