feat: add i18n support for Vietnamese and English
This commit is contained in:
@@ -21,13 +21,18 @@ class FeedbackResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChatBubbleLeftRight;
|
||||
|
||||
protected static ?string $pluralLabel = 'Feedbacks';
|
||||
protected static ?string $pluralLabel = 'app.resource_feedbacks';
|
||||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
public static function getPluralLabel(): ?string
|
||||
{
|
||||
return __('app.resource_feedbacks');
|
||||
}
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return 'Customer Care';
|
||||
return __('app.nav_customer_care');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
|
||||
@@ -29,7 +29,7 @@ class CreateFeedback extends CreateRecord
|
||||
$feedback->interactions()->create([
|
||||
'user_id' => auth()->id(),
|
||||
'type' => 'note',
|
||||
'content' => 'Feedback created via ' . ($feedback->feedbackChannel?->name ?? 'unknown channel'),
|
||||
'content' => __('feedback.created_via', ['channel' => $feedback->feedbackChannel?->name ?? __('feedback.unknown_channel')]),
|
||||
'changes' => ['status' => ['old' => null, 'new' => $feedback->status]],
|
||||
]);
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ class EditFeedback extends EditRecord
|
||||
protected function transferDepartmentAction(): Action
|
||||
{
|
||||
return Action::make('transferDepartment')
|
||||
->label('Transfer Department')
|
||||
->label(__('feedback.transfer_department'))
|
||||
->icon('heroicon-o-arrows-right-left')
|
||||
->color('warning')
|
||||
->visible(fn (): bool => auth()->user()->hasPermissionTo('transfer-department'))
|
||||
->form([
|
||||
Select::make('to_department_id')
|
||||
->label('Target Department')
|
||||
->label(__('feedback.target_department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->required()
|
||||
->searchable(),
|
||||
Select::make('new_assignee_id')
|
||||
->label('Assign To (optional)')
|
||||
->label(__('feedback.assign_to_optional'))
|
||||
->options(function (\Filament\Forms\Get $get) {
|
||||
$deptId = $get('to_department_id');
|
||||
if (! $deptId) {
|
||||
@@ -57,11 +57,11 @@ class EditFeedback extends EditRecord
|
||||
->searchable()
|
||||
->nullable(),
|
||||
Textarea::make('reason')
|
||||
->label('Reason')
|
||||
->label(__('feedback.reason'))
|
||||
->required()
|
||||
->rows(3),
|
||||
FileUpload::make('transfer_attachments')
|
||||
->label('Attachments')
|
||||
->label(__('app.attachments'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
@@ -92,7 +92,7 @@ class EditFeedback extends EditRecord
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Transferred successfully')
|
||||
->title(__('feedback.transferred_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
@@ -103,14 +103,14 @@ class EditFeedback extends EditRecord
|
||||
protected function closeTicketAction(): Action
|
||||
{
|
||||
return Action::make('closeTicket')
|
||||
->label('Close Ticket')
|
||||
->label(__('feedback.close_ticket'))
|
||||
->icon('heroicon-o-check-circle')
|
||||
->color('success')
|
||||
->visible(fn (): bool => $this->getRecord()->status !== 'closed' && auth()->user()->hasPermissionTo('close-ticket'))
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Close Ticket')
|
||||
->modalDescription('Bạn có chắc muốn đóng phiếu này? Hành động này KHÔNG thể hoàn tác.')
|
||||
->modalSubmitActionLabel('Close')
|
||||
->modalHeading(__('feedback.close_ticket'))
|
||||
->modalDescription(__('feedback.close_confirm'))
|
||||
->modalSubmitActionLabel(__('feedback.close_submit'))
|
||||
->action(function (): void {
|
||||
$feedback = $this->getRecord();
|
||||
$actor = auth()->user();
|
||||
@@ -121,7 +121,7 @@ class EditFeedback extends EditRecord
|
||||
$closingService->close($feedback, $actor);
|
||||
|
||||
Notification::make()
|
||||
->title('Ticket closed successfully')
|
||||
->title(__('feedback.closed_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'interactions';
|
||||
|
||||
protected static ?string $title = 'Interaction History';
|
||||
protected static ?string $title = 'feedback.interaction_history';
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
@@ -35,9 +35,9 @@ class InteractionsRelationManager extends RelationManager
|
||||
->components([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'note' => 'Note',
|
||||
'status_change' => 'Status Change',
|
||||
'assignment' => 'Assignment / Forward',
|
||||
'note' => __('feedback.interaction_type_note'),
|
||||
'status_change' => __('feedback.interaction_type_status_change'),
|
||||
'assignment' => __('feedback.interaction_type_assignment'),
|
||||
])
|
||||
->default('note')
|
||||
->required()
|
||||
@@ -52,28 +52,28 @@ class InteractionsRelationManager extends RelationManager
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('new_status')
|
||||
->label('New Status')
|
||||
->label(__('app.new_status'))
|
||||
->options(fn (): array => auth()->user()->hasPermissionTo('close-ticket')
|
||||
? ['pending' => 'Pending', 'processing' => 'Processing', 'resolved' => 'Resolved', 'closed' => 'Closed']
|
||||
: ['pending' => 'Pending', 'processing' => 'Processing', 'resolved' => 'Resolved'],
|
||||
? ['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('Assign To')
|
||||
->label(__('app.assign_to'))
|
||||
->options(User::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
||||
|
||||
Select::make('department_id')
|
||||
->label('Target Department')
|
||||
->label(__('app.target_department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->visible(fn ($get): bool => $get('type') === 'assignment'),
|
||||
|
||||
FileUpload::make('attachments')
|
||||
->label('Attachments')
|
||||
->label(__('app.attachments'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
@@ -90,13 +90,13 @@ class InteractionsRelationManager extends RelationManager
|
||||
->dateTime('d/m/Y H:i')
|
||||
->sortable(),
|
||||
TextColumn::make('user.name')
|
||||
->label('By'),
|
||||
->label(__('app.by')),
|
||||
TextColumn::make('type')
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'note' => 'Note',
|
||||
'status_change' => 'Status Change',
|
||||
'assignment' => 'Assignment',
|
||||
'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) {
|
||||
@@ -110,7 +110,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
->limit(80)
|
||||
->wrap(),
|
||||
TextColumn::make('changes')
|
||||
->label('Details')
|
||||
->label(__('app.details'))
|
||||
->formatStateUsing(function (?array $state): string {
|
||||
if (! $state) return '';
|
||||
$lines = [];
|
||||
@@ -123,7 +123,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
})
|
||||
->html(),
|
||||
TextColumn::make('attachments_count')
|
||||
->label('Files')
|
||||
->label(__('app.files'))
|
||||
->counts('attachments')
|
||||
->badge()
|
||||
->color(fn (int $state): string => $state > 0 ? 'primary' : 'gray'),
|
||||
@@ -131,9 +131,9 @@ class InteractionsRelationManager extends RelationManager
|
||||
->defaultSort('created_at', 'desc')
|
||||
->recordActions([
|
||||
Action::make('viewAttachments')
|
||||
->label('View Attachments')
|
||||
->label(__('feedback.view_attachments'))
|
||||
->icon('heroicon-o-paper-clip')
|
||||
->modalHeading('Attachments')
|
||||
->modalHeading(__('app.attachments'))
|
||||
->modalContent(function (FeedbackInteraction $record): \Illuminate\Contracts\View\View {
|
||||
$fileService = App::make(FileService::class);
|
||||
$attachments = $record->attachments;
|
||||
@@ -155,7 +155,7 @@ class InteractionsRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->label('Add Interaction')
|
||||
->label(__('feedback.add_interaction'))
|
||||
->icon('heroicon-o-plus')
|
||||
->mutateFormDataUsing(function (array $data, $livewire): array {
|
||||
$data['user_id'] = auth()->id();
|
||||
@@ -177,8 +177,8 @@ class InteractionsRelationManager extends RelationManager
|
||||
|
||||
if ($data['type'] === 'assignment' && isset($data['new_assignee'])) {
|
||||
$feedback = $livewire->getOwnerRecord();
|
||||
$oldAssignee = $feedback->assignedTo?->name ?? 'Unassigned';
|
||||
$newAssignee = User::find($data['new_assignee'])?->name ?? 'Unknown';
|
||||
$oldAssignee = $feedback->assignedTo?->name ?? __('app.unassigned');
|
||||
$newAssignee = User::find($data['new_assignee'])?->name ?? __('app.widget_unknown');
|
||||
$data['changes'] = [
|
||||
'assignment' => ['old' => $oldAssignee, 'new' => $newAssignee],
|
||||
];
|
||||
@@ -221,13 +221,13 @@ class InteractionsRelationManager extends RelationManager
|
||||
|
||||
if ($record->type === 'assignment') {
|
||||
Notification::make()
|
||||
->title('Feedback assigned successfully')
|
||||
->title(__('feedback.assigned_successfully'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
if ($record->type === 'status_change') {
|
||||
Notification::make()
|
||||
->title('Status updated successfully')
|
||||
->title(__('feedback.status_updated'))
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
@@ -20,8 +20,8 @@ class FeedbackForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Section::make('Customer & Product')
|
||||
->description('Select the customer and their associated product')
|
||||
Section::make(__('app.customer_product'))
|
||||
->description(__('app.select_customer_product'))
|
||||
->icon('heroicon-o-user-group')
|
||||
->schema([
|
||||
Select::make('customer_id')
|
||||
@@ -32,13 +32,13 @@ class FeedbackForm
|
||||
->columnSpan(1),
|
||||
|
||||
Toggle::make('is_general')
|
||||
->label('General feedback (not related to a product)')
|
||||
->label(__('app.general_feedback'))
|
||||
->default(false)
|
||||
->live()
|
||||
->columnSpan(1),
|
||||
|
||||
Select::make('customer_product_id')
|
||||
->label('Product')
|
||||
->label(__('app.product'))
|
||||
->searchable()
|
||||
->getSearchResultsUsing(function (string $search, $get): array {
|
||||
$customerId = $get('customer_id');
|
||||
@@ -62,7 +62,7 @@ class FeedbackForm
|
||||
->live(),
|
||||
|
||||
Select::make('contract_id')
|
||||
->label('Contract')
|
||||
->label(__('app.contract'))
|
||||
->visible(fn ($get): bool => ! $get('is_general') && filled($get('customer_product_id')))
|
||||
->options(function ($get): array {
|
||||
$customerProductId = $get('customer_product_id');
|
||||
@@ -110,8 +110,8 @@ class FeedbackForm
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Feedback Details')
|
||||
->description('Describe the customer feedback')
|
||||
Section::make(__('app.feedback_details'))
|
||||
->description(__('app.describe_feedback'))
|
||||
->icon('heroicon-o-chat-bubble-left-right')
|
||||
->schema([
|
||||
Select::make('feedback_channel_id')
|
||||
@@ -143,16 +143,16 @@ class FeedbackForm
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Assignment & Status')
|
||||
->description('Assign handler and set initial status')
|
||||
Section::make(__('app.assignment_status'))
|
||||
->description(__('app.assign_handler'))
|
||||
->icon('heroicon-o-arrow-path-rounded-square')
|
||||
->schema([
|
||||
Select::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'resolved' => 'Resolved',
|
||||
'closed' => 'Closed',
|
||||
'pending' => __('app.status_pending'),
|
||||
'processing' => __('app.status_processing'),
|
||||
'resolved' => __('app.status_resolved'),
|
||||
'closed' => __('app.status_closed'),
|
||||
])
|
||||
->default('pending')
|
||||
->required()
|
||||
@@ -165,35 +165,35 @@ class FeedbackForm
|
||||
->columnSpan(1),
|
||||
|
||||
Select::make('current_department_id')
|
||||
->label('Department')
|
||||
->label(__('app.department'))
|
||||
->options(Department::pluck('name', 'id')->toArray())
|
||||
->searchable()
|
||||
->nullable()
|
||||
->columnSpan(1),
|
||||
|
||||
Toggle::make('is_escalated')
|
||||
->label('Escalated')
|
||||
->label(__('app.escalated'))
|
||||
->visible(fn (): bool => auth()->user()?->hasPermissionTo('close-ticket') ?? false)
|
||||
->columnSpan(1),
|
||||
])
|
||||
->columns(2),
|
||||
|
||||
Section::make('Attachments')
|
||||
->description('Upload files related to this feedback')
|
||||
Section::make(__('app.attachments'))
|
||||
->description(__('app.upload_files'))
|
||||
->icon('heroicon-o-paper-clip')
|
||||
->schema([
|
||||
Select::make('attachment_collection')
|
||||
->label('Collection')
|
||||
->label(__('app.collection'))
|
||||
->options([
|
||||
'general' => 'General',
|
||||
'proof_of_resolution' => 'Proof of Resolution',
|
||||
'customer_evidence' => 'Customer Evidence',
|
||||
'general' => __('app.collection_general'),
|
||||
'proof_of_resolution' => __('app.collection_proof_of_resolution'),
|
||||
'customer_evidence' => __('app.collection_customer_evidence'),
|
||||
])
|
||||
->default('general')
|
||||
->columnSpan(1),
|
||||
|
||||
FileUpload::make('attachments')
|
||||
->label('Files')
|
||||
->label(__('app.files'))
|
||||
->multiple()
|
||||
->disk('local')
|
||||
->directory('feedback-attachments')
|
||||
|
||||
@@ -33,13 +33,13 @@ class FeedbackTable
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('customerProduct.product.name')
|
||||
->label('Product')
|
||||
->placeholder('General')
|
||||
->label(__('app.product'))
|
||||
->placeholder(__('app.general'))
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('contract.type')
|
||||
->label('Contract')
|
||||
->label(__('app.contract'))
|
||||
->badge()
|
||||
->formatStateUsing(fn (?string $state): string => $state ? \App\Enums\ContractType::from($state)->label() : '-')
|
||||
->color(fn (?string $state): string => match ($state) {
|
||||
@@ -52,7 +52,7 @@ class FeedbackTable
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('feedbackChannel.name')
|
||||
->label('Channel')
|
||||
->label(__('app.channel'))
|
||||
->badge()
|
||||
->color('gray'),
|
||||
|
||||
@@ -72,22 +72,22 @@ class FeedbackTable
|
||||
}),
|
||||
|
||||
TextColumn::make('currentDepartment.name')
|
||||
->label('Department')
|
||||
->label(__('app.department'))
|
||||
->badge()
|
||||
->color('info')
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('assignedTo.name')
|
||||
->label('Assigned To')
|
||||
->label(__('app.assigned_to'))
|
||||
->toggleable(),
|
||||
|
||||
IconColumn::make('is_escalated')
|
||||
->label('Escalated')
|
||||
->label(__('app.escalated'))
|
||||
->boolean()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->label(__('app.created'))
|
||||
->since()
|
||||
->sortable()
|
||||
->toggleable()
|
||||
@@ -96,20 +96,20 @@ class FeedbackTable
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'processing' => 'Processing',
|
||||
'resolved' => 'Resolved',
|
||||
'closed' => 'Closed',
|
||||
'pending' => __('app.status_pending'),
|
||||
'processing' => __('app.status_processing'),
|
||||
'resolved' => __('app.status_resolved'),
|
||||
'closed' => __('app.status_closed'),
|
||||
]),
|
||||
SelectFilter::make('feedback_channel_id')
|
||||
->relationship('feedbackChannel', 'name')
|
||||
->label('Channel'),
|
||||
->label(__('app.channel')),
|
||||
SelectFilter::make('current_department_id')
|
||||
->relationship('currentDepartment', 'name')
|
||||
->label('Department'),
|
||||
->label(__('app.department')),
|
||||
SelectFilter::make('assigned_to')
|
||||
->relationship('assignedTo', 'name')
|
||||
->label('Assigned To'),
|
||||
->label(__('app.assigned_to')),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make(),
|
||||
|
||||
Reference in New Issue
Block a user