Some checks failed
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Has been cancelled
407 lines
17 KiB
PHP
407 lines
17 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Resources;
|
|
|
|
use App\Filament\Resources\VisitorEntryResource\Pages;
|
|
use App\Filament\Resources\VisitorEntryResource\RelationManagers;
|
|
use App\Models\VisitorEntry;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Forms;
|
|
use Filament\Forms\Form;
|
|
use Filament\Resources\Resource;
|
|
use Filament\Tables;
|
|
use Filament\Tables\Table;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
|
use Filament\Infolists\Infolist;
|
|
use Filament\Infolists\Components\ImageEntry;
|
|
use Filament\Infolists\Components\TextEntry;
|
|
use Filament\Infolists\Components\Section;
|
|
use Carbon\Carbon;
|
|
|
|
class VisitorEntryResource extends Resource
|
|
{
|
|
protected static ?string $model = VisitorEntry::class;
|
|
|
|
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
|
|
|
|
protected static ?string $navigationGroup = 'Gate Entry';
|
|
|
|
public static function form(Form $form): Form
|
|
{
|
|
return $form
|
|
->schema([
|
|
Forms\Components\TextInput::make('register_id')
|
|
->label('Register ID')
|
|
->default(function () {
|
|
|
|
$datePart = Carbon::now()->format('ymd'); // 260527
|
|
|
|
$prefix = 'CRI' . $datePart . 'V';
|
|
|
|
// Get latest record for today
|
|
$lastRecord = VisitorEntry::where('register_id', 'like', $prefix . '%')
|
|
->latest('id')
|
|
->first();
|
|
|
|
if ($lastRecord) {
|
|
$lastNumber = (int) substr($lastRecord->register_id, -3);
|
|
$nextNumber = str_pad($lastNumber + 1, 3, '0', STR_PAD_LEFT);
|
|
} else {
|
|
$nextNumber = '001';
|
|
}
|
|
|
|
return $prefix . $nextNumber;
|
|
})
|
|
->readOnly(),
|
|
Forms\Components\TextInput::make('mobile_number')
|
|
->label('Mobile Number')
|
|
->length(10)
|
|
->reactive()
|
|
->extraInputAttributes([
|
|
'oninput' => 'this.value = this.value.replace(/[^0-9]/g, "").slice(0, 10)', // blocks non-numbers + limits to 10 chars
|
|
'maxlength' => 10,
|
|
])
|
|
->required()
|
|
->extraAttributes([
|
|
'id' => 'mobile_number_input',
|
|
'x-data' => '{ value: "" }',
|
|
'x-model' => 'value',
|
|
'wire:keydown.enter.prevent' => 'processMobile(value)',
|
|
]),
|
|
Forms\Components\TextInput::make('name')
|
|
->label('Name')
|
|
->required()
|
|
->reactive()
|
|
->extraInputAttributes([
|
|
'oninput' => 'this.value = this.value.replace(/[^a-zA-Z\s]/g, "")',
|
|
]),
|
|
Forms\Components\Select::make('type')
|
|
->label('Type')
|
|
->reactive()
|
|
->options([
|
|
'Student' => 'Student',
|
|
'Consultant' => 'Consultant',
|
|
'Vendor' => 'Vendor',
|
|
'Other' => 'Other',
|
|
])
|
|
->required()
|
|
->dehydrateStateUsing(function ($state, callable $get) {
|
|
return $state == 'Other'
|
|
? $get('other_type')
|
|
: $state;
|
|
}),
|
|
Forms\Components\TextInput::make('other_type')
|
|
->label('Specify Type')
|
|
->reactive()
|
|
->visible(fn (callable $get) => $get('type') == 'Other')
|
|
->required(fn (callable $get) => $get('type') == 'Other')
|
|
->dehydrated(false),
|
|
Forms\Components\TextInput::make('company')
|
|
->label('Company')
|
|
->required(),
|
|
Forms\Components\Select::make('department')
|
|
->label('Employee Department')
|
|
->options(
|
|
\App\Models\EmployeeMaster::distinct()
|
|
->pluck('department', 'department')
|
|
)
|
|
->required()
|
|
->reactive()
|
|
->afterStateUpdated(function (callable $set) {
|
|
$set('employee_master_id', null);
|
|
$set('code', null);
|
|
}),
|
|
// Forms\Components\Select::make('employee_master_id')
|
|
// ->label('Recipient Employee')
|
|
// ->required()
|
|
// ->options(function (callable $get) {
|
|
// $department = $get('department');
|
|
|
|
// if (!$department) {
|
|
// return [];
|
|
// }
|
|
|
|
// return \App\Models\EmployeeMaster::where('department', $department)
|
|
// ->pluck('name', 'id');
|
|
// })
|
|
// ->reactive()
|
|
// ->afterStateUpdated(function (callable $set, callable $get, ?string $state) {
|
|
// $department = $get('department');
|
|
|
|
// $employee = \App\Models\EmployeeMaster::where('id', $state)
|
|
// ->where('department', $department)
|
|
// ->first();
|
|
|
|
// $set('code', $employee ? $employee->code : '');
|
|
// }),
|
|
|
|
Forms\Components\Select::make('employee_master_id')
|
|
->label('Recipient Employee')
|
|
->required()
|
|
->options(function (callable $get) {
|
|
$department = $get('department');
|
|
// Always load ALL employees, filter by department if set
|
|
if ($department) {
|
|
return \App\Models\EmployeeMaster::where('department', $department)
|
|
->pluck('name', 'id');
|
|
}
|
|
// Fallback: load all so fill() can always match the ID
|
|
return \App\Models\EmployeeMaster::pluck('name', 'id');
|
|
})
|
|
->reactive()
|
|
->afterStateUpdated(function (callable $set, ?string $state) {
|
|
$employee = \App\Models\EmployeeMaster::find($state);
|
|
$set('code', $employee?->code ?? '');
|
|
}),
|
|
|
|
Forms\Components\TextInput::make('code')
|
|
->label('Employee Code')
|
|
->readOnly(),
|
|
Forms\Components\Textarea::make('purpose_of_visit')
|
|
->label('Purpose of Visit')
|
|
->required(),
|
|
Forms\Components\TextInput::make('number_of_person')
|
|
->numeric()
|
|
->default(1)
|
|
->required(),
|
|
Forms\Components\DateTimePicker::make('in_time')
|
|
->label('In Time')
|
|
->required()
|
|
->default(now()),
|
|
Forms\Components\DateTimePicker::make('out_time')
|
|
->label('Out Time'),
|
|
Forms\Components\DateTimePicker::make('valid_upto')
|
|
->label('Valid Upto')
|
|
->after('in_time')
|
|
->validationMessages([
|
|
'after' => 'Valid Upto must be after the In Time.',
|
|
]),
|
|
Forms\Components\View::make('components.webcam-field')
|
|
->columnSpanFull(),
|
|
Forms\Components\Hidden::make('photo'),
|
|
Forms\Components\Hidden::make('created_by')
|
|
->label('created_by')
|
|
->default(Filament::auth()->user()?->name),
|
|
Forms\Components\Hidden::make('updated_by')
|
|
->label('updated_by')
|
|
->default(Filament::auth()->user()?->name),
|
|
]);
|
|
}
|
|
|
|
public static function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->columns([
|
|
Tables\Columns\TextColumn::make('No.')
|
|
->label('NO')
|
|
->alignCenter()
|
|
->getStateUsing(function ($record, $livewire, $column, $rowLoop) {
|
|
$paginator = $livewire->getTableRecords();
|
|
$perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10;
|
|
$currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1;
|
|
|
|
return ($currentPage - 1) * $perPage + $rowLoop->iteration;
|
|
}),
|
|
Tables\Columns\ImageColumn::make('photo')
|
|
->label('Photo')
|
|
->disk('public')
|
|
->height(50)
|
|
->width(50)
|
|
// ->defaultImageUrl('https://ui-avatars.com/api/?name=Visitor&background=555&color=fff')
|
|
->defaultImageUrl(asset('images/profile.png'))
|
|
->alignCenter()
|
|
->extraImgAttributes(['style' => 'border-radius: 6px; object-fit: cover;']),
|
|
Tables\Columns\TextColumn::make('register_id')
|
|
->label('Register ID')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('type')
|
|
->label('Visitor Type')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('name')
|
|
->label('Visitor Name')
|
|
->sortable()
|
|
->alignCenter()
|
|
->searchable(),
|
|
Tables\Columns\TextColumn::make('mobile_number')
|
|
->label('Visitor Mobile Number')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('employeeMaster.name')
|
|
->label('Recipient Name')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('employeeMaster.code')
|
|
->label('Receipient ID')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('employeeMaster.department')
|
|
->label('Receipient Department')
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('number_of_person')
|
|
->label('Number of Person')
|
|
->numeric()
|
|
->alignCenter()
|
|
->sortable(),
|
|
Tables\Columns\TextColumn::make('in_time')
|
|
->label('In Time')
|
|
->dateTime()
|
|
->sortable()
|
|
->alignCenter(),
|
|
Tables\Columns\TextColumn::make('out_time')
|
|
->label('Out Time')
|
|
->dateTime()
|
|
->sortable()
|
|
->alignCenter(),
|
|
Tables\Columns\TextColumn::make('valid_upto')
|
|
->label('Valid Upto')
|
|
->dateTime()
|
|
->sortable()
|
|
->alignCenter()
|
|
->color(fn ($record) => $record->valid_upto && \Carbon\Carbon::parse($record->valid_upto)->isPast()
|
|
? 'danger'
|
|
: 'success'
|
|
),
|
|
Tables\Columns\TextColumn::make('created_at')
|
|
->label('Created At')
|
|
->dateTime()
|
|
->sortable()
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->alignCenter(),
|
|
Tables\Columns\TextColumn::make('updated_at')
|
|
->label('Updated At')
|
|
->dateTime()
|
|
->sortable()
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->alignCenter(),
|
|
Tables\Columns\TextColumn::make('deleted_at')
|
|
->label('Deleted At')
|
|
->dateTime()
|
|
->sortable()
|
|
->toggleable(isToggledHiddenByDefault: true)
|
|
->alignCenter(),
|
|
])
|
|
->filters([
|
|
Tables\Filters\TrashedFilter::make(),
|
|
])
|
|
->actions([
|
|
Tables\Actions\ViewAction::make(),
|
|
Tables\Actions\EditAction::make(),
|
|
])
|
|
->bulkActions([
|
|
Tables\Actions\BulkActionGroup::make([
|
|
Tables\Actions\DeleteBulkAction::make(),
|
|
Tables\Actions\ForceDeleteBulkAction::make(),
|
|
Tables\Actions\RestoreBulkAction::make(),
|
|
]),
|
|
]);
|
|
}
|
|
|
|
public static function infolist(Infolist $infolist): Infolist
|
|
{
|
|
return $infolist
|
|
->schema([
|
|
|
|
// ── Visitor Photo (full width at the top) ──
|
|
Section::make('Visitor Photo')
|
|
->schema([
|
|
ImageEntry::make('photo')
|
|
->label('')
|
|
->disk('public')
|
|
->height(220)
|
|
->defaultImageUrl(asset('images/profile.png'))
|
|
->extraImgAttributes([
|
|
'style' => 'border-radius: 10px; object-fit: cover;'
|
|
]),
|
|
])
|
|
->columnSpanFull(),
|
|
|
|
// ── Visitor Details ──
|
|
Section::make('Visitor Details')
|
|
->schema([
|
|
TextEntry::make('name')
|
|
->label('Visitor Name'),
|
|
TextEntry::make('mobile_number')
|
|
->label('Mobile Number'),
|
|
TextEntry::make('type')
|
|
->label('Visitor Type')
|
|
->badge()
|
|
->color(fn (string $state): string => match ($state) {
|
|
'internal' => 'success',
|
|
'external' => 'warning',
|
|
default => 'gray',
|
|
}),
|
|
TextEntry::make('company')
|
|
->label('Company'),
|
|
TextEntry::make('purpose_of_visit')
|
|
->label('Purpose of Visit')
|
|
->columnSpanFull(),
|
|
])
|
|
->columns(2),
|
|
|
|
// ── Employee Details ──
|
|
Section::make('Recipient Details')
|
|
->schema([
|
|
TextEntry::make('employeeMaster.name')
|
|
->label('Recipient Employee'),
|
|
TextEntry::make('code')
|
|
->label('Employee Code'),
|
|
TextEntry::make('employeeMaster.department')
|
|
->label('Department'),
|
|
TextEntry::make('number_of_person')
|
|
->label('Number of Persons'),
|
|
])
|
|
->columns(2),
|
|
|
|
// ── Visit Timing ──
|
|
Section::make('Visit Timing')
|
|
->schema([
|
|
TextEntry::make('in_time')
|
|
->label('In Time')
|
|
->dateTime(),
|
|
TextEntry::make('out_time')
|
|
->label('Out Time')
|
|
->dateTime(),
|
|
TextEntry::make('valid_upto')
|
|
->label('Valid Upto')
|
|
->dateTime()
|
|
->badge()
|
|
->color(fn ($record) => $record->valid_upto && \Carbon\Carbon::parse($record->valid_upto)->isPast()
|
|
? 'danger'
|
|
: 'success'
|
|
),
|
|
])
|
|
->columns(2),
|
|
|
|
]);
|
|
}
|
|
|
|
public static function getRelations(): array
|
|
{
|
|
return [
|
|
//
|
|
];
|
|
}
|
|
|
|
public static function getPages(): array
|
|
{
|
|
return [
|
|
'index' => Pages\ListVisitorEntries::route('/'),
|
|
'create' => Pages\CreateVisitorEntry::route('/create'),
|
|
'view' => Pages\ViewVisitorEntry::route('/{record}'),
|
|
'edit' => Pages\EditVisitorEntry::route('/{record}/edit'),
|
|
];
|
|
}
|
|
|
|
public static function getEloquentQuery(): Builder
|
|
{
|
|
return parent::getEloquentQuery()
|
|
->withoutGlobalScopes([
|
|
SoftDeletingScope::class,
|
|
]);
|
|
}
|
|
}
|