Enhance ActivityLogsTable by updating event and causer filters to utilize the Activity model directly. Introduce a new causer_id filter with improved query logic for better user experience. Add new methods in EmployeeStatisticsService for applying table aggregates, and update localization files to include new terms for employee statistics in English, Russian, and Turkmen.

This commit is contained in:
Mekan1206
2026-08-02 22:31:59 +05:00
parent 9e623d2d29
commit 0237c2b9ef
8 changed files with 288 additions and 4 deletions

View File

@@ -17,6 +17,7 @@ use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Spatie\Activitylog\Models\Activity;
class ActivityLogsTable class ActivityLogsTable
{ {
@@ -67,16 +68,29 @@ class ActivityLogsTable
]), ]),
SelectFilter::make('event') SelectFilter::make('event')
->label(__('hr.fields.event')) ->label(__('hr.fields.event'))
->options(fn (): array => \Spatie\Activitylog\Models\Activity::query() ->options(fn (): array => Activity::query()
->whereNotNull('event') ->whereNotNull('event')
->distinct() ->distinct()
->pluck('event', 'event') ->pluck('event', 'event')
->all()), ->all()),
SelectFilter::make('causer') SelectFilter::make('causer_id')
->label(__('hr.fields.causer')) ->label(__('hr.fields.causer'))
->relationship('causer', 'name') ->options(fn (): array => User::query()
->whereIn('id', Activity::query()
->where('causer_type', User::class)
->whereNotNull('causer_id')
->distinct()
->pluck('causer_id'))
->orderBy('name')
->pluck('name', 'id')
->all())
->searchable() ->searchable()
->preload(), ->query(fn (Builder $query, array $data): Builder => $query->when(
$data['value'] ?? null,
fn (Builder $query, $causerId): Builder => $query
->where('causer_id', $causerId)
->where('causer_type', User::class),
)),
Filter::make('created_at') Filter::make('created_at')
->label(__('hr.fields.date')) ->label(__('hr.fields.date'))
->schema([ ->schema([

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\EmployeeStats;
use App\Enums\NavigationGroup;
use App\Filament\Concerns\HasHrResourceLabels;
use App\Filament\Resources\EmployeeStats\Pages\ListEmployeeStats;
use App\Filament\Resources\EmployeeStats\Tables\EmployeeStatsTable;
use App\Models\Employee;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Facades\Gate;
use UnitEnum;
class EmployeeStatsResource extends Resource
{
use HasHrResourceLabels;
protected static ?string $model = Employee::class;
protected static function hrLabelKeys(): array
{
return [
'model' => 'employee_stat',
'plural' => 'stats',
'navigation' => 'stats',
];
}
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChartBar;
protected static string|UnitEnum|null $navigationGroup = NavigationGroup::Employees;
protected static ?int $navigationSort = 2;
protected static ?string $slug = 'stats';
protected static ?string $recordTitleAttribute = 'full_name';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return $schema;
}
public static function infolist(Schema $schema): Schema
{
return $schema;
}
public static function table(Table $table): Table
{
return EmployeeStatsTable::configure($table);
}
public static function getRelations(): array
{
return [];
}
public static function getPages(): array
{
return [
'index' => ListEmployeeStats::route('/'),
];
}
public static function canViewAny(): bool
{
return Gate::check('viewAny', Employee::class);
}
public static function canView(Model $record): bool
{
return Gate::check('view', $record);
}
public static function canCreate(): bool
{
return false;
}
public static function canEdit(Model $record): bool
{
return false;
}
public static function canDelete(Model $record): bool
{
return false;
}
public static function canForceDelete(Model $record): bool
{
return false;
}
public static function canRestore(Model $record): bool
{
return false;
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\EmployeeStats\Pages;
use App\Filament\Resources\EmployeeStats\EmployeeStatsResource;
use Filament\Resources\Pages\ListRecords;
class ListEmployeeStats extends ListRecords
{
protected static string $resource = EmployeeStatsResource::class;
protected function getHeaderActions(): array
{
return [];
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\EmployeeStats\Tables;
use App\Enums\EmploymentStatus;
use App\Filament\Resources\Employees\EmployeeResource;
use App\Models\Employee;
use App\Services\Employee\EmployeeStatisticsService;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
class EmployeeStatsTable
{
public static function configure(Table $table): Table
{
return $table
->modifyQueryUsing(fn ($query) => app(EmployeeStatisticsService::class)->applyTableAggregates($query))
->columns([
TextColumn::make('employee_number')
->label(__('hr.fields.number'))
->searchable()
->sortable(),
TextColumn::make('full_name')
->label(__('hr.fields.full_name'))
->searchable()
->sortable(),
TextColumn::make('department.name')
->label(__('hr.fields.department'))
->badge()
->color('info')
->searchable()
->sortable(),
TextColumn::make('position.name')
->label(__('hr.fields.position'))
->badge()
->color('primary')
->searchable()
->sortable(),
TextColumn::make('hire_date')
->label(__('hr.fields.hire_date'))
->date()
->sortable(),
TextColumn::make('tenure')
->label(__('hr.fields.tenure'))
->state(fn (Employee $record): string => $record->hire_date
? $record->hire_date->diffForHumans(now(), true)
: '—'),
TextColumn::make('annual_vacation_days_taken')
->label(__('hr.fields.vacation_taken_days'))
->numeric()
->sortable(),
TextColumn::make('vacation_remaining')
->label(__('hr.fields.vacation_remaining_days'))
->state(fn (Employee $record): int => (int) config('hr.annual_leave_days') - (int) ($record->annual_vacation_days_taken ?? 0))
->numeric()
->color('success'),
TextColumn::make('sick_leaves_count')
->label(__('hr.fields.sick_leave_records'))
->numeric()
->sortable(),
TextColumn::make('unpaid_leaves_count')
->label(__('hr.fields.unpaid_leave_records'))
->numeric()
->sortable(),
TextColumn::make('disciplinary_reports_count')
->label(__('hr.fields.disciplinary_reports'))
->numeric()
->sortable(),
TextColumn::make('explanations_count')
->label(__('hr.fields.explanations'))
->numeric()
->sortable(),
TextColumn::make('bonuses_sum_amount')
->label(__('hr.fields.total_bonuses'))
->money('TMT')
->sortable(),
TextColumn::make('gifts_count')
->label(__('hr.fields.gifts_received'))
->numeric()
->sortable(),
])
->filters([
SelectFilter::make('department_id')
->label(__('hr.fields.department'))
->relationship('department', 'name')
->searchable()
->preload(),
SelectFilter::make('position_id')
->label(__('hr.fields.position'))
->relationship('position', 'name')
->searchable()
->preload(),
SelectFilter::make('employment_status')
->label(__('hr.fields.employment_status'))
->options(EmploymentStatus::class),
])
->recordActions([
ViewAction::make()
->url(fn (Employee $record): string => EmployeeResource::getUrl('view', ['record' => $record])),
]);
}
}

View File

@@ -6,8 +6,10 @@ namespace App\Services\Employee;
use App\DTOs\EmployeeSummaryDto; use App\DTOs\EmployeeSummaryDto;
use App\Enums\ApprovalStatus; use App\Enums\ApprovalStatus;
use App\Enums\VacationType;
use App\Models\Employee; use App\Models\Employee;
use App\Services\Vacation\VacationBalanceService; use App\Services\Vacation\VacationBalanceService;
use Illuminate\Database\Eloquent\Builder;
class EmployeeStatisticsService class EmployeeStatisticsService
{ {
@@ -15,6 +17,25 @@ class EmployeeStatisticsService
private readonly VacationBalanceService $vacationBalanceService, private readonly VacationBalanceService $vacationBalanceService,
) {} ) {}
/**
* @param Builder<Employee> $query
* @return Builder<Employee>
*/
public function applyTableAggregates(Builder $query, ?int $year = null): Builder
{
$year ??= (int) now()->year;
return $query
->with(['department', 'position'])
->withCount(['sickLeaves', 'unpaidLeaves', 'disciplinaryReports', 'explanations', 'gifts'])
->withSum('bonuses', 'amount')
->withSum(['vacations as annual_vacation_days_taken' => fn (Builder $vacationQuery): Builder => $vacationQuery
->where('type', VacationType::Annual)
->where('status', ApprovalStatus::Approved)
->whereYear('start_date', $year),
], 'days');
}
public function summary(Employee $employee): EmployeeSummaryDto public function summary(Employee $employee): EmployeeSummaryDto
{ {
return new EmployeeSummaryDto( return new EmployeeSummaryDto(

View File

@@ -24,6 +24,8 @@ return [
'shifts' => 'Shifts', 'shifts' => 'Shifts',
'employee' => 'Employee', 'employee' => 'Employee',
'employees' => 'Employees', 'employees' => 'Employees',
'employee_stat' => 'Employee Stat',
'stats' => 'Stats',
'vacation' => 'Vacation', 'vacation' => 'Vacation',
'vacations' => 'Vacations', 'vacations' => 'Vacations',
'sick_leave' => 'Sick Leave', 'sick_leave' => 'Sick Leave',

View File

@@ -24,6 +24,8 @@ return [
'shifts' => 'Смены', 'shifts' => 'Смены',
'employee' => 'Сотрудник', 'employee' => 'Сотрудник',
'employees' => 'Сотрудники', 'employees' => 'Сотрудники',
'employee_stat' => 'Статистика сотрудника',
'stats' => 'Статистика',
'vacation' => 'Отпуск', 'vacation' => 'Отпуск',
'vacations' => 'Отпуска', 'vacations' => 'Отпуска',
'sick_leave' => 'Больничный', 'sick_leave' => 'Больничный',

View File

@@ -24,6 +24,8 @@ return [
'shifts' => 'Smenalar', 'shifts' => 'Smenalar',
'employee' => 'Işgär', 'employee' => 'Işgär',
'employees' => 'Işgärler', 'employees' => 'Işgärler',
'employee_stat' => 'Işgär statistikasy',
'stats' => 'Statistika',
'vacation' => 'Dynç alyş', 'vacation' => 'Dynç alyş',
'vacations' => 'Dynç alyşlar', 'vacations' => 'Dynç alyşlar',
'sick_leave' => 'Kesel rugsady', 'sick_leave' => 'Kesel rugsady',