mirror of
https://github.com/anna-sara/vbytes_lan.git
synced 2026-09-03 11:15:23 +02:00
New features with Mail and SMS
This commit is contained in:
parent
ee718a3a7d
commit
0b1a3ae96f
28 changed files with 656 additions and 85 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
77
app/Filament/Resources/EmailLogs/EmailLogResource.php
Normal file
77
app/Filament/Resources/EmailLogs/EmailLogResource.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/Filament/Resources/EmailLogs/Pages/ListEmailLogs.php
Normal file
16
app/Filament/Resources/EmailLogs/Pages/ListEmailLogs.php
Normal 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 [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
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\RichEditor;
|
|
||||||
use Filament\Forms\Components\Toggle;
|
use Filament\Forms\Components\Toggle;
|
||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Forms\Components\MarkdownEditor;
|
use Filament\Forms\Components\MarkdownEditor;
|
||||||
|
|
@ -25,6 +25,11 @@ class MailtemplateForm
|
||||||
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')
|
Toggle::make('draft')
|
||||||
->default(false)
|
->default(false)
|
||||||
->onColor('warning')
|
->onColor('warning')
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
@ -101,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">⚠ ' . 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> — {$mail}{$sms}{$error}</li>";
|
||||||
|
})->join('');
|
||||||
|
return new HtmlString("<ul class=\"divide-y divide-gray-100\">{$rows}</ul>");
|
||||||
|
}),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -32,9 +35,11 @@ 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')
|
||||||
|
->getTitleFromRecordUsing(fn ($record) => ucfirst($record->status))
|
||||||
->collapsible(),
|
->collapsible(),
|
||||||
])
|
])
|
||||||
->defaultGroup('status')
|
->defaultGroup('status')
|
||||||
|
|
@ -59,7 +64,6 @@ class ParticipantsTable
|
||||||
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',
|
||||||
|
|
@ -73,7 +77,8 @@ class ParticipantsTable
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
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(),
|
||||||
|
|
@ -114,7 +119,16 @@ class ParticipantsTable
|
||||||
->sortable(),
|
->sortable(),
|
||||||
IconColumn::make('emailed')
|
IconColumn::make('emailed')
|
||||||
->boolean()
|
->boolean()
|
||||||
->sortable(),
|
->sortable()
|
||||||
|
->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),
|
||||||
|
|
@ -161,34 +175,72 @@ class ParticipantsTable
|
||||||
->options(Mailtemplate::where('draft', false)->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']);
|
||||||
|
if ($record->guardian_email) {
|
||||||
|
$error = null;
|
||||||
|
try {
|
||||||
Mail::to($record->guardian_email)
|
Mail::to($record->guardian_email)
|
||||||
->send(new LanMail($mailContent, $record));
|
->send(new LanMail(collect([$mailtemplate]), $record));
|
||||||
Participant::where('id', $record->id)->update(['emailed' => true]);
|
Participant::where('id', $record->id)->update(['emailed' => true]);
|
||||||
|
if ($mailtemplate->smstemplate && config('app.smsUrl')) {
|
||||||
Mail::to(config('app.smsUrl'))
|
Mail::to(config('app.smsUrl'))
|
||||||
->send(new SmsMail($record));
|
->send(new SmsMail($record, $mailtemplate->smstemplate->content));
|
||||||
|
}
|
||||||
})
|
} catch (\Throwable $e) {
|
||||||
->hidden(fn($record) => $record->emailed),
|
$error = $e->getMessage();
|
||||||
Action::make('sendRemindEmail')
|
}
|
||||||
->label('Send remind email')
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
->toolbarActions([
|
||||||
|
BulkActionGroup::make([
|
||||||
|
DeleteBulkAction::make(),
|
||||||
|
BulkAction::make('bulkSendEmail')
|
||||||
|
->label('Send email')
|
||||||
->icon(Heroicon::Envelope)
|
->icon(Heroicon::Envelope)
|
||||||
->schema([
|
->schema([
|
||||||
Select::make('mailtemplate')
|
Select::make('mailtemplate')
|
||||||
->label('Mailtemplate')
|
->label('Mailtemplate')
|
||||||
->options(Mailtemplate::where('draft', false)->pluck('title', 'id'))
|
->options(Mailtemplate::where('draft', false)->pluck('title', 'id'))
|
||||||
|
->required(),
|
||||||
])
|
])
|
||||||
->action(function (array $data, Participant $record) {
|
->action(function (Collection $records, array $data) {
|
||||||
$mailContent = Mailtemplate::where('id', $data['mailtemplate'])->get();
|
$mailtemplate = Mailtemplate::with('smstemplate')->find($data['mailtemplate']);
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if (!$record->guardian_email) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$error = null;
|
||||||
|
try {
|
||||||
Mail::to($record->guardian_email)
|
Mail::to($record->guardian_email)
|
||||||
->queue(new LanMail($mailContent, $record));
|
->queue(new LanMail(collect([$mailtemplate]), $record));
|
||||||
Participant::where('id', $record->id)->update(['emailed' => true]);
|
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]);
|
||||||
})
|
})
|
||||||
->hidden(fn($record) => !$record->emailed),
|
->deselectRecordsAfterCompletion(),
|
||||||
])
|
|
||||||
->toolbarActions([
|
|
||||||
BulkActionGroup::make([
|
|
||||||
DeleteBulkAction::make(),
|
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
54
app/Filament/Resources/Smstemplates/SmstemplatesResource.php
Normal file
54
app/Filament/Resources/Smstemplates/SmstemplatesResource.php
Normal 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'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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([
|
||||||
|
|
|
||||||
44
app/Http/Controllers/SmstemplateController.php
Normal file
44
app/Http/Controllers/SmstemplateController.php
Normal 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)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,6 +66,7 @@ class SmsMail extends Mailable
|
||||||
{
|
{
|
||||||
return new Content(
|
return new Content(
|
||||||
view: 'mail.sms',
|
view: 'mail.sms',
|
||||||
|
with: ['smsContent' => $this->smsContent],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
32
app/Models/EmailLog.php
Normal file
32
app/Models/EmailLog.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,12 @@ class Mailtemplate extends Model
|
||||||
'title',
|
'title',
|
||||||
'content',
|
'content',
|
||||||
'type',
|
'type',
|
||||||
'draft'
|
'draft',
|
||||||
|
'smstemplate_id',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function smstemplate()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Smstemplate::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ class Participant extends Model
|
||||||
'ssn'
|
'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) {
|
||||||
|
|
|
||||||
15
app/Models/Smstemplate.php
Normal file
15
app/Models/Smstemplate.php
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Smstemplate extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'content',
|
||||||
|
'type',
|
||||||
|
'draft',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -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([
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,2 @@
|
||||||
Mejlutskick gällande LAN
|
{{ $smsContent }}
|
||||||
|
|
||||||
Vi vill med detta sms göra dig uppmärksam på 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 på vbyteslan@gmail.com.
|
|
||||||
|
|
||||||
Med vänliga hälsningar, LAN-gruppen Vbytes
|
|
||||||
Loading…
Reference in a new issue