base commit

This commit is contained in:
Mekan1206
2026-07-30 17:24:40 +05:00
commit a794dacd39
345 changed files with 29597 additions and 0 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[{compose,docker-compose}.{yml,yaml}]
indent_size = 4

65
.env.example Normal file
View File

@@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.codex
/.cursor/
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/fonts-manifest.dev.json
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
_ide_helper.php
Homestead.json
Homestead.yaml
Thumbs.db

2
.npmrc Normal file
View File

@@ -0,0 +1,2 @@
ignore-scripts=true
audit=true

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>
## About Laravel
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
Laravel is accessible, powerful, and provides tools required for large, robust applications.
## Learning Laravel
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.
In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals.
## Agentic Development
Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow:
```bash
composer require laravel/boost --dev
php artisan boost:install
```
Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices.
## Contributing
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
## License
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\DTOs;
use App\Models\Vacation;
use Illuminate\Support\Collection;
readonly class EmployeeSummaryDto
{
/**
* @param Collection<int, Vacation> $upcomingLeave
*/
public function __construct(
public int $vacationTaken,
public int $vacationRemaining,
public int $sickLeaveCount,
public int $reportsCount,
public float $bonusesTotal,
public int $giftsCount,
public Collection $upcomingLeave,
) {}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Import;
readonly class BonusRowDto
{
public function __construct(
public string $employeeNumber,
public ?string $bonusDate,
public ?string $bonusType,
public ?float $amount,
public ?string $reason,
) {}
/**
* @param array<string, mixed> $row
*/
public static function fromMappedRow(array $row): self
{
return new self(
employeeNumber: (string) ($row['employee_number'] ?? ''),
bonusDate: self::nullableString($row['bonus_date'] ?? null),
bonusType: self::nullableString($row['bonus_type'] ?? null),
amount: isset($row['amount']) && $row['amount'] !== '' ? (float) $row['amount'] : null,
reason: self::nullableString($row['reason'] ?? null),
);
}
private static function nullableString(mixed $value): ?string
{
if ($value === null || $value === '') {
return null;
}
return (string) $value;
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Import;
readonly class EmployeeRowDto
{
public function __construct(
public string $employeeNumber,
public string $fullName,
public ?string $nationalId,
public ?string $gender,
public ?string $birthDate,
public ?string $phone,
public ?string $address,
public ?string $department,
public ?string $position,
public ?string $shift,
public ?string $employmentStatus,
public ?string $hireDate,
public ?string $notes,
) {}
/**
* @param array<string, mixed> $row
*/
public static function fromMappedRow(array $row): self
{
return new self(
employeeNumber: (string) ($row['employee_number'] ?? ''),
fullName: (string) ($row['full_name'] ?? ''),
nationalId: self::nullableString($row['national_id'] ?? null),
gender: self::nullableString($row['gender'] ?? null),
birthDate: self::nullableString($row['birth_date'] ?? null),
phone: self::nullableString($row['phone'] ?? null),
address: self::nullableString($row['address'] ?? null),
department: self::nullableString($row['department'] ?? null),
position: self::nullableString($row['position'] ?? null),
shift: self::nullableString($row['shift'] ?? null),
employmentStatus: self::nullableString($row['employment_status'] ?? null),
hireDate: self::nullableString($row['hire_date'] ?? null),
notes: self::nullableString($row['notes'] ?? null),
);
}
private static function nullableString(mixed $value): ?string
{
if ($value === null || $value === '') {
return null;
}
return (string) $value;
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Import;
readonly class ImportResultDto
{
/**
* @param array<int, string> $errors
*/
public function __construct(
public int $total,
public int $imported,
public int $skipped,
public int $failed,
public array $errors = [],
) {}
/**
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'total' => $this->total,
'imported' => $this->imported,
'skipped' => $this->skipped,
'failed' => $this->failed,
'errors' => $this->errors,
];
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Import;
readonly class ReportRowDto
{
public function __construct(
public string $employeeNumber,
public ?string $reportDate,
public ?string $title,
public ?string $description,
public ?string $severity,
) {}
/**
* @param array<string, mixed> $row
*/
public static function fromMappedRow(array $row): self
{
return new self(
employeeNumber: (string) ($row['employee_number'] ?? ''),
reportDate: self::nullableString($row['report_date'] ?? null),
title: self::nullableString($row['title'] ?? null),
description: self::nullableString($row['description'] ?? null),
severity: self::nullableString($row['severity'] ?? null),
);
}
private static function nullableString(mixed $value): ?string
{
if ($value === null || $value === '') {
return null;
}
return (string) $value;
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\DTOs\Import;
readonly class VacationRowDto
{
public function __construct(
public string $employeeNumber,
public ?string $type,
public ?string $startDate,
public ?string $endDate,
public ?string $returnDate,
public ?int $days,
public ?string $status,
public ?string $reason,
) {}
/**
* @param array<string, mixed> $row
*/
public static function fromMappedRow(array $row): self
{
return new self(
employeeNumber: (string) ($row['employee_number'] ?? ''),
type: self::nullableString($row['type'] ?? null),
startDate: self::nullableString($row['start_date'] ?? null),
endDate: self::nullableString($row['end_date'] ?? null),
returnDate: self::nullableString($row['return_date'] ?? null),
days: isset($row['days']) && $row['days'] !== '' ? (int) $row['days'] : null,
status: self::nullableString($row['status'] ?? null),
reason: self::nullableString($row['reason'] ?? null),
);
}
private static function nullableString(mixed $value): ?string
{
if ($value === null || $value === '') {
return null;
}
return (string) $value;
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum ApprovalStatus: string
{
case Pending = 'pending';
case Approved = 'approved';
case Rejected = 'rejected';
public function label(): string
{
return match ($this) {
self::Pending => 'Pending',
self::Approved => 'Approved',
self::Rejected => 'Rejected',
};
}
public function color(): string
{
return match ($this) {
self::Pending => 'warning',
self::Approved => 'success',
self::Rejected => 'danger',
};
}
public function icon(): string
{
return match ($this) {
self::Pending => 'heroicon-o-clock',
self::Approved => 'heroicon-o-check-badge',
self::Rejected => 'heroicon-o-x-mark',
};
}
}

43
app/Enums/BonusType.php Normal file
View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum BonusType: string
{
case Performance = 'performance';
case Holiday = 'holiday';
case Retention = 'retention';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::Performance => 'Performance',
self::Holiday => 'Holiday',
self::Retention => 'Retention',
self::Other => 'Other',
};
}
public function color(): string
{
return match ($this) {
self::Performance => 'success',
self::Holiday => 'primary',
self::Retention => 'info',
self::Other => 'gray',
};
}
public function icon(): string
{
return match ($this) {
self::Performance => 'heroicon-o-star',
self::Holiday => 'heroicon-o-gift',
self::Retention => 'heroicon-o-hand-raised',
self::Other => 'heroicon-o-banknotes',
};
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum DisciplinarySeverity: string
{
case Low = 'low';
case Medium = 'medium';
case High = 'high';
public function label(): string
{
return match ($this) {
self::Low => 'Low',
self::Medium => 'Medium',
self::High => 'High',
};
}
public function color(): string
{
return match ($this) {
self::Low => 'info',
self::Medium => 'warning',
self::High => 'danger',
};
}
public function icon(): string
{
return match ($this) {
self::Low => 'heroicon-o-information-circle',
self::Medium => 'heroicon-o-exclamation-triangle',
self::High => 'heroicon-o-shield-exclamation',
};
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum DocumentType: string
{
case Passport = 'passport';
case Contract = 'contract';
case MedicalCertificate = 'medical_certificate';
case EducationCertificate = 'education_certificate';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::Passport => 'Passport',
self::Contract => 'Contract',
self::MedicalCertificate => 'Medical Certificate',
self::EducationCertificate => 'Education Certificate',
self::Other => 'Other',
};
}
public function color(): string
{
return match ($this) {
self::Passport => 'primary',
self::Contract => 'success',
self::MedicalCertificate => 'danger',
self::EducationCertificate => 'info',
self::Other => 'gray',
};
}
public function icon(): string
{
return match ($this) {
self::Passport => 'heroicon-o-identification',
self::Contract => 'heroicon-o-document-text',
self::MedicalCertificate => 'heroicon-o-heart',
self::EducationCertificate => 'heroicon-o-academic-cap',
self::Other => 'heroicon-o-folder',
};
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum EmploymentStatus: string
{
case Active = 'active';
case Inactive = 'inactive';
case Terminated = 'terminated';
case OnLeave = 'on_leave';
public function label(): string
{
return match ($this) {
self::Active => 'Active',
self::Inactive => 'Inactive',
self::Terminated => 'Terminated',
self::OnLeave => 'On Leave',
};
}
public function color(): string
{
return match ($this) {
self::Active => 'success',
self::Inactive => 'gray',
self::Terminated => 'danger',
self::OnLeave => 'warning',
};
}
public function icon(): string
{
return match ($this) {
self::Active => 'heroicon-o-check-circle',
self::Inactive => 'heroicon-o-pause-circle',
self::Terminated => 'heroicon-o-x-circle',
self::OnLeave => 'heroicon-o-clock',
};
}
}

39
app/Enums/Gender.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum Gender: string
{
case Male = 'male';
case Female = 'female';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::Male => 'Male',
self::Female => 'Female',
self::Other => 'Other',
};
}
public function color(): string
{
return match ($this) {
self::Male => 'info',
self::Female => 'pink',
self::Other => 'gray',
};
}
public function icon(): string
{
return match ($this) {
self::Male => 'heroicon-o-user',
self::Female => 'heroicon-o-user',
self::Other => 'heroicon-o-users',
};
}
}

71
app/Enums/ImportType.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum ImportType: string
{
case Employees = 'employees';
case Vacations = 'vacations';
case Bonuses = 'bonuses';
case Reports = 'reports';
public function label(): string
{
return match ($this) {
self::Employees => 'Employees',
self::Vacations => 'Vacations',
self::Bonuses => 'Bonuses',
self::Reports => 'Disciplinary Reports',
};
}
/**
* @return array<string, string>
*/
public function fields(): array
{
return match ($this) {
self::Employees => [
'employee_number' => 'Employee Number',
'full_name' => 'Full Name',
'national_id' => 'National ID',
'gender' => 'Gender',
'birth_date' => 'Birth Date',
'phone' => 'Phone',
'address' => 'Address',
'department' => 'Department',
'position' => 'Position',
'shift' => 'Shift',
'employment_status' => 'Employment Status',
'hire_date' => 'Hire Date',
'notes' => 'Notes',
],
self::Vacations => [
'employee_number' => 'Employee Number',
'type' => 'Type',
'start_date' => 'Start Date',
'end_date' => 'End Date',
'return_date' => 'Return Date',
'days' => 'Days',
'status' => 'Status',
'reason' => 'Reason',
],
self::Bonuses => [
'employee_number' => 'Employee Number',
'bonus_date' => 'Bonus Date',
'bonus_type' => 'Bonus Type',
'amount' => 'Amount',
'reason' => 'Reason',
],
self::Reports => [
'employee_number' => 'Employee Number',
'report_date' => 'Report Date',
'title' => 'Title',
'description' => 'Description',
'severity' => 'Severity',
],
};
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum VacationType: string
{
case Annual = 'annual';
case Marriage = 'marriage';
case Study = 'study';
case Other = 'other';
public function label(): string
{
return match ($this) {
self::Annual => 'Annual',
self::Marriage => 'Marriage',
self::Study => 'Study',
self::Other => 'Other',
};
}
public function color(): string
{
return match ($this) {
self::Annual => 'primary',
self::Marriage => 'pink',
self::Study => 'info',
self::Other => 'gray',
};
}
public function icon(): string
{
return match ($this) {
self::Annual => 'heroicon-o-sun',
self::Marriage => 'heroicon-o-heart',
self::Study => 'heroicon-o-academic-cap',
self::Other => 'heroicon-o-calendar-days',
};
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace App\Exports;
use App\Models\Bonus;
use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class BonusesExport implements FromQuery, WithHeadings, WithMapping
{
/**
* @param array<int, int>|null $ids
*/
public function __construct(
private readonly ?array $ids = null,
) {}
public function query(): Builder
{
$query = Bonus::query()->with('employee');
if ($this->ids !== null) {
$query->whereIn('id', $this->ids);
}
return $query->orderByDesc('bonus_date');
}
/**
* @return array<int, string>
*/
public function headings(): array
{
return [
'Employee Number',
'Employee Name',
'Bonus Date',
'Bonus Type',
'Amount',
'Reason',
];
}
/**
* @param Bonus $bonus
* @return array<int, mixed>
*/
public function map($bonus): array
{
return [
$bonus->employee?->employee_number,
$bonus->employee?->full_name,
$bonus->bonus_date?->toDateString(),
$bonus->bonus_type?->value,
$bonus->amount,
$bonus->reason,
];
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace App\Exports;
use App\Models\Employee;
use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class EmployeesExport implements FromQuery, WithHeadings, WithMapping
{
/**
* @param array<int, int>|null $ids
*/
public function __construct(
private readonly ?array $ids = null,
) {}
public function query(): Builder
{
$query = Employee::query()
->with(['department', 'position', 'shift']);
if ($this->ids !== null) {
$query->whereIn('id', $this->ids);
}
return $query->orderBy('employee_number');
}
/**
* @return array<int, string>
*/
public function headings(): array
{
return [
'Employee Number',
'Full Name',
'National ID',
'Gender',
'Birth Date',
'Phone',
'Address',
'Department',
'Position',
'Shift',
'Employment Status',
'Hire Date',
'Termination Date',
'Notes',
];
}
/**
* @param Employee $employee
* @return array<int, mixed>
*/
public function map($employee): array
{
return [
$employee->employee_number,
$employee->full_name,
$employee->national_id,
$employee->gender?->value,
$employee->birth_date?->toDateString(),
$employee->phone,
$employee->address,
$employee->department?->name,
$employee->position?->name,
$employee->shift?->name,
$employee->employment_status?->value,
$employee->hire_date?->toDateString(),
$employee->termination_date?->toDateString(),
$employee->notes,
];
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace App\Exports;
use App\Models\DisciplinaryReport;
use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class ReportsExport implements FromQuery, WithHeadings, WithMapping
{
/**
* @param array<int, int>|null $ids
*/
public function __construct(
private readonly ?array $ids = null,
) {}
public function query(): Builder
{
$query = DisciplinaryReport::query()->with(['employee', 'creator']);
if ($this->ids !== null) {
$query->whereIn('id', $this->ids);
}
return $query->orderByDesc('report_date');
}
/**
* @return array<int, string>
*/
public function headings(): array
{
return [
'Employee Number',
'Employee Name',
'Report Date',
'Title',
'Description',
'Severity',
'Created By',
];
}
/**
* @param DisciplinaryReport $report
* @return array<int, mixed>
*/
public function map($report): array
{
return [
$report->employee?->employee_number,
$report->employee?->full_name,
$report->report_date?->toDateString(),
$report->title,
$report->description,
$report->severity?->value,
$report->creator?->name,
];
}
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Exports;
use App\Models\Vacation;
use Illuminate\Database\Eloquent\Builder;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
class VacationsExport implements FromQuery, WithHeadings, WithMapping
{
/**
* @param array<int, int>|null $ids
*/
public function __construct(
private readonly ?array $ids = null,
) {}
public function query(): Builder
{
$query = Vacation::query()->with(['employee', 'approver']);
if ($this->ids !== null) {
$query->whereIn('id', $this->ids);
}
return $query->orderByDesc('start_date');
}
/**
* @return array<int, string>
*/
public function headings(): array
{
return [
'Employee Number',
'Employee Name',
'Type',
'Start Date',
'End Date',
'Return Date',
'Days',
'Status',
'Reason',
'Approved By',
'Approved At',
];
}
/**
* @param Vacation $vacation
* @return array<int, mixed>
*/
public function map($vacation): array
{
return [
$vacation->employee?->employee_number,
$vacation->employee?->full_name,
$vacation->type?->value,
$vacation->start_date?->toDateString(),
$vacation->end_date?->toDateString(),
$vacation->return_date?->toDateString(),
$vacation->days,
$vacation->status?->value,
$vacation->reason,
$vacation->approver?->name,
$vacation->approved_at?->toDateTimeString(),
];
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Filament\Widgets\DepartmentDistributionChart;
use App\Filament\Widgets\HrStatsOverviewWidget;
use App\Filament\Widgets\MonthlyHiringChart;
use App\Filament\Widgets\MonthlyLeaveChart;
use App\Filament\Widgets\RecentReportsWidget;
use App\Filament\Widgets\UpcomingBirthdaysWidget;
use App\Filament\Widgets\UpcomingLeaveWidget;
use Filament\Pages\Dashboard as BaseDashboard;
class Dashboard extends BaseDashboard
{
/**
* @return array<class-string>
*/
public function getWidgets(): array
{
return [
HrStatsOverviewWidget::class,
RecentReportsWidget::class,
UpcomingBirthdaysWidget::class,
UpcomingLeaveWidget::class,
MonthlyHiringChart::class,
MonthlyLeaveChart::class,
DepartmentDistributionChart::class,
];
}
public function getColumns(): int | array
{
return [
'default' => 1,
'md' => 2,
'xl' => 3,
];
}
}

View File

@@ -0,0 +1,332 @@
<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Enums\ImportType;
use App\Jobs\ProcessImportJob;
use App\Services\Import\ImportService;
use BackedEnum;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Alignment;
use Filament\Support\Icons\Heroicon;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\HtmlString;
use UnitEnum;
class ImportWizard extends Page
{
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedArrowUpTray;
protected static string|UnitEnum|null $navigationGroup = 'System';
protected static ?string $navigationLabel = 'Import Wizard';
protected static ?int $navigationSort = 2;
protected static ?string $title = 'Import Wizard';
protected static ?string $slug = 'import-wizard';
/**
* @var array<string, mixed>|null
*/
public ?array $data = [];
/** @var array<int, string> */
public array $headers = [];
public ?string $storedFilePath = null;
/** @var array<string, mixed>|null */
public ?array $importResult = null;
public function mount(): void
{
$this->form->fill([
'import_type' => ImportType::Employees->value,
'column_mapping' => [],
]);
$this->refreshImportResult();
}
public function defaultForm(Schema $schema): Schema
{
return $schema
->statePath('data')
->columns(1);
}
public function form(Schema $schema): Schema
{
return $schema->components([
Wizard::make([
Step::make('Upload')
->description('Select import type and upload an Excel file')
->schema([
Select::make('import_type')
->label('Import Type')
->options(ImportType::class)
->required()
->live()
->afterStateUpdated(fn () => $this->resetColumnMapping()),
FileUpload::make('file')
->label('Excel File')
->disk(config('hr.file_uploads.disk'))
->directory(config('hr.file_uploads.directory') . '/imports')
->acceptedFileTypes([
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel',
'text/csv',
])
->required()
->afterStateUpdated(fn () => $this->loadHeaders()),
]),
Step::make('Map Columns')
->description('Match spreadsheet columns to system fields')
->schema([
Section::make('Column Mapping')
->schema(fn (): array => $this->getColumnMappingFields()),
]),
Step::make('Preview')
->description('Review the first 10 rows with validation')
->schema([
Placeholder::make('preview_table')
->label('Preview')
->content(fn (): HtmlString => new HtmlString($this->getPreviewHtml())),
]),
Step::make('Import')
->description('Run the import and review results')
->schema([
Placeholder::make('import_results')
->label('Import Results')
->content(fn (): HtmlString => new HtmlString($this->getResultsHtml())),
]),
])
->alpineSubmitHandler('$wire.runImport()')
->submitAction(new HtmlString('<button type="submit" class="fi-btn fi-btn-primary">Start Import</button>'))
->skippable(false),
]);
}
public function content(Schema $schema): Schema
{
return $schema->components([
Form::make([EmbeddedSchema::make('form')])
->id('import-wizard-form')
->livewireSubmitHandler('runImport')
->footer([
Actions::make([])
->alignment(Alignment::Start)
->key('import-wizard-actions'),
]),
]);
}
public function runImport(): void
{
$this->form->validate();
$filePath = $this->resolveStoredFilePath();
if ($filePath === null) {
Notification::make()
->title('Upload required')
->body('Please upload an Excel file before importing.')
->danger()
->send();
return;
}
$importType = ImportType::from($this->data['import_type']);
$columnMapping = $this->data['column_mapping'] ?? [];
ProcessImportJob::dispatch(
$importType,
$filePath,
$columnMapping,
Auth::id(),
);
Notification::make()
->title('Import queued')
->body('Your import has been queued and will be processed shortly.')
->success()
->send();
}
public function refreshImportResult(): void
{
$result = ProcessImportJob::getCachedResult(Auth::id());
$this->importResult = $result?->toArray();
}
public function loadHeaders(): void
{
$path = $this->resolveStoredFilePath();
if ($path === null) {
$this->headers = [];
return;
}
$this->headers = app(ImportService::class)->readHeaders($path);
$this->resetColumnMapping();
}
protected function resetColumnMapping(): void
{
$this->data['column_mapping'] = [];
}
/**
* @return array<int, Select>
*/
protected function getColumnMappingFields(): array
{
if ($this->headers === []) {
return [
Placeholder::make('mapping_hint')
->content('Upload a file in step 1 to load column headers.'),
];
}
$importType = ImportType::from($this->data['import_type'] ?? ImportType::Employees->value);
$headerOptions = array_combine($this->headers, $this->headers);
return collect($importType->fields())
->map(function (string $label, string $field) use ($headerOptions): Select {
return Select::make("column_mapping.{$field}")
->label($label)
->options($headerOptions)
->searchable()
->nullable();
})
->values()
->all();
}
protected function getPreviewHtml(): string
{
$path = $this->resolveStoredFilePath();
if ($path === null) {
return '<p class="text-sm text-gray-500">Upload a file to preview rows.</p>';
}
$importType = ImportType::from($this->data['import_type'] ?? ImportType::Employees->value);
$importService = app(ImportService::class);
$rows = $importService->readRows($path, 10);
$mappedRows = $importService->mapRows($rows, $this->data['column_mapping'] ?? []);
$validation = $importService->validatePreview($importType, $mappedRows);
if ($mappedRows === []) {
return '<p class="text-sm text-gray-500">No rows found in the uploaded file.</p>';
}
$fields = array_keys($importType->fields());
$html = '<div class="overflow-x-auto"><table class="fi-ta-table w-full text-sm"><thead><tr>';
$html .= '<th class="px-3 py-2 text-left">Row</th>';
foreach ($fields as $field) {
$html .= '<th class="px-3 py-2 text-left">' . e($importType->fields()[$field]) . '</th>';
}
$html .= '<th class="px-3 py-2 text-left">Validation</th></tr></thead><tbody>';
foreach ($mappedRows as $index => $row) {
$errors = $validation[$index]['errors'] ?? [];
$html .= '<tr class="border-t border-gray-200 dark:border-gray-700">';
$html .= '<td class="px-3 py-2">' . ($index + 2) . '</td>';
foreach ($fields as $field) {
$html .= '<td class="px-3 py-2">' . e((string) ($row[$field] ?? '—')) . '</td>';
}
$html .= '<td class="px-3 py-2">';
if ($errors === []) {
$html .= '<span class="text-success-600">Valid</span>';
} else {
$html .= '<span class="text-danger-600">' . e(implode(' ', $errors)) . '</span>';
}
$html .= '</td></tr>';
}
$html .= '</tbody></table></div>';
return $html;
}
protected function getResultsHtml(): string
{
$this->refreshImportResult();
if ($this->importResult === null) {
return '<p class="text-sm text-gray-500">Submit the import to see results here after processing completes.</p>';
}
$html = '<div class="space-y-2 text-sm">';
$html .= '<p><strong>Total rows:</strong> ' . $this->importResult['total'] . '</p>';
$html .= '<p><strong>Imported:</strong> ' . $this->importResult['imported'] . '</p>';
$html .= '<p><strong>Skipped (duplicates):</strong> ' . $this->importResult['skipped'] . '</p>';
$html .= '<p><strong>Failed:</strong> ' . $this->importResult['failed'] . '</p>';
if (($this->importResult['errors'] ?? []) !== []) {
$html .= '<div class="mt-3"><strong>Errors:</strong><ul class="list-disc ps-5">';
foreach ($this->importResult['errors'] as $error) {
$html .= '<li>' . e($error) . '</li>';
}
$html .= '</ul></div>';
}
$html .= '</div>';
return $html;
}
protected function resolveStoredFilePath(): ?string
{
$file = $this->data['file'] ?? null;
if ($file === null || $file === []) {
return null;
}
$relativePath = is_array($file) ? ($file[0] ?? null) : $file;
if ($relativePath === null) {
return null;
}
$disk = Storage::disk(config('hr.file_uploads.disk'));
$this->storedFilePath = $disk->path($relativePath);
return $this->storedFilePath;
}
public function getTitle(): string|Htmlable
{
return 'Import Wizard';
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\ActivityLogs;
use App\Filament\Resources\ActivityLogs\Pages\ListActivityLogs;
use App\Filament\Resources\ActivityLogs\Pages\ViewActivityLog;
use App\Filament\Resources\ActivityLogs\Schemas\ActivityLogInfolist;
use App\Filament\Resources\ActivityLogs\Tables\ActivityLogsTable;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Spatie\Activitylog\Models\Activity;
use UnitEnum;
class ActivityLogResource extends Resource
{
protected static ?string $model = Activity::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedClipboardDocumentList;
protected static string|UnitEnum|null $navigationGroup = 'System';
protected static ?int $navigationSort = 3;
protected static ?string $navigationLabel = 'Audit Log';
protected static ?string $modelLabel = 'Activity Log';
protected static ?string $pluralModelLabel = 'Activity Logs';
protected static ?string $slug = 'activity-logs';
public static function form(Schema $schema): Schema
{
return $schema;
}
public static function infolist(Schema $schema): Schema
{
return ActivityLogInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return ActivityLogsTable::configure($table);
}
public static function getRelations(): array
{
return [];
}
public static function getPages(): array
{
return [
'index' => ListActivityLogs::route('/'),
'view' => ViewActivityLog::route('/{record}'),
];
}
public static function canCreate(): bool
{
return false;
}
public static function canEdit($record): bool
{
return false;
}
public static function canDelete($record): bool
{
return false;
}
public static function canForceDelete($record): bool
{
return false;
}
public static function canRestore($record): bool
{
return false;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\ActivityLogs\Schemas;
use Filament\Infolists\Components\KeyValueEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class ActivityLogInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Section::make('Activity Details')
->schema([
TextEntry::make('created_at')
->label('Date')
->dateTime(),
TextEntry::make('description'),
TextEntry::make('event')
->badge(),
TextEntry::make('log_name')
->label('Log Name'),
TextEntry::make('subject_type')
->label('Subject Type')
->formatStateUsing(fn (?string $state): string => $state ? class_basename($state) : '—'),
TextEntry::make('subject_id')
->label('Subject ID'),
TextEntry::make('causer.name')
->label('Causer'),
KeyValueEntry::make('properties.attributes')
->label('Changes')
->visible(fn ($record): bool => filled($record->properties['attributes'] ?? null)),
KeyValueEntry::make('properties.old')
->label('Previous Values')
->visible(fn ($record): bool => filled($record->properties['old'] ?? null)),
])
->columns(2),
]);
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\ActivityLogs\Tables;
use App\Models\Bonus;
use App\Models\Department;
use App\Models\DisciplinaryReport;
use App\Models\Employee;
use App\Models\User;
use App\Models\Vacation;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\DatePicker;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class ActivityLogsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('created_at')
->label('Date')
->dateTime()
->sortable(),
TextColumn::make('description')
->searchable()
->limit(50),
TextColumn::make('event')
->badge()
->searchable()
->sortable(),
TextColumn::make('subject_type')
->label('Subject Type')
->formatStateUsing(fn (?string $state): string => $state ? class_basename($state) : '—')
->searchable()
->sortable(),
TextColumn::make('subject_id')
->label('Subject ID')
->sortable(),
TextColumn::make('causer.name')
->label('Causer')
->searchable()
->sortable(),
TextColumn::make('log_name')
->toggleable(isToggledHiddenByDefault: true),
])
->defaultSort('created_at', 'desc')
->filters([
SelectFilter::make('subject_type')
->label('Subject Type')
->options([
Employee::class => 'Employee',
Vacation::class => 'Vacation',
Bonus::class => 'Bonus',
DisciplinaryReport::class => 'Disciplinary Report',
Department::class => 'Department',
User::class => 'User',
]),
SelectFilter::make('event')
->options(fn (): array => \Spatie\Activitylog\Models\Activity::query()
->whereNotNull('event')
->distinct()
->pluck('event', 'event')
->all()),
SelectFilter::make('causer')
->label('Causer')
->relationship('causer', 'name')
->searchable()
->preload(),
Filter::make('created_at')
->schema([
DatePicker::make('from')
->label('From'),
DatePicker::make('until')
->label('Until'),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['from'] ?? null,
fn (Builder $query, string $date): Builder => $query->whereDate('created_at', '>=', $date),
)
->when(
$data['until'] ?? null,
fn (Builder $query, string $date): Builder => $query->whereDate('created_at', '<=', $date),
);
}),
])
->recordActions([
ViewAction::make(),
])
->toolbarActions([]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Bonuses;
use App\Filament\Resources\Bonuses\Pages\CreateBonus;
use App\Filament\Resources\Bonuses\Pages\EditBonus;
use App\Filament\Resources\Bonuses\Pages\ListBonuses;
use App\Filament\Resources\Bonuses\Pages\ViewBonus;
use App\Filament\Resources\Bonuses\Schemas\BonusForm;
use App\Filament\Resources\Bonuses\Schemas\BonusInfolist;
use App\Filament\Resources\Bonuses\Tables\BonusesTable;
use App\Models\Bonus;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class BonusResource extends Resource
{
protected static ?string $model = Bonus::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCurrencyDollar;
protected static string | UnitEnum | null $navigationGroup = 'Records';
protected static ?int $navigationSort = 3;
protected static ?string $recordTitleAttribute = 'reason';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return BonusForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return BonusInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return BonusesTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListBonuses::route('/'),
'create' => CreateBonus::route('/create'),
'view' => ViewBonus::route('/{record}'),
'edit' => EditBonus::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Bonuses\Pages;
use App\Filament\Resources\Bonuses\BonusResource;
use Filament\Resources\Pages\CreateRecord;
class CreateBonus extends CreateRecord
{
protected static string $resource = BonusResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Bonuses\Pages;
use App\Filament\Resources\Bonuses\BonusResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditBonus extends EditRecord
{
protected static string $resource = BonusResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Bonuses\Pages;
use App\Exports\BonusesExport;
use App\Filament\Resources\Bonuses\BonusResource;
use App\Filament\Support\ExportExcelAction;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListBonuses extends ListRecords
{
protected static string $resource = BonusResource::class;
protected function getHeaderActions(): array
{
return [
ExportExcelAction::make('export', BonusesExport::class, 'bonuses.xlsx'),
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Bonuses\Pages;
use App\Filament\Resources\Bonuses\BonusResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewBonus extends ViewRecord
{
protected static string $resource = BonusResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Filament\Resources\Bonuses\Schemas;
use App\Enums\BonusType;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Schema;
class BonusForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
HrForm::employeeSelect(),
DatePicker::make('bonus_date')
->required()
->default(now()),
Select::make('bonus_type')
->options(BonusType::class)
->required()
->native(false),
TextInput::make('amount')
->required()
->numeric()
->prefix('TMT')
->minValue(0)
->step(0.01),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Filament\Resources\Bonuses\Schemas;
use App\Models\Bonus;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class BonusInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('employee.id')
->label('Employee'),
TextEntry::make('bonus_date')
->date(),
TextEntry::make('bonus_type')
->badge(),
TextEntry::make('amount')
->numeric(),
TextEntry::make('reason')
->placeholder('-')
->columnSpanFull(),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (Bonus $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Filament\Resources\Bonuses\Tables;
use App\Enums\BonusType;
use App\Filament\Support\ExportExcelAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class BonusesTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee.full_name')
->label('Employee')
->searchable()
->sortable(),
TextColumn::make('bonus_date')
->date()
->sortable(),
TextColumn::make('bonus_type')
->label('Type')
->badge()
->color(fn (BonusType $state): string => $state->color())
->formatStateUsing(fn (BonusType $state): string => $state->label())
->searchable()
->sortable(),
TextColumn::make('amount')
->money('TMT')
->sortable(),
TextColumn::make('reason')
->limit(40)
->toggleable(),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('bonus_type')
->label('Type')
->options(BonusType::class),
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
ExportExcelAction::bulk('exportSelected', \App\Exports\BonusesExport::class, 'bonuses.xlsx'),
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Departments;
use App\Filament\Resources\Departments\Pages\CreateDepartment;
use App\Filament\Resources\Departments\Pages\EditDepartment;
use App\Filament\Resources\Departments\Pages\ListDepartments;
use App\Filament\Resources\Departments\Pages\ViewDepartment;
use App\Filament\Resources\Departments\Schemas\DepartmentForm;
use App\Filament\Resources\Departments\Schemas\DepartmentInfolist;
use App\Filament\Resources\Departments\Tables\DepartmentsTable;
use App\Models\Department;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class DepartmentResource extends Resource
{
protected static ?string $model = Department::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedBuildingOffice2;
protected static string | UnitEnum | null $navigationGroup = 'Organization';
protected static ?int $navigationSort = 1;
protected static ?string $recordTitleAttribute = 'name';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return DepartmentForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return DepartmentInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return DepartmentsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListDepartments::route('/'),
'create' => CreateDepartment::route('/create'),
'view' => ViewDepartment::route('/{record}'),
'edit' => EditDepartment::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Departments\Pages;
use App\Filament\Resources\Departments\DepartmentResource;
use Filament\Resources\Pages\CreateRecord;
class CreateDepartment extends CreateRecord
{
protected static string $resource = DepartmentResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Departments\Pages;
use App\Filament\Resources\Departments\DepartmentResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditDepartment extends EditRecord
{
protected static string $resource = DepartmentResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Departments\Pages;
use App\Filament\Resources\Departments\DepartmentResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListDepartments extends ListRecords
{
protected static string $resource = DepartmentResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Departments\Pages;
use App\Filament\Resources\Departments\DepartmentResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewDepartment extends ViewRecord
{
protected static string $resource = DepartmentResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\Departments\Schemas;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Schema;
class DepartmentForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->required()
->maxLength(255),
TextInput::make('code')
->required()
->maxLength(50)
->unique(ignoreRecord: true),
Textarea::make('description')
->rows(3)
->columnSpanFull(),
Toggle::make('is_active')
->label('Active')
->default(true)
->required(),
]);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Filament\Resources\Departments\Schemas;
use App\Models\Department;
use Filament\Infolists\Components\IconEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class DepartmentInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('name'),
TextEntry::make('code'),
TextEntry::make('description')
->placeholder('-')
->columnSpanFull(),
IconEntry::make('is_active')
->boolean(),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (Department $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Filament\Resources\Departments\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class DepartmentsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->searchable()
->sortable(),
TextColumn::make('code')
->searchable()
->sortable(),
TextColumn::make('description')
->limit(50)
->toggleable(),
TextColumn::make('is_active')
->label('Status')
->badge()
->formatStateUsing(fn (bool $state): string => $state ? 'Active' : 'Inactive')
->color(fn (bool $state): string => $state ? 'success' : 'danger')
->sortable(),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
TernaryFilter::make('is_active')
->label('Status')
->placeholder('All departments')
->trueLabel('Active')
->falseLabel('Inactive'),
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\DisciplinaryReports;
use App\Filament\Resources\DisciplinaryReports\Pages\CreateDisciplinaryReport;
use App\Filament\Resources\DisciplinaryReports\Pages\EditDisciplinaryReport;
use App\Filament\Resources\DisciplinaryReports\Pages\ListDisciplinaryReports;
use App\Filament\Resources\DisciplinaryReports\Pages\ViewDisciplinaryReport;
use App\Filament\Resources\DisciplinaryReports\Schemas\DisciplinaryReportForm;
use App\Filament\Resources\DisciplinaryReports\Schemas\DisciplinaryReportInfolist;
use App\Filament\Resources\DisciplinaryReports\Tables\DisciplinaryReportsTable;
use App\Models\DisciplinaryReport;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class DisciplinaryReportResource extends Resource
{
protected static ?string $model = DisciplinaryReport::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedExclamationTriangle;
protected static string | UnitEnum | null $navigationGroup = 'Records';
protected static ?int $navigationSort = 1;
protected static ?string $recordTitleAttribute = 'title';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return DisciplinaryReportForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return DisciplinaryReportInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return DisciplinaryReportsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListDisciplinaryReports::route('/'),
'create' => CreateDisciplinaryReport::route('/create'),
'view' => ViewDisciplinaryReport::route('/{record}'),
'edit' => EditDisciplinaryReport::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Pages;
use App\Filament\Resources\DisciplinaryReports\DisciplinaryReportResource;
use Filament\Resources\Pages\CreateRecord;
class CreateDisciplinaryReport extends CreateRecord
{
protected static string $resource = DisciplinaryReportResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Pages;
use App\Filament\Resources\DisciplinaryReports\DisciplinaryReportResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditDisciplinaryReport extends EditRecord
{
protected static string $resource = DisciplinaryReportResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\DisciplinaryReports\Pages;
use App\Exports\ReportsExport;
use App\Filament\Resources\DisciplinaryReports\DisciplinaryReportResource;
use App\Filament\Support\ExportExcelAction;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListDisciplinaryReports extends ListRecords
{
protected static string $resource = DisciplinaryReportResource::class;
protected function getHeaderActions(): array
{
return [
ExportExcelAction::make('export', ReportsExport::class, 'disciplinary-reports.xlsx'),
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Pages;
use App\Filament\Resources\DisciplinaryReports\DisciplinaryReportResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewDisciplinaryReport extends ViewRecord
{
protected static string $resource = DisciplinaryReportResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Schemas;
use App\Enums\DisciplinarySeverity;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Schema;
class DisciplinaryReportForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
HrForm::employeeSelect(),
DatePicker::make('report_date')
->required()
->default(now()),
TextInput::make('title')
->required()
->maxLength(255),
Textarea::make('description')
->required()
->rows(4)
->columnSpanFull(),
Select::make('severity')
->options(DisciplinarySeverity::class)
->required()
->native(false),
HrForm::fileUpload('attachment_path')
->label('Attachment')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Schemas;
use App\Models\DisciplinaryReport;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class DisciplinaryReportInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('employee.id')
->label('Employee'),
TextEntry::make('report_date')
->date(),
TextEntry::make('title'),
TextEntry::make('description')
->columnSpanFull(),
TextEntry::make('severity')
->badge(),
TextEntry::make('attachment_path')
->placeholder('-'),
TextEntry::make('created_by')
->numeric()
->placeholder('-'),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (DisciplinaryReport $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Filament\Resources\DisciplinaryReports\Tables;
use App\Enums\DisciplinarySeverity;
use App\Filament\Support\ExportExcelAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class DisciplinaryReportsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee.full_name')
->label('Employee')
->searchable()
->sortable(),
TextColumn::make('report_date')
->date()
->sortable(),
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('severity')
->badge()
->color(fn (DisciplinarySeverity $state): string => $state->color())
->formatStateUsing(fn (DisciplinarySeverity $state): string => $state->label())
->searchable()
->sortable(),
IconColumn::make('attachment_path')
->label('Attachment')
->boolean()
->trueIcon('heroicon-o-paper-clip')
->falseIcon('heroicon-o-minus'),
TextColumn::make('creator.name')
->label('Created By')
->sortable()
->toggleable(),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('severity')
->options(DisciplinarySeverity::class),
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
ExportExcelAction::bulk('exportSelected', \App\Exports\ReportsExport::class, 'disciplinary-reports.xlsx'),
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\EmployeeDocuments;
use App\Filament\Resources\EmployeeDocuments\Pages\CreateEmployeeDocument;
use App\Filament\Resources\EmployeeDocuments\Pages\EditEmployeeDocument;
use App\Filament\Resources\EmployeeDocuments\Pages\ListEmployeeDocuments;
use App\Filament\Resources\EmployeeDocuments\Pages\ViewEmployeeDocument;
use App\Filament\Resources\EmployeeDocuments\Schemas\EmployeeDocumentForm;
use App\Filament\Resources\EmployeeDocuments\Schemas\EmployeeDocumentInfolist;
use App\Filament\Resources\EmployeeDocuments\Tables\EmployeeDocumentsTable;
use App\Models\EmployeeDocument;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class EmployeeDocumentResource extends Resource
{
protected static ?string $model = EmployeeDocument::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocument;
protected static string | UnitEnum | null $navigationGroup = 'Records';
protected static ?int $navigationSort = 5;
protected static ?string $recordTitleAttribute = 'title';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return EmployeeDocumentForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return EmployeeDocumentInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return EmployeeDocumentsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListEmployeeDocuments::route('/'),
'create' => CreateEmployeeDocument::route('/create'),
'view' => ViewEmployeeDocument::route('/{record}'),
'edit' => EditEmployeeDocument::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Pages;
use App\Filament\Resources\EmployeeDocuments\EmployeeDocumentResource;
use Filament\Resources\Pages\CreateRecord;
class CreateEmployeeDocument extends CreateRecord
{
protected static string $resource = EmployeeDocumentResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Pages;
use App\Filament\Resources\EmployeeDocuments\EmployeeDocumentResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditEmployeeDocument extends EditRecord
{
protected static string $resource = EmployeeDocumentResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Pages;
use App\Filament\Resources\EmployeeDocuments\EmployeeDocumentResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListEmployeeDocuments extends ListRecords
{
protected static string $resource = EmployeeDocumentResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Pages;
use App\Filament\Resources\EmployeeDocuments\EmployeeDocumentResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewEmployeeDocument extends ViewRecord
{
protected static string $resource = EmployeeDocumentResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Schemas;
use App\Enums\DocumentType;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
class EmployeeDocumentForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
HrForm::employeeSelect(),
Select::make('document_type')
->options(DocumentType::class)
->required()
->native(false),
TextInput::make('title')
->required()
->maxLength(255),
HrForm::fileUpload('file_path')
->label('Document')
->required()
->acceptedFileTypes(['application/pdf', 'image/*', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])
->maxSize(15360),
]);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Schemas;
use App\Models\EmployeeDocument;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class EmployeeDocumentInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('employee.id')
->label('Employee'),
TextEntry::make('document_type')
->badge(),
TextEntry::make('title'),
TextEntry::make('file_path'),
TextEntry::make('uploaded_at')
->dateTime(),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (EmployeeDocument $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Filament\Resources\EmployeeDocuments\Tables;
use App\Enums\DocumentType;
use App\Models\EmployeeDocument;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Storage;
class EmployeeDocumentsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee.full_name')
->label('Employee')
->searchable()
->sortable(),
TextColumn::make('document_type')
->label('Type')
->badge()
->color(fn (DocumentType $state): string => $state->color())
->formatStateUsing(fn (DocumentType $state): string => $state->label())
->searchable()
->sortable(),
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('uploaded_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('document_type')
->label('Type')
->options(DocumentType::class),
TrashedFilter::make(),
])
->recordActions([
Action::make('download')
->label('Download')
->icon('heroicon-o-arrow-down-tray')
->visible(fn (EmployeeDocument $record): bool => filled($record->file_path))
->action(function (EmployeeDocument $record) {
$disk = Storage::disk(config('hr.file_uploads.disk'));
return response()->download(
$disk->path($record->file_path),
basename($record->file_path),
);
}),
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees;
use App\Filament\Resources\Employees\Pages\CreateEmployee;
use App\Filament\Resources\Employees\Pages\EditEmployee;
use App\Filament\Resources\Employees\Pages\ListEmployees;
use App\Filament\Resources\Employees\Pages\ViewEmployee;
use App\Filament\Resources\Employees\RelationManagers\BonusesRelationManager;
use App\Filament\Resources\Employees\RelationManagers\DisciplinaryReportsRelationManager;
use App\Filament\Resources\Employees\RelationManagers\DocumentsRelationManager;
use App\Filament\Resources\Employees\RelationManagers\ExplanationsRelationManager;
use App\Filament\Resources\Employees\RelationManagers\GiftsRelationManager;
use App\Filament\Resources\Employees\RelationManagers\SickLeavesRelationManager;
use App\Filament\Resources\Employees\RelationManagers\UnpaidLeavesRelationManager;
use App\Filament\Resources\Employees\RelationManagers\VacationsRelationManager;
use App\Filament\Resources\Employees\Schemas\EmployeeForm;
use App\Filament\Resources\Employees\Schemas\EmployeeInfolist;
use App\Filament\Resources\Employees\Tables\EmployeesTable;
use App\Models\Employee;
use BackedEnum;
use UnitEnum;
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;
class EmployeeResource extends Resource
{
protected static ?string $model = Employee::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
protected static string | UnitEnum | null $navigationGroup = 'Employees';
protected static ?int $navigationSort = 1;
protected static ?string $recordTitleAttribute = 'full_name';
protected static bool $isGloballySearchable = true;
public static function form(Schema $schema): Schema
{
return EmployeeForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return EmployeeInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return EmployeesTable::configure($table);
}
/**
* @return array<string>
*/
public static function getGloballySearchableAttributes(): array
{
return [
'employee_number',
'full_name',
'phone',
'department.name',
'position.name',
];
}
/**
* @return array<string, string>
*/
public static function getGlobalSearchResultDetails(Model $record): array
{
/** @var Employee $record */
return [
'Employee #' => $record->employee_number,
'Department' => $record->department?->name ?? '—',
'Position' => $record->position?->name ?? '—',
];
}
public static function getRelations(): array
{
return [
VacationsRelationManager::class,
SickLeavesRelationManager::class,
UnpaidLeavesRelationManager::class,
DisciplinaryReportsRelationManager::class,
ExplanationsRelationManager::class,
BonusesRelationManager::class,
GiftsRelationManager::class,
DocumentsRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => ListEmployees::route('/'),
'create' => CreateEmployee::route('/create'),
'view' => ViewEmployee::route('/{record}'),
'edit' => EditEmployee::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Employees\Pages;
use App\Filament\Resources\Employees\EmployeeResource;
use Filament\Resources\Pages\CreateRecord;
class CreateEmployee extends CreateRecord
{
protected static string $resource = EmployeeResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Employees\Pages;
use App\Filament\Resources\Employees\EmployeeResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditEmployee extends EditRecord
{
protected static string $resource = EmployeeResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\Pages;
use App\Exports\EmployeesExport;
use App\Filament\Resources\Employees\EmployeeResource;
use App\Filament\Support\ExportExcelAction;
use App\Filament\Support\ExportPdfAction;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListEmployees extends ListRecords
{
protected static string $resource = EmployeeResource::class;
protected function getHeaderActions(): array
{
return [
ExportExcelAction::make('export', EmployeesExport::class, 'employees.xlsx'),
ExportPdfAction::employeeList(),
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\Pages;
use App\Filament\Resources\Employees\EmployeeResource;
use App\Filament\Resources\Employees\Widgets\EmployeeStatsWidget;
use App\Filament\Support\ExportPdfAction;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewEmployee extends ViewRecord
{
protected static string $resource = EmployeeResource::class;
protected function getHeaderActions(): array
{
return [
ExportPdfAction::employeeProfile(),
ExportPdfAction::employeeLeaveSummary(),
EditAction::make(),
];
}
/**
* @return array<class-string>
*/
protected function getHeaderWidgets(): array
{
return [
EmployeeStatsWidget::class,
];
}
/**
* @return int | array<string, ?int>
*/
public function getHeaderWidgetsColumns(): int | array
{
return [
'default' => 1,
'sm' => 2,
'xl' => 4,
];
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Enums\BonusType;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class BonusesRelationManager extends RelationManager
{
protected static string $relationship = 'bonuses';
protected static ?string $title = 'Bonuses';
public function form(Schema $schema): Schema
{
return $schema
->components([
DatePicker::make('bonus_date')
->required()
->default(now()),
Select::make('bonus_type')
->options(BonusType::class)
->required()
->native(false),
TextInput::make('amount')
->required()
->numeric()
->prefix('TMT')
->minValue(0)
->step(0.01),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('bonus_date')
->columns([
TextColumn::make('bonus_date')
->date()
->sortable(),
TextColumn::make('bonus_type')
->label('Type')
->badge()
->color(fn (BonusType $state): string => $state->color())
->formatStateUsing(fn (BonusType $state): string => $state->label())
->sortable(),
TextColumn::make('amount')
->money('TMT')
->sortable(),
TextColumn::make('reason')
->limit(40)
->toggleable(),
])
->filters([
SelectFilter::make('bonus_type')
->label('Type')
->options(BonusType::class),
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Enums\DisciplinarySeverity;
use App\Filament\Support\HrForm;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class DisciplinaryReportsRelationManager extends RelationManager
{
protected static string $relationship = 'disciplinaryReports';
protected static ?string $title = 'Disciplinary Reports';
public function form(Schema $schema): Schema
{
return $schema
->components([
DatePicker::make('report_date')
->required()
->default(now()),
TextInput::make('title')
->required()
->maxLength(255),
Textarea::make('description')
->required()
->rows(4)
->columnSpanFull(),
Select::make('severity')
->options(DisciplinarySeverity::class)
->required()
->native(false),
HrForm::fileUpload('attachment_path')
->label('Attachment')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('title')
->columns([
TextColumn::make('report_date')
->date()
->sortable(),
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('severity')
->badge()
->color(fn (DisciplinarySeverity $state): string => $state->color())
->formatStateUsing(fn (DisciplinarySeverity $state): string => $state->label())
->sortable(),
IconColumn::make('attachment_path')
->label('Attachment')
->boolean()
->trueIcon('heroicon-o-paper-clip')
->falseIcon('heroicon-o-minus'),
TextColumn::make('creator.name')
->label('Created By')
->toggleable(),
])
->filters([
SelectFilter::make('severity')
->options(DisciplinarySeverity::class),
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Enums\DocumentType;
use App\Filament\Support\HrForm;
use App\Models\EmployeeDocument;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Storage;
class DocumentsRelationManager extends RelationManager
{
protected static string $relationship = 'documents';
protected static ?string $title = 'Employee Documents';
public function form(Schema $schema): Schema
{
return $schema
->components([
Select::make('document_type')
->options(DocumentType::class)
->required()
->native(false),
TextInput::make('title')
->required()
->maxLength(255),
HrForm::fileUpload('file_path')
->label('Document')
->required()
->acceptedFileTypes(['application/pdf', 'image/*', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'])
->maxSize(15360),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('title')
->columns([
TextColumn::make('document_type')
->label('Type')
->badge()
->color(fn (DocumentType $state): string => $state->color())
->formatStateUsing(fn (DocumentType $state): string => $state->label())
->sortable(),
TextColumn::make('title')
->searchable()
->sortable(),
TextColumn::make('uploaded_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('document_type')
->label('Type')
->options(DocumentType::class),
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
Action::make('download')
->label('Download')
->icon('heroicon-o-arrow-down-tray')
->visible(fn (EmployeeDocument $record): bool => filled($record->file_path))
->action(function (EmployeeDocument $record) {
$disk = Storage::disk(config('hr.file_uploads.disk'));
return response()->download(
$disk->path($record->file_path),
basename($record->file_path),
);
}),
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Filament\Support\HrForm;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class ExplanationsRelationManager extends RelationManager
{
protected static string $relationship = 'explanations';
protected static ?string $title = 'Explanations';
public function form(Schema $schema): Schema
{
return $schema
->components([
DatePicker::make('explanation_date')
->required()
->default(now()),
TextInput::make('reason')
->required()
->maxLength(255),
Textarea::make('description')
->required()
->rows(4)
->columnSpanFull(),
HrForm::fileUpload('attachment_path')
->label('Attachment')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('reason')
->columns([
TextColumn::make('explanation_date')
->date()
->sortable(),
TextColumn::make('reason')
->searchable()
->limit(40),
TextColumn::make('description')
->limit(50)
->toggleable(),
IconColumn::make('attachment_path')
->label('Attachment')
->boolean()
->trueIcon('heroicon-o-paper-clip')
->falseIcon('heroicon-o-minus'),
])
->filters([
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class GiftsRelationManager extends RelationManager
{
protected static string $relationship = 'gifts';
protected static ?string $title = 'Gifts';
public function form(Schema $schema): Schema
{
return $schema
->components([
DatePicker::make('gift_date')
->required()
->default(now()),
TextInput::make('gift_name')
->label('Gift Name')
->required()
->maxLength(255),
TextInput::make('value')
->required()
->numeric()
->prefix('TMT')
->minValue(0)
->step(0.01)
->default(0),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('gift_name')
->columns([
TextColumn::make('gift_date')
->date()
->sortable(),
TextColumn::make('gift_name')
->label('Gift')
->searchable()
->sortable(),
TextColumn::make('value')
->money('TMT')
->sortable(),
TextColumn::make('reason')
->limit(40)
->toggleable(),
])
->filters([
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Filament\Support\HrForm;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class SickLeavesRelationManager extends RelationManager
{
protected static string $relationship = 'sickLeaves';
protected static ?string $title = 'Sick Leaves';
public function form(Schema $schema): Schema
{
return $schema
->components([
HrForm::leaveDatePicker('start_date', 'Start Date'),
HrForm::leaveDatePicker('end_date', 'End Date'),
HrForm::leaveDaysDisplay(),
TextInput::make('medical_institution')
->maxLength(255),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
HrForm::fileUpload('document_path')
->label('Medical Document')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('start_date')
->columns([
TextColumn::make('start_date')
->date()
->sortable(),
TextColumn::make('end_date')
->date()
->sortable(),
TextColumn::make('days')
->numeric()
->sortable(),
TextColumn::make('medical_institution')
->searchable()
->toggleable(),
IconColumn::make('document_path')
->label('Document')
->boolean()
->trueIcon('heroicon-o-document-check')
->falseIcon('heroicon-o-document'),
])
->filters([
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Enums\ApprovalStatus;
use App\Filament\Support\HrForm;
use App\Models\UnpaidLeave;
use App\Services\Leave\UnpaidLeaveService;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Auth;
class UnpaidLeavesRelationManager extends RelationManager
{
protected static string $relationship = 'unpaidLeaves';
protected static ?string $title = 'Unpaid Leaves';
public function form(Schema $schema): Schema
{
return $schema
->components([
HrForm::leaveDatePicker('start_date', 'Start Date'),
HrForm::leaveDatePicker('end_date', 'End Date'),
HrForm::leaveDaysDisplay(),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('start_date')
->columns([
TextColumn::make('start_date')
->date()
->sortable(),
TextColumn::make('end_date')
->date()
->sortable(),
TextColumn::make('days')
->numeric()
->sortable(),
TextColumn::make('status')
->badge()
->color(fn (ApprovalStatus $state): string => $state->color())
->formatStateUsing(fn (ApprovalStatus $state): string => $state->label())
->sortable(),
TextColumn::make('approver.name')
->label('Approved By')
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('status')
->options(ApprovalStatus::class),
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
Action::make('approve')
->label('Approve')
->icon('heroicon-o-check-badge')
->color('success')
->requiresConfirmation()
->visible(fn (UnpaidLeave $record): bool => $record->status === ApprovalStatus::Pending)
->action(function (UnpaidLeave $record, UnpaidLeaveService $unpaidLeaveService): void {
$unpaidLeaveService->approve($record, Auth::user());
}),
Action::make('reject')
->label('Reject')
->icon('heroicon-o-x-mark')
->color('danger')
->requiresConfirmation()
->visible(fn (UnpaidLeave $record): bool => $record->status === ApprovalStatus::Pending)
->action(function (UnpaidLeave $record, UnpaidLeaveService $unpaidLeaveService): void {
$unpaidLeaveService->reject($record, Auth::user());
}),
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\RelationManagers;
use App\Enums\ApprovalStatus;
use App\Enums\VacationType;
use App\Filament\Support\HrForm;
use App\Models\Vacation;
use App\Services\Vacation\VacationService;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Support\Facades\Auth;
class VacationsRelationManager extends RelationManager
{
protected static string $relationship = 'vacations';
protected static ?string $title = 'Vacations';
public function form(Schema $schema): Schema
{
return $schema
->components([
Select::make('type')
->options(VacationType::class)
->required()
->native(false),
HrForm::leaveDatePicker('start_date', 'Start Date'),
HrForm::leaveDatePicker('end_date', 'End Date'),
DatePicker::make('return_date')
->label('Return Date'),
HrForm::leaveDaysDisplay(),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
HrForm::fileUpload('attachment_path')
->label('Attachment')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('start_date')
->columns([
TextColumn::make('type')
->badge()
->color(fn (VacationType $state): string => $state->color())
->formatStateUsing(fn (VacationType $state): string => $state->label())
->sortable(),
TextColumn::make('start_date')
->date()
->sortable(),
TextColumn::make('end_date')
->date()
->sortable(),
TextColumn::make('days')
->numeric()
->sortable(),
TextColumn::make('status')
->badge()
->color(fn (ApprovalStatus $state): string => $state->color())
->formatStateUsing(fn (ApprovalStatus $state): string => $state->label())
->sortable(),
TextColumn::make('approver.name')
->label('Approved By')
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('status')
->options(ApprovalStatus::class),
SelectFilter::make('type')
->options(VacationType::class),
TrashedFilter::make(),
])
->headerActions([
CreateAction::make(),
])
->recordActions([
Action::make('approve')
->label('Approve')
->icon('heroicon-o-check-badge')
->color('success')
->requiresConfirmation()
->visible(fn (Vacation $record): bool => $record->status === ApprovalStatus::Pending)
->action(function (Vacation $record, VacationService $vacationService): void {
$vacationService->approve($record, Auth::user());
}),
Action::make('reject')
->label('Reject')
->icon('heroicon-o-x-mark')
->color('danger')
->requiresConfirmation()
->visible(fn (Vacation $record): bool => $record->status === ApprovalStatus::Pending)
->action(function (Vacation $record, VacationService $vacationService): void {
$vacationService->reject($record, Auth::user());
}),
EditAction::make(),
DeleteAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Filament\Resources\Employees\Schemas;
use App\Enums\EmploymentStatus;
use App\Enums\Gender;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
class EmployeeForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Tabs::make('Employee')
->tabs([
Tab::make('Personal')
->schema([
HrForm::fileUpload('profile_photo_path')
->label('Photo')
->image()
->avatar()
->imageEditor(),
TextInput::make('employee_number')
->required()
->maxLength(50)
->unique(ignoreRecord: true),
TextInput::make('full_name')
->required()
->maxLength(255),
TextInput::make('national_id')
->label('National ID')
->maxLength(50),
Select::make('gender')
->options(Gender::class)
->required()
->native(false),
DatePicker::make('birth_date')
->required(),
TextInput::make('phone')
->tel()
->default(config('hr.default_phone'))
->required(),
Textarea::make('address')
->rows(3)
->columnSpanFull(),
])
->columns(2),
Tab::make('Employment')
->schema([
Select::make('department_id')
->relationship('department', 'name')
->searchable()
->preload()
->required(),
Select::make('position_id')
->relationship('position', 'name')
->searchable()
->preload()
->required(),
Select::make('shift_id')
->relationship('shift', 'name')
->searchable()
->preload()
->required(),
Select::make('employment_status')
->options(EmploymentStatus::class)
->default(EmploymentStatus::Active)
->required()
->native(false),
DatePicker::make('hire_date')
->required(),
DatePicker::make('termination_date'),
Textarea::make('notes')
->rows(4)
->columnSpanFull(),
])
->columns(2),
])
->columnSpanFull(),
]);
}
}

View File

@@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\Schemas;
use App\Enums\EmploymentStatus;
use App\Enums\Gender;
use App\Models\Employee;
use App\Models\Vacation;
use App\Services\Employee\EmployeeStatisticsService;
use Filament\Infolists\Components\ImageEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Schema;
class EmployeeInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Tabs::make('Employee Details')
->tabs([
Tab::make('Overview')
->schema([
Section::make('Profile')
->schema([
ImageEntry::make('profile_photo_path')
->label('Photo')
->disk(config('hr.file_uploads.disk'))
->visibility('private')
->circular()
->height(120)
->placeholder('—')
->columnSpanFull(),
TextEntry::make('employee_number')
->label('Employee #'),
TextEntry::make('full_name'),
TextEntry::make('national_id')
->label('National ID')
->placeholder('—'),
TextEntry::make('gender')
->badge()
->color(fn (Gender $state): string => $state->color())
->formatStateUsing(fn (Gender $state): string => $state->label()),
TextEntry::make('birth_date')
->date(),
TextEntry::make('phone'),
TextEntry::make('address')
->placeholder('—')
->columnSpanFull(),
])
->columns(2),
Section::make('Organization')
->schema([
TextEntry::make('department.name')
->label('Department')
->badge()
->color('info'),
TextEntry::make('position.name')
->label('Position')
->badge()
->color('primary'),
TextEntry::make('shift.name')
->label('Shift')
->badge()
->color('gray'),
TextEntry::make('employment_status')
->badge()
->color(fn (EmploymentStatus $state): string => $state->color())
->formatStateUsing(fn (EmploymentStatus $state): string => $state->label())
->icon(fn (EmploymentStatus $state): string => $state->icon()),
])
->columns(2),
Section::make('Employment Timeline')
->schema([
TextEntry::make('hire_date')
->label('Hire Date')
->date()
->icon('heroicon-o-calendar'),
TextEntry::make('termination_date')
->label('Termination Date')
->date()
->placeholder('—')
->icon('heroicon-o-calendar-days'),
TextEntry::make('tenure')
->label('Tenure')
->state(fn (Employee $record): string => $record->hire_date
? $record->hire_date->diffForHumans(now(), true)
: '—'),
TextEntry::make('notes')
->placeholder('—')
->columnSpanFull(),
])
->columns(2),
]),
Tab::make('Statistics')
->schema([
Section::make('Leave & Attendance')
->schema([
TextEntry::make('stats_vacation_taken')
->label('Vacation Taken (days)')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->vacationTaken)
->numeric()
->icon('heroicon-o-sun'),
TextEntry::make('stats_vacation_remaining')
->label('Vacation Remaining (days)')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->vacationRemaining)
->numeric()
->color('success')
->icon('heroicon-o-calendar'),
TextEntry::make('stats_sick_leave_count')
->label('Sick Leave Records')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->sickLeaveCount)
->numeric()
->icon('heroicon-o-heart'),
])
->columns(3),
Section::make('HR Records')
->schema([
TextEntry::make('stats_reports_count')
->label('Disciplinary Reports')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->reportsCount)
->numeric()
->icon('heroicon-o-exclamation-triangle'),
TextEntry::make('stats_bonuses_total')
->label('Total Bonuses')
->state(fn (Employee $record): float => app(EmployeeStatisticsService::class)->summary($record)->bonusesTotal)
->money('TMT')
->icon('heroicon-o-banknotes'),
TextEntry::make('stats_gifts_count')
->label('Gifts Received')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->giftsCount)
->numeric()
->icon('heroicon-o-gift'),
])
->columns(3),
]),
Tab::make('Leave Summary')
->schema([
Section::make('Annual Leave Balance')
->schema([
TextEntry::make('leave_balance_taken')
->label('Days Taken')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->vacationTaken)
->suffix(' days'),
TextEntry::make('leave_balance_remaining')
->label('Days Remaining')
->state(fn (Employee $record): int => app(EmployeeStatisticsService::class)->summary($record)->vacationRemaining)
->suffix(' days')
->color('success'),
TextEntry::make('leave_balance_total')
->label('Annual Allowance')
->state(fn (): int => (int) config('hr.annual_leave_days'))
->suffix(' days'),
])
->columns(3),
Section::make('Upcoming Approved Leave')
->schema([
TextEntry::make('upcoming_leave')
->label('Scheduled Vacations')
->state(function (Employee $record): string {
$upcoming = app(EmployeeStatisticsService::class)
->summary($record)
->upcomingLeave;
if ($upcoming->isEmpty()) {
return 'No upcoming approved leave';
}
return $upcoming
->map(fn (Vacation $vacation): string => sprintf(
'%s: %s %s (%d days)',
$vacation->type->label(),
$vacation->start_date->format('M j, Y'),
$vacation->end_date->format('M j, Y'),
$vacation->days,
))
->implode("\n");
})
->listWithLineBreaks()
->columnSpanFull(),
]),
Section::make('Other Leave Types')
->schema([
TextEntry::make('unpaid_leaves_count')
->label('Unpaid Leave Records')
->state(fn (Employee $record): int => $record->unpaidLeaves()->count())
->numeric(),
TextEntry::make('explanations_count')
->label('Explanations')
->state(fn (Employee $record): int => $record->explanations()->count())
->numeric(),
])
->columns(2),
]),
])
->columnSpanFull(),
]);
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Filament\Resources\Employees\Tables;
use App\Enums\EmploymentStatus;
use App\Enums\Gender;
use App\Models\Department;
use App\Models\Employee;
use App\Filament\Support\ExportExcelAction;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Select;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Collection;
class EmployeesTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee_number')
->label('Number')
->searchable()
->sortable(),
TextColumn::make('full_name')
->searchable()
->sortable(),
TextColumn::make('national_id')
->label('National ID')
->searchable()
->toggleable(),
TextColumn::make('gender')
->badge()
->color(fn (Gender $state): string => $state->color())
->formatStateUsing(fn (Gender $state): string => $state->label())
->searchable(),
TextColumn::make('phone')
->searchable()
->toggleable(),
TextColumn::make('department.name')
->label('Department')
->badge()
->color('info')
->searchable()
->sortable(),
TextColumn::make('position.name')
->label('Position')
->badge()
->color('primary')
->searchable()
->sortable(),
TextColumn::make('shift.name')
->label('Shift')
->badge()
->color('gray')
->searchable()
->sortable(),
TextColumn::make('employment_status')
->label('Status')
->badge()
->color(fn (EmploymentStatus $state): string => $state->color())
->formatStateUsing(fn (EmploymentStatus $state): string => $state->label())
->searchable()
->sortable(),
TextColumn::make('hire_date')
->date()
->sortable(),
TextColumn::make('termination_date')
->date()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
SelectFilter::make('department_id')
->label('Department')
->relationship('department', 'name')
->searchable()
->preload(),
SelectFilter::make('position_id')
->label('Position')
->relationship('position', 'name')
->searchable()
->preload(),
SelectFilter::make('shift_id')
->label('Shift')
->relationship('shift', 'name')
->searchable()
->preload(),
SelectFilter::make('employment_status')
->label('Employment Status')
->options(EmploymentStatus::class),
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
ExportExcelAction::bulk('exportSelected', \App\Exports\EmployeesExport::class, 'employees.xlsx'),
BulkAction::make('changeDepartment')
->label('Change Department')
->icon('heroicon-o-building-office-2')
->schema([
Select::make('department_id')
->label('Department')
->options(fn (): array => Department::query()->orderBy('name')->pluck('name', 'id')->all())
->searchable()
->required(),
])
->action(function (Collection $records, array $data): void {
$records->each(
fn (Employee $employee) => $employee->update([
'department_id' => $data['department_id'],
]),
);
})
->deselectRecordsAfterCompletion(),
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Employees\Widgets;
use App\Models\Employee;
use App\Services\Employee\EmployeeStatisticsService;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class EmployeeStatsWidget extends StatsOverviewWidget
{
public ?Employee $record = null;
protected function getStats(): array
{
if (! $this->record instanceof Employee) {
return [];
}
$summary = app(EmployeeStatisticsService::class)->summary($this->record);
return [
Stat::make('Vacation Taken', (string) $summary->vacationTaken)
->description("{$summary->vacationRemaining} days remaining")
->descriptionIcon('heroicon-o-calendar')
->color('primary')
->icon('heroicon-o-sun'),
Stat::make('Sick Leaves', (string) $summary->sickLeaveCount)
->description('Total records')
->descriptionIcon('heroicon-o-heart')
->color('warning')
->icon('heroicon-o-heart'),
Stat::make('Disciplinary Reports', (string) $summary->reportsCount)
->description('On file')
->descriptionIcon('heroicon-o-exclamation-triangle')
->color('danger')
->icon('heroicon-o-exclamation-triangle'),
Stat::make('Total Bonuses', number_format($summary->bonusesTotal, 2).' TMT')
->description("{$summary->giftsCount} gifts received")
->descriptionIcon('heroicon-o-gift')
->color('success')
->icon('heroicon-o-banknotes'),
];
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Explanations;
use App\Filament\Resources\Explanations\Pages\CreateExplanation;
use App\Filament\Resources\Explanations\Pages\EditExplanation;
use App\Filament\Resources\Explanations\Pages\ListExplanations;
use App\Filament\Resources\Explanations\Pages\ViewExplanation;
use App\Filament\Resources\Explanations\Schemas\ExplanationForm;
use App\Filament\Resources\Explanations\Schemas\ExplanationInfolist;
use App\Filament\Resources\Explanations\Tables\ExplanationsTable;
use App\Models\Explanation;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class ExplanationResource extends Resource
{
protected static ?string $model = Explanation::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedDocumentText;
protected static string | UnitEnum | null $navigationGroup = 'Records';
protected static ?int $navigationSort = 2;
protected static ?string $recordTitleAttribute = 'reason';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return ExplanationForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return ExplanationInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return ExplanationsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListExplanations::route('/'),
'create' => CreateExplanation::route('/create'),
'view' => ViewExplanation::route('/{record}'),
'edit' => EditExplanation::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Explanations\Pages;
use App\Filament\Resources\Explanations\ExplanationResource;
use Filament\Resources\Pages\CreateRecord;
class CreateExplanation extends CreateRecord
{
protected static string $resource = ExplanationResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Explanations\Pages;
use App\Filament\Resources\Explanations\ExplanationResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditExplanation extends EditRecord
{
protected static string $resource = ExplanationResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Explanations\Pages;
use App\Filament\Resources\Explanations\ExplanationResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListExplanations extends ListRecords
{
protected static string $resource = ExplanationResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Explanations\Pages;
use App\Filament\Resources\Explanations\ExplanationResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewExplanation extends ViewRecord
{
protected static string $resource = ExplanationResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Filament\Resources\Explanations\Schemas;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Schema;
class ExplanationForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
HrForm::employeeSelect(),
DatePicker::make('explanation_date')
->required()
->default(now()),
TextInput::make('reason')
->required()
->maxLength(255),
Textarea::make('description')
->required()
->rows(4)
->columnSpanFull(),
HrForm::fileUpload('attachment_path')
->label('Attachment')
->acceptedFileTypes(['application/pdf', 'image/*'])
->maxSize(10240),
]);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Filament\Resources\Explanations\Schemas;
use App\Models\Explanation;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class ExplanationInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('employee.id')
->label('Employee'),
TextEntry::make('explanation_date')
->date(),
TextEntry::make('reason'),
TextEntry::make('description')
->columnSpanFull(),
TextEntry::make('attachment_path')
->placeholder('-'),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (Explanation $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Filament\Resources\Explanations\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class ExplanationsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee.full_name')
->label('Employee')
->searchable()
->sortable(),
TextColumn::make('explanation_date')
->date()
->sortable(),
TextColumn::make('reason')
->searchable()
->limit(40),
TextColumn::make('description')
->limit(50)
->toggleable(),
IconColumn::make('attachment_path')
->label('Attachment')
->boolean()
->trueIcon('heroicon-o-paper-clip')
->falseIcon('heroicon-o-minus'),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Gifts;
use App\Filament\Resources\Gifts\Pages\CreateGift;
use App\Filament\Resources\Gifts\Pages\EditGift;
use App\Filament\Resources\Gifts\Pages\ListGifts;
use App\Filament\Resources\Gifts\Pages\ViewGift;
use App\Filament\Resources\Gifts\Schemas\GiftForm;
use App\Filament\Resources\Gifts\Schemas\GiftInfolist;
use App\Filament\Resources\Gifts\Tables\GiftsTable;
use App\Models\Gift;
use BackedEnum;
use UnitEnum;
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\SoftDeletingScope;
class GiftResource extends Resource
{
protected static ?string $model = Gift::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedGift;
protected static string | UnitEnum | null $navigationGroup = 'Records';
protected static ?int $navigationSort = 4;
protected static ?string $recordTitleAttribute = 'gift_name';
protected static bool $isGloballySearchable = false;
public static function form(Schema $schema): Schema
{
return GiftForm::configure($schema);
}
public static function infolist(Schema $schema): Schema
{
return GiftInfolist::configure($schema);
}
public static function table(Table $table): Table
{
return GiftsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListGifts::route('/'),
'create' => CreateGift::route('/create'),
'view' => ViewGift::route('/{record}'),
'edit' => EditGift::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Gifts\Pages;
use App\Filament\Resources\Gifts\GiftResource;
use Filament\Resources\Pages\CreateRecord;
class CreateGift extends CreateRecord
{
protected static string $resource = GiftResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Gifts\Pages;
use App\Filament\Resources\Gifts\GiftResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditGift extends EditRecord
{
protected static string $resource = GiftResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Gifts\Pages;
use App\Filament\Resources\Gifts\GiftResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListGifts extends ListRecords
{
protected static string $resource = GiftResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Gifts\Pages;
use App\Filament\Resources\Gifts\GiftResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewGift extends ViewRecord
{
protected static string $resource = GiftResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Filament\Resources\Gifts\Schemas;
use App\Filament\Support\HrForm;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Textarea;
use Filament\Schemas\Schema;
class GiftForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
HrForm::employeeSelect(),
DatePicker::make('gift_date')
->required()
->default(now()),
TextInput::make('gift_name')
->label('Gift Name')
->required()
->maxLength(255),
TextInput::make('value')
->required()
->numeric()
->prefix('TMT')
->minValue(0)
->step(0.01)
->default(0),
Textarea::make('reason')
->rows(3)
->columnSpanFull(),
]);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Filament\Resources\Gifts\Schemas;
use App\Models\Gift;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Schema;
class GiftInfolist
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextEntry::make('employee.id')
->label('Employee'),
TextEntry::make('gift_date')
->date(),
TextEntry::make('gift_name'),
TextEntry::make('value')
->numeric(),
TextEntry::make('reason')
->placeholder('-')
->columnSpanFull(),
TextEntry::make('created_at')
->dateTime()
->placeholder('-'),
TextEntry::make('updated_at')
->dateTime()
->placeholder('-'),
TextEntry::make('deleted_at')
->dateTime()
->visible(fn (Gift $record): bool => $record->trashed()),
]);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Filament\Resources\Gifts\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class GiftsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('employee.full_name')
->label('Employee')
->searchable()
->sortable(),
TextColumn::make('gift_date')
->date()
->sortable(),
TextColumn::make('gift_name')
->label('Gift')
->searchable()
->sortable(),
TextColumn::make('value')
->money('TMT')
->sortable(),
TextColumn::make('reason')
->limit(40)
->toggleable(),
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('deleted_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
TrashedFilter::make(),
])
->recordActions([
ViewAction::make(),
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
]);
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Positions\Pages;
use App\Filament\Resources\Positions\PositionResource;
use Filament\Resources\Pages\CreateRecord;
class CreatePosition extends CreateRecord
{
protected static string $resource = PositionResource::class;
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Filament\Resources\Positions\Pages;
use App\Filament\Resources\Positions\PositionResource;
use Filament\Actions\DeleteAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\ViewAction;
use Filament\Resources\Pages\EditRecord;
class EditPosition extends EditRecord
{
protected static string $resource = PositionResource::class;
protected function getHeaderActions(): array
{
return [
ViewAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Positions\Pages;
use App\Filament\Resources\Positions\PositionResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListPositions extends ListRecords
{
protected static string $resource = PositionResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Positions\Pages;
use App\Filament\Resources\Positions\PositionResource;
use Filament\Actions\EditAction;
use Filament\Resources\Pages\ViewRecord;
class ViewPosition extends ViewRecord
{
protected static string $resource = PositionResource::class;
protected function getHeaderActions(): array
{
return [
EditAction::make(),
];
}
}

Some files were not shown because too many files have changed in this diff Show More