266 lines
12 KiB
PHP
266 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources\Feedback\RelationManagers;
|
|
|
|
use App\Models\Department;
|
|
use App\Models\FeedbackInteraction;
|
|
use App\Models\User;
|
|
use App\Services\ClosingService;
|
|
use App\Services\FileService;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\CreateAction;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Filament\Forms\Components\FileUpload;
|
|
use Filament\Forms\Components\RichEditor;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Resources\RelationManagers\RelationManager;
|
|
use Filament\Schemas\Schema;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Support\Facades\App;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Symfony\Component\HttpKernel\Exception\HttpException;
|
|
|
|
class InteractionsRelationManager extends RelationManager
|
|
{
|
|
protected static string $relationship = 'interactions';
|
|
|
|
protected static ?string $title = 'feedback.interaction_history';
|
|
|
|
public function form(Schema $schema): Schema
|
|
{
|
|
return $schema
|
|
->components([
|
|
Select::make('type')
|
|
->options([
|
|
'note' => __('feedback.interaction_type_note'),
|
|
'status_change' => __('feedback.interaction_type_status_change'),
|
|
'assignment' => __('feedback.interaction_type_assignment'),
|
|
])
|
|
->default('note')
|
|
->required()
|
|
->live(),
|
|
|
|
TextInput::make('title')
|
|
->maxLength(255)
|
|
->visible(fn ($get): bool => $get('type') === 'note'),
|
|
|
|
RichEditor::make('content')
|
|
->required()
|
|
->columnSpanFull(),
|
|
|
|
Select::make('new_status')
|
|
->label(__('app.new_status'))
|
|
->options(fn (): array => auth()->user()->hasPermissionTo('close-ticket')
|
|
? ['pending' => __('app.status_pending'), 'processing' => __('app.status_processing'), 'resolved' => __('app.status_resolved'), 'closed' => __('app.status_closed')]
|
|
: ['pending' => __('app.status_pending'), 'processing' => __('app.status_processing'), 'resolved' => __('app.status_resolved')],
|
|
)
|
|
->visible(fn ($get): bool => $get('type') === 'status_change'),
|
|
|
|
Select::make('new_assignee')
|
|
->label(__('app.assign_to'))
|
|
->options(User::pluck('name', 'id')->toArray())
|
|
->searchable()
|
|
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
|
|
|
Select::make('department_id')
|
|
->label(__('app.target_department'))
|
|
->options(Department::pluck('name', 'id')->toArray())
|
|
->searchable()
|
|
->nullable()
|
|
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
|
|
|
FileUpload::make('attachments')
|
|
->label(__('app.attachments'))
|
|
->multiple()
|
|
->disk('public')
|
|
->directory('feedback-attachments')
|
|
->acceptedFileTypes(['image/jpeg', 'image/png', 'application/pdf', 'video/mp4', 'video/quicktime'])
|
|
->maxSize(51200) // 50MB in KB
|
|
->columnSpanFull(),
|
|
]);
|
|
}
|
|
|
|
public function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->recordTitleAttribute('content')
|
|
->columns([
|
|
TextColumn::make('created_at')
|
|
->dateTime('d/m/Y H:i')
|
|
->sortable(),
|
|
TextColumn::make('user.name')
|
|
->label(__('app.by')),
|
|
TextColumn::make('type')
|
|
->badge()
|
|
->formatStateUsing(fn (string $state): string => match ($state) {
|
|
'note' => __('feedback.interaction_type_note'),
|
|
'status_change' => __('feedback.interaction_type_status_change'),
|
|
'assignment' => __('feedback.interaction_type_assignment'),
|
|
default => $state,
|
|
})
|
|
->color(fn (string $state): string => match ($state) {
|
|
'note' => 'gray',
|
|
'status_change' => 'info',
|
|
'assignment' => 'warning',
|
|
default => 'gray',
|
|
}),
|
|
TextColumn::make('content')
|
|
->html()
|
|
->limit(80)
|
|
->wrap(),
|
|
TextColumn::make('changes')
|
|
->label(__('app.details'))
|
|
->formatStateUsing(function (?array $state): string {
|
|
if (! $state) return '';
|
|
$lines = [];
|
|
foreach ($state as $field => $values) {
|
|
if (is_array($values)) {
|
|
$lines[] = "<b>{$field}</b>: {$values['old']} → {$values['new']}";
|
|
}
|
|
}
|
|
return implode('<br>', $lines);
|
|
})
|
|
->html(),
|
|
TextColumn::make('attachments_count')
|
|
->label(__('app.files'))
|
|
->counts('attachments')
|
|
->badge()
|
|
->color(fn (int $state): string => $state > 0 ? 'primary' : 'gray'),
|
|
])
|
|
->defaultSort('created_at', 'desc')
|
|
->recordActions([
|
|
Action::make('viewAttachments')
|
|
->label(__('feedback.view_attachments'))
|
|
->icon('heroicon-o-paper-clip')
|
|
->modalHeading(__('app.attachments'))
|
|
->modalContent(function (FeedbackInteraction $record): \Illuminate\Contracts\View\View {
|
|
$fileService = App::make(FileService::class);
|
|
$attachments = $record->attachments;
|
|
$links = $attachments->map(function ($a) use ($fileService) {
|
|
return [
|
|
'name' => $a->name,
|
|
'url' => $fileService->getTemporaryUrl($a, 60),
|
|
'size' => $a->size ? round($a->size / 1024, 1) . ' KB' : 'N/A',
|
|
'collection' => $a->collection,
|
|
'mime_type' => $a->mime_type,
|
|
];
|
|
});
|
|
|
|
return view('filament.resources.feedback.attachment-links', [
|
|
'links' => $links,
|
|
]);
|
|
})
|
|
->visible(fn (FeedbackInteraction $record): bool => $record->attachments()->count() > 0),
|
|
])
|
|
->headerActions([
|
|
CreateAction::make()
|
|
->label(__('feedback.add_interaction'))
|
|
->icon('heroicon-o-plus')
|
|
->mutateFormDataUsing(function (array $data, $livewire): array {
|
|
$data['user_id'] = auth()->id();
|
|
$data['feedback_id'] = $livewire->getOwnerRecord()->id;
|
|
|
|
if ($data['type'] === 'status_change' && isset($data['new_status'])) {
|
|
$feedback = $livewire->getOwnerRecord();
|
|
|
|
if ($data['new_status'] === 'closed') {
|
|
$this->handleClosingViaService($feedback, auth()->user(), $data);
|
|
}
|
|
|
|
$data['changes'] = [
|
|
'status' => ['old' => $feedback->status, 'new' => $data['new_status']],
|
|
];
|
|
$feedback->update(['status' => $data['new_status']]);
|
|
unset($data['new_status']);
|
|
}
|
|
|
|
if ($data['type'] === 'assignment' && isset($data['new_assignee'])) {
|
|
$feedback = $livewire->getOwnerRecord();
|
|
$oldAssignee = $feedback->assignedTo?->name ?? __('app.unassigned');
|
|
$newAssignee = User::find($data['new_assignee'])?->name ?? __('app.widget_unknown');
|
|
$data['changes'] = [
|
|
'assignment' => ['old' => $oldAssignee, 'new' => $newAssignee],
|
|
];
|
|
$update = ['assigned_to' => $data['new_assignee']];
|
|
if (! empty($data['department_id'])) {
|
|
$update['current_department_id'] = $data['department_id'];
|
|
$dept = Department::find($data['department_id']);
|
|
$data['changes']['department'] = [
|
|
'old' => $feedback->currentDepartment?->name ?? 'N/A',
|
|
'new' => $dept?->name ?? 'N/A',
|
|
];
|
|
}
|
|
$feedback->update($update);
|
|
unset($data['new_assignee'], $data['department_id']);
|
|
}
|
|
|
|
$data['_attachments'] = $data['attachments'] ?? [];
|
|
unset($data['attachments'], $data['title']);
|
|
return $data;
|
|
})
|
|
->after(function (FeedbackInteraction $record, array $data): void {
|
|
$attachments = $data['_attachments'] ?? [];
|
|
|
|
if (! empty($attachments)) {
|
|
$disk = Storage::disk('public');
|
|
foreach ($attachments as $path) {
|
|
if (! $disk->exists($path)) {
|
|
continue;
|
|
}
|
|
|
|
$record->attachments()->create([
|
|
'uuid' => (string) \Illuminate\Support\Str::uuid(),
|
|
'feedback_id' => $record->feedback_id,
|
|
'user_id' => auth()->id(),
|
|
'name' => basename($path),
|
|
'path' => $path,
|
|
'disk' => 'public',
|
|
'collection' => 'general',
|
|
'mime_type' => $disk->mimeType($path),
|
|
'size' => $disk->size($path),
|
|
]);
|
|
}
|
|
}
|
|
|
|
if ($record->type === 'assignment') {
|
|
Notification::make()
|
|
->title(__('feedback.assigned_successfully'))
|
|
->success()
|
|
->send();
|
|
}
|
|
if ($record->type === 'status_change') {
|
|
Notification::make()
|
|
->title(__('feedback.status_updated'))
|
|
->success()
|
|
->send();
|
|
}
|
|
}),
|
|
]);
|
|
}
|
|
|
|
protected function handleClosingViaService($feedback, $actor, array &$data): void
|
|
{
|
|
try {
|
|
$closingService = App::make(ClosingService::class);
|
|
$closingService->close($feedback, $actor);
|
|
} catch (HttpException $e) {
|
|
Notification::make()
|
|
->title($e->getMessage())
|
|
->danger()
|
|
->send();
|
|
unset($data['new_status']);
|
|
throw new \Filament\Exceptions\SilentActionException;
|
|
} catch (ValidationException $e) {
|
|
Notification::make()
|
|
->title(collect($e->errors())->flatten()->first())
|
|
->danger()
|
|
->send();
|
|
unset($data['new_status']);
|
|
throw new \Filament\Exceptions\SilentActionException;
|
|
}
|
|
}
|
|
}
|