Skip to content

Commit 7982831

Browse files
committed
feat: add feedback mechanism
1 parent 14461ab commit 7982831

18 files changed

Lines changed: 756 additions & 6 deletions

app/Exports/FeedbackExport.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Exports;
6+
7+
use App\Models\Feedback;
8+
use Illuminate\Database\Eloquent\Builder;
9+
use Maatwebsite\Excel\Concerns\Exportable;
10+
use Maatwebsite\Excel\Concerns\FromQuery;
11+
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
12+
use Maatwebsite\Excel\Concerns\WithHeadings;
13+
use Maatwebsite\Excel\Concerns\WithMapping;
14+
15+
class FeedbackExport implements FromQuery, ShouldAutoSize, WithHeadings, WithMapping
16+
{
17+
use Exportable;
18+
19+
public function query(): Builder
20+
{
21+
return Feedback::query()
22+
->with([
23+
'category',
24+
'subcategory',
25+
'county',
26+
'city',
27+
'user:id,full_name,role',
28+
])
29+
->latest('id');
30+
}
31+
32+
public function headings(): array
33+
{
34+
return [
35+
__('field.id'),
36+
__('field.county'),
37+
__('field.city'),
38+
__('field.user'),
39+
__('field.role'),
40+
__('field.date'),
41+
__('field.category'),
42+
__('field.subcategory'),
43+
__('field.notes'),
44+
];
45+
}
46+
47+
/**
48+
* @param Feedback $row
49+
*/
50+
public function map($row): array
51+
{
52+
return [
53+
$row->id,
54+
$row->county->name,
55+
$row->city->name,
56+
$row->user->full_name,
57+
$row->user->role?->getLabel(),
58+
$row->created_at->toDateString(),
59+
$row->category->name,
60+
$row->subcategory?->name,
61+
$row->description,
62+
];
63+
}
64+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback\Actions;
6+
7+
use App\Exports\FeedbackExport;
8+
use Filament\Actions\Action;
9+
use Filament\Support\Icons\Heroicon;
10+
11+
class ExportAction extends Action
12+
{
13+
public static function getDefaultName(): ?string
14+
{
15+
return 'export';
16+
}
17+
18+
protected function setUp(): void
19+
{
20+
parent::setUp();
21+
22+
$this->authorize('export');
23+
24+
$this->label(__('feedback.action.export'));
25+
26+
$this->icon(Heroicon::OutlinedArrowDownTray);
27+
28+
$this->color('gray');
29+
30+
$this->action(
31+
fn () => (new FeedbackExport)
32+
->download('sesizari-' . now()->toDateString() . '.xlsx')
33+
);
34+
}
35+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback;
6+
7+
use App\Filament\Resources\Feedback\Pages\ManageFeedback;
8+
use App\Filament\Resources\Feedback\Schemas\FeedbackForm;
9+
use App\Filament\Resources\Feedback\Schemas\FeedbackInfolist;
10+
use App\Filament\Resources\Feedback\Tables\FeedbackTable;
11+
use App\Models\Feedback;
12+
use Filament\Resources\Resource;
13+
use Filament\Schemas\Schema;
14+
use Filament\Tables\Table;
15+
16+
class FeedbackResource extends Resource
17+
{
18+
protected static ?string $model = Feedback::class;
19+
20+
protected static ?int $navigationSort = 7;
21+
22+
public static function shouldRegisterNavigation(): bool
23+
{
24+
return auth()->user()->isAdmin();
25+
}
26+
27+
public static function getModelLabel(): string
28+
{
29+
return __('feedback.label.singular');
30+
}
31+
32+
public static function getPluralModelLabel(): string
33+
{
34+
return __('feedback.label.plural');
35+
}
36+
37+
public static function form(Schema $schema): Schema
38+
{
39+
return FeedbackForm::configure($schema);
40+
}
41+
42+
public static function infolist(Schema $schema): Schema
43+
{
44+
return FeedbackInfolist::configure($schema);
45+
}
46+
47+
public static function table(Table $table): Table
48+
{
49+
return FeedbackTable::configure($table);
50+
}
51+
52+
public static function getPages(): array
53+
{
54+
return [
55+
'index' => ManageFeedback::route('/'),
56+
];
57+
}
58+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback\Pages;
6+
7+
use App\Filament\Resources\Feedback\Actions\ExportAction;
8+
use App\Filament\Resources\Feedback\FeedbackResource;
9+
use Filament\Actions\CreateAction;
10+
use Filament\Resources\Pages\ManageRecords;
11+
12+
class ManageFeedback extends ManageRecords
13+
{
14+
protected static string $resource = FeedbackResource::class;
15+
16+
protected function getHeaderActions(): array
17+
{
18+
return [
19+
ExportAction::make(),
20+
21+
CreateAction::make()
22+
->label(__('feedback.action.create')),
23+
];
24+
}
25+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback\Schemas;
6+
7+
use App\Models\FeedbackCategory;
8+
use App\Models\FeedbackSubcategory;
9+
use Filament\Forms\Components\Select;
10+
use Filament\Forms\Components\Textarea;
11+
use Filament\Schemas\Components\Utilities\Get;
12+
use Filament\Schemas\Components\Utilities\Set;
13+
use Filament\Schemas\Schema;
14+
15+
class FeedbackForm
16+
{
17+
public static function configure(Schema $schema): Schema
18+
{
19+
$subcategories = FeedbackSubcategory::allAsOptions();
20+
$activityCities = auth()->user()->activityCities
21+
->pluck('formatted_name', 'id');
22+
23+
return $schema
24+
->components([
25+
Select::make('category_id')
26+
->label(__('field.category'))
27+
->options(fn () => FeedbackCategory::query()->pluck('name', 'id'))
28+
->live()
29+
->afterStateUpdated(fn (Set $set) => $set('subcategory_id', null))
30+
->required(),
31+
32+
Select::make('subcategory_id')
33+
->label(__('field.subcategory'))
34+
->options(fn (Get $get) => $subcategories->get($get('category_id')))
35+
->visible(fn (Get $get): bool => $subcategories->has($get('category_id')))
36+
->required(fn (Get $get): bool => $subcategories->has($get('category_id'))),
37+
38+
Textarea::make('description')
39+
->label(__('field.notes'))
40+
->required()
41+
->rows(4)
42+
->columnSpanFull(),
43+
44+
Select::make('city_id')
45+
->label(__('field.city'))
46+
->placeholder(__('placeholder.city'))
47+
->allowHtml()
48+
->searchable()
49+
->required()
50+
->options($activityCities)
51+
->in($activityCities->keys()),
52+
]);
53+
}
54+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback\Schemas;
6+
7+
use App\Filament\Infolists\Components\Location;
8+
use App\Models\Feedback;
9+
use Filament\Infolists\Components\TextEntry;
10+
use Filament\Schemas\Schema;
11+
12+
class FeedbackInfolist
13+
{
14+
public static function configure(Schema $schema): Schema
15+
{
16+
return $schema
17+
->components([
18+
TextEntry::make('category.name')
19+
->label(__('field.category'))
20+
->columnSpanFull(),
21+
22+
TextEntry::make('subcategory.name')
23+
->label(__('field.subcategory'))
24+
->visible(fn (Feedback $record): bool => filled($record->subcategory_id))
25+
->columnSpanFull(),
26+
27+
TextEntry::make('description')
28+
->label(__('field.notes'))
29+
->columnSpanFull(),
30+
31+
Location::make()
32+
->contained(false),
33+
34+
TextEntry::make('created_at')
35+
->label(__('field.date'))
36+
->date(),
37+
38+
TextEntry::make('user.full_name')
39+
->label(__('field.user'))
40+
->helperText(fn (Feedback $record): string => $record->user->role->getLabel())
41+
->visible(fn (): bool => auth()->user()->isAdmin()),
42+
43+
TextEntry::make('county.name')
44+
->label(__('field.county'))
45+
->visible(fn (): bool => auth()->user()->isAdmin()),
46+
47+
TextEntry::make('city.name')
48+
->label(__('field.city'))
49+
->visible(fn (): bool => auth()->user()->isAdmin()),
50+
]);
51+
}
52+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Filament\Resources\Feedback\Tables;
6+
7+
use App\Models\County;
8+
use App\Models\Feedback;
9+
use Filament\Actions\DeleteAction;
10+
use Filament\Actions\EditAction;
11+
use Filament\Actions\ViewAction;
12+
use Filament\Tables\Columns\TextColumn;
13+
use Filament\Tables\Filters\SelectFilter;
14+
use Filament\Tables\Table;
15+
use Illuminate\Database\Eloquent\Builder;
16+
use Malzariey\FilamentDaterangepickerFilter\Filters\DateRangeFilter;
17+
18+
class FeedbackTable
19+
{
20+
public static function configure(Table $table): Table
21+
{
22+
return $table
23+
->modifyQueryUsing(fn (Builder $query) => $query->with(['user', 'category', 'subcategory', 'county', 'city']))
24+
->columns([
25+
TextColumn::make('id')
26+
->label(__('field.id'))
27+
->prefix('#')
28+
->sortable()
29+
->searchable()
30+
->toggleable(),
31+
32+
TextColumn::make('user.full_name')
33+
->label(__('field.user'))
34+
->toggleable(fn (TextColumn $column) => ! $column->isHidden())
35+
->description(fn (Feedback $record): string => $record->user->role->getLabel())
36+
->hidden(fn (): bool => auth()->user()->isNurseOrMediator()),
37+
38+
TextColumn::make('category.name')
39+
->label(__('field.category'))
40+
->limit()
41+
->sortable()
42+
->toggleable(),
43+
44+
TextColumn::make('subcategory.name')
45+
->label(__('field.subcategory'))
46+
->limit()
47+
->toggleable(),
48+
49+
TextColumn::make('county.name')
50+
->label(__('field.county'))
51+
->hidden(fn (): bool => auth()->user()->isNurseOrMediator())
52+
->toggleable(),
53+
54+
TextColumn::make('city.name')
55+
->label(__('field.city'))
56+
->toggleable(),
57+
58+
TextColumn::make('created_at')
59+
->label(__('field.date'))
60+
->size('sm')
61+
->date()
62+
->sortable()
63+
->toggleable(),
64+
])
65+
->filters([
66+
SelectFilter::make('category')
67+
->label(__('field.category'))
68+
->relationship('category', 'name'),
69+
70+
SelectFilter::make('subcategory')
71+
->label(__('field.subcategory'))
72+
->relationship('subcategory', 'name'),
73+
74+
SelectFilter::make('county_id')
75+
->label(__('field.county'))
76+
->options(County::cachedList())
77+
->visible(fn (): bool => auth()->user()->isAdmin()),
78+
79+
SelectFilter::make('user')
80+
->label(__('field.user'))
81+
->relationship('user', 'full_name', fn (Builder $query) => $query->onlyNursesAndMediators())
82+
->multiple()
83+
->hidden(fn (): bool => auth()->user()->isNurseOrMediator()),
84+
85+
DateRangeFilter::make('created_at')
86+
->label(__('field.date')),
87+
])
88+
->recordActions([
89+
ViewAction::make()
90+
->iconButton(),
91+
92+
EditAction::make()
93+
->iconButton(),
94+
95+
DeleteAction::make()
96+
->iconButton(),
97+
])
98+
->defaultSort('id', 'desc');
99+
}
100+
}

0 commit comments

Comments
 (0)