A reply starts from a template filled in the language the person wrote in, is sent by the application when it has a mailer that delivers or handed to the replier's mail program otherwise, and is recorded on the submission. The same is available through the API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
6.1 KiB
PHP
132 lines
6.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Resources\Applications\Pages;
|
|
|
|
use App\Filament\Resources\Applications\ApplicationResource;
|
|
use App\Filament\Resources\Applications\Tables\ApplicationsTable;
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationReply;
|
|
use App\Models\ReplyTemplate;
|
|
use App\Support\Inbox\Replier;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\DeleteAction;
|
|
use Filament\Actions\ForceDeleteAction;
|
|
use Filament\Actions\RestoreAction;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Resources\Pages\ViewRecord;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class ViewApplication extends ViewRecord
|
|
{
|
|
protected static string $resource = ApplicationResource::class;
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
// Answering is the point of the record, so the reply is the first
|
|
// button rather than something to copy out of the page.
|
|
// Answering is the point of the record, so the reply is the first
|
|
// button: a template to start from, edited here, and sent — by
|
|
// this application when it can send mail, or handed to the
|
|
// person's own mail program with the text already written.
|
|
Action::make('reply')
|
|
->label(__('panel.applications.actions.reply'))
|
|
->icon('heroicon-o-envelope')
|
|
->color('primary')
|
|
->authorize('update')
|
|
->modalHeading(fn (Application $record) => __('panel.replies.heading', ['name' => $record->name]))
|
|
->modalDescription(fn () => Replier::canSendMail() ? __('panel.replies.sends') : __('panel.replies.hands_over'))
|
|
->modalSubmitActionLabel(fn () => Replier::canSendMail() ? __('panel.replies.send') : __('panel.replies.open_mail'))
|
|
->schema([
|
|
Select::make('template_id')
|
|
->label(__('panel.replies.fields.template'))
|
|
->options(fn (Application $record) => ReplyTemplate::query()->for($record->kind)->pluck('name', 'id')->all())
|
|
->placeholder(__('panel.replies.no_template'))
|
|
->live()
|
|
->afterStateUpdated(function (?string $state, callable $set, Application $record): void {
|
|
$template = $state ? ReplyTemplate::find($state) : null;
|
|
|
|
if ($template !== null) {
|
|
$text = $template->renderFor($record, Auth::user());
|
|
$set('subject', $text['subject']);
|
|
$set('body', $text['body']);
|
|
}
|
|
}),
|
|
|
|
TextInput::make('subject')
|
|
->label(__('panel.replies.fields.subject'))
|
|
->default(fn (Application $record) => __('panel.applications.reply_subject', ['subject' => $record->subject()], $record->locale))
|
|
->required()
|
|
->maxLength(255),
|
|
|
|
Textarea::make('body')
|
|
->label(__('panel.replies.fields.body'))
|
|
->helperText(fn (Application $record) => __('panel.replies.in_language', [
|
|
'language' => config("locales.supported.{$record->locale}.native", $record->locale),
|
|
]))
|
|
->required()
|
|
->rows(12),
|
|
])
|
|
->action(function (Application $record, array $data, $livewire): void {
|
|
$reply = Replier::reply($record, $data['subject'], $data['body'], Auth::user());
|
|
|
|
if ($reply->channel === ApplicationReply::MAILTO) {
|
|
$livewire->js('window.location.href = '.json_encode(Replier::mailto($record, $data['subject'], $data['body'])));
|
|
}
|
|
|
|
Notification::make()
|
|
->title($reply->channel === ApplicationReply::MAIL ? __('panel.replies.sent') : __('panel.replies.recorded'))
|
|
->success()
|
|
->send();
|
|
}),
|
|
|
|
// The way it always worked: an empty message in your own mail
|
|
// program, for an answer that fits no template.
|
|
Action::make('reply_elsewhere')
|
|
->label(__('panel.replies.elsewhere'))
|
|
->icon('heroicon-o-arrow-top-right-on-square')
|
|
->color('gray')
|
|
->url(fn (Application $record) => 'mailto:'.$record->email.'?subject='
|
|
.rawurlencode(__('panel.applications.reply_subject', [
|
|
'subject' => $record->subject(),
|
|
])))
|
|
->openUrlInNewTab(),
|
|
|
|
Action::make('notes')
|
|
->label(__('panel.applications.actions.notes'))
|
|
->icon('heroicon-o-pencil-square')
|
|
->color('gray')
|
|
->authorize('update')
|
|
->schema([
|
|
Textarea::make('notes')
|
|
->label(__('panel.applications.fields.notes'))
|
|
->helperText(__('panel.applications.fields.notes_hint'))
|
|
->rows(6),
|
|
])
|
|
->fillForm(fn (Application $record) => ['notes' => $record->notes])
|
|
->action(function (Application $record, array $data): void {
|
|
// Notes are outside the model's fillable list on purpose —
|
|
// nothing arriving from the public form may ever land in
|
|
// them — so they are written explicitly here.
|
|
$record->forceFill(['notes' => $data['notes']])->save();
|
|
|
|
Notification::make()
|
|
->title(__('panel.applications.saved'))
|
|
->success()
|
|
->send();
|
|
}),
|
|
|
|
...ApplicationsTable::statusActions(),
|
|
|
|
DeleteAction::make(),
|
|
RestoreAction::make(),
|
|
ForceDeleteAction::make(),
|
|
];
|
|
}
|
|
}
|