schema([ Forms\Components\Select::make('plant_id') ->label('Plant') ->relationship('plant', 'name') ->required() ->reactive() ->options(function (callable $get) { $userHas = Filament::auth()->user()->plant_id; return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray(); }) ->default(function () { return optional(GuardPatrolEntry::where('created_by', Filament::auth()->user()?->name)->latest()->first())->plant_id; }) ->disabled(fn (Get $get) => !empty($get('id'))) ->afterStateUpdated(function ($state, callable $set, callable $get) { $plantId = $get('plant_id'); if (!$plantId) { // $set('gPePlantError', 'Please select a plant first.'); $set('gPePlantError', 'Please select a plant first.'); return; } else { $set('patrol_time', now()->format('Y-m-d H:i:s')); $set('updated_by', Filament::auth()->user()?->name); $set('gPePlantError', null); } }) ->extraAttributes(fn ($get) => [ 'class' => $get('gPePlantError') ? 'border-red-500' : '', ]) ->hint(fn ($get) => $get('gPePlantError') ? $get('gPePlantError') : null) ->hintColor('danger'), Forms\Components\Select::make('guard_name_id') ->label('Guard Name') // ->relationship('guardNames', 'name') ->options(function (callable $get) { $plantId = $get('plant_id'); if (!$plantId) { return []; } return GuardName::where('plant_id', $plantId) ->pluck('name', 'id') ->toArray(); }) ->required() ->reactive() ->default(function () { return optional(GuardPatrolEntry::where('created_by', Filament::auth()->user()?->name)->latest()->first())->guard_name_id; }) ->disabled(fn (Get $get) => !empty($get('id'))) ->afterStateUpdated(function ($state, callable $set, callable $get) { $guardName = $get('guard_name_id'); if (!$guardName) { $set('gPeGuardNameError', 'Please select a guard name first.'); return; } else { $set('patrol_time', now()->format('Y-m-d H:i:s')); $set('updated_by', Filament::auth()->user()?->name); $set('gPeGuardNameError', null); } }) ->extraAttributes(fn ($get) => [ 'class' => $get('gPeGuardNameError') ? 'border-red-500' : '', ]) ->hint(fn ($get) => $get('gPeGuardNameError') ? $get('gPeGuardNameError') : null) ->hintColor('danger'), Forms\Components\Hidden::make('check_point_name')//TextInput ->label('Check Point Name') ->reactive() ->afterStateUpdated(function ($state, callable $set, callable $get) { $set('patrol_time', now()->format('Y-m-d H:i:s')); $set('updated_by', Filament::auth()->user()?->name); }) ->extraAttributes([ 'x-on:keydown.enter.prevent' => '$wire.processCheckPointName()', ]), Forms\Components\Select::make('check_point_name_id') ->label('Check Point Name') // ->relationship('checkPointNames', 'name') ->options(function (callable $get) { $plantId = $get('plant_id'); if (!$plantId) { return []; } return CheckPointName::where('plant_id', $plantId) ->pluck('name', 'id') ->toArray(); }) ->required() ->reactive() // ->default(function () { // return optional(GuardPatrolEntry::where('created_by', Filament::auth()->user()?->name)->latest()->first())->check_point_name_id; // }) ->disabled(fn (Get $get) => !empty($get('id'))) ->afterStateUpdated(function ($state, callable $set, callable $get) { $checkPointName = $get('check_point_name_id'); if (!$checkPointName) { $set('check_point_name_id', null); $set('gPeCheckPointNameError', 'Please select a check point name first.'); return; } else { $set('patrol_time', now()->format('Y-m-d H:i:s')); $set('updated_by', Filament::auth()->user()?->name); $set('gPeCheckPointNameError', null); } }) ->extraAttributes(fn ($get) => [ 'class' => $get('gPeCheckPointNameError') ? 'border-red-500' : '', ]) ->hint(fn ($get) => $get('gPeCheckPointNameError') ? $get('gPeCheckPointNameError') : null) ->hintColor('danger') ->rule(function (callable $get) { return Rule::unique('guard_patrol_entries', 'check_point_name_id') ->where('guard_name_id', $get('guard_name_id')) ->where('patrol_time', now()) ->where('plant_id', $get('plant_id')) ->ignore($get('id')); }), Forms\Components\TextInput::make('reader_code') ->label('Reader Code') ->hidden(fn (Get $get) => !$get('id')) ->reactive() ->afterStateUpdated(function ($state, callable $set, callable $get) { if(!$get('id')) { $set('patrol_time', now()->format('Y-m-d H:i:s')); } $set('updated_by', Filament::auth()->user()?->name); }), Forms\Components\DateTimePicker::make('patrol_time') ->label('Patrol Time') ->reactive() ->default(fn () => now()) ->readOnly(fn (Get $get) => !$get('id')) ->afterStateUpdated(function ($state, callable $set, callable $get) { $set('updated_by', Filament::auth()->user()?->name); }) ->required() ->rule(function (callable $get) { return Rule::unique('guard_patrol_entries', 'patrol_time') ->where('guard_name_id', $get('guard_name_id')) ->where('check_point_name_id', $get('check_point_name_id')) ->where('plant_id', $get('plant_id')) ->ignore($get('id')); }), Forms\Components\Hidden::make('created_by') ->default(fn () => Filament::auth()->user()?->name) ->required(), Forms\Components\Hidden::make('updated_by') ->reactive() ->default(fn () => Filament::auth()->user()?->name) ->required(), Forms\Components\TextInput::make('id') ->hidden() ->reactive() ->afterStateUpdated(function ($state, callable $set, callable $get) { $set('updated_by', Filament::auth()->user()?->name); }) ->readOnly(), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('No.') ->label('No.') ->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\TextColumn::make('id') // ->label('ID') // ->numeric() // ->sortable(), Tables\Columns\TextColumn::make('plant.name') ->label('Plant') ->alignCenter() ->sortable(), Tables\Columns\TextColumn::make('guardNames.name') //guard_name_id ->label('Guard Name') ->alignCenter() ->sortable(), Tables\Columns\TextColumn::make('checkPointNames.name') //check_point_name_id ->label('Check Point Name') ->alignCenter() ->sortable(), // Tables\Columns\TextColumn::make('reader_code') // ->label('Reader Code') // ->alignCenter() // ->sortable(), Tables\Columns\TextColumn::make('patrol_time') ->label('Patrol Time') ->dateTime() ->alignCenter() ->sortable(), Tables\Columns\TextColumn::make('created_at') ->label('Created At') ->dateTime() ->alignCenter() ->sortable(), Tables\Columns\TextColumn::make('created_by') ->label('Created By') ->alignCenter(), Tables\Columns\TextColumn::make('updated_at') ->label('Updated At') ->dateTime() ->alignCenter() ->sortable(), Tables\Columns\TextColumn::make('updated_by') ->label('Updated By') ->alignCenter(), Tables\Columns\TextColumn::make('deleted_at') ->label('Deleted At') ->dateTime() ->alignCenter() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Tables\Filters\TrashedFilter::make(), Filter::make('advanced_filters') ->label('Advanced Filters') ->form([ Select::make('Plant') ->label('Select Plant') ->nullable() // ->options(function () { // return Plant::pluck('name', 'id'); // }) ->options(function (callable $get) { $userHas = Filament::auth()->user()->plant_id; return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray(); }) ->reactive(), // ->afterStateUpdated(function ($state, callable $set, callable $get) { // $set('sticker_master_id', null); // $set('sap_msg_status', null); // }), Select::make('Guard Name') ->label('Select Guard Name') ->options(function (callable $get) { $plantId = $get('Plant'); if (!$plantId) { return []; } return GuardName::where('plant_id', $plantId)->pluck('name', 'id')->toArray(); }) ->reactive(), Select::make('Check Point Name') ->label('Select Check Point Name') ->options(function (callable $get) { $plantId = $get('Plant'); if (!$plantId) { return []; } return CheckPointName::where('plant_id', $plantId)->pluck('name', 'id')->toArray(); }) ->reactive(), Select::make('Created By') ->label('Created By') ->placeholder('Select Created By') ->options(function (callable $get) { $plantId = $get('Plant'); if (!$plantId) { return []; } return GuardPatrolEntry::where('plant_id', $plantId)->orderBy('patrol_time', 'asc')->get()->unique('created_by')->pluck('created_by', 'created_by')->toArray();//, 'id' }) ->reactive(), DateTimePicker::make(name: 'From Patrol Time') ->label('From Patrol Time') ->beforeOrEqual('To Patrol Time') ->placeholder(placeholder: 'Select From Patrol Time') ->reactive() ->native(false), DateTimePicker::make('To Patrol Time') ->label('To Patrol Time') ->afterOrEqual('From Patrol Time') ->placeholder(placeholder: 'Select To Patrol Time') ->reactive() ->native(false), ]) ->query(function ($query, array $data) { //Hide all records initially if no filters are applied if (empty($data['Plant']) && empty($data['Guard Name']) && empty($data['Check Point Name']) && empty($data['Created By']) && empty($data['From Patrol Time']) && empty($data['To Patrol Time'])) { return $query->whereRaw('1 = 0'); } if (!empty($data['Plant'])) { $query->where('plant_id', $data['Plant']); } if (!empty($data['Guard Name'])) { $query->where('guard_name_id', $data['Guard Name']); } if (!empty($data['Check Point Name'])) { $query->where('check_point_name_id', $data['Check Point Name']); } if (!empty($data['Created By'])) { $query->where('created_by', $data['Created By']); } if (!empty($data['From Patrol Time'])) { $query->where('patrol_time', '>=', $data['From Patrol Time']); } if (!empty($data['To Patrol Time'])) { $query->where('patrol_time', '<=', $data['To Patrol Time']); } }) ->indicateUsing(function (array $data) { $indicators = []; if (!empty($data['Plant'])) { $indicators[] = 'Plant: ' . Plant::where('id', $data['Plant'])->value('name'); } if (!empty($data['Guard Name'])) { $indicators[] = 'Guard Name: ' . GuardName::where('plant_id', $data['Plant'])->where('id', $data['Guard Name'])->value('name'); } if (!empty($data['Check Point Name'])) { $indicators[] = 'Check Point Name: ' . CheckPointName::where('plant_id', $data['Plant'])->where('id', $data['Check Point Name'])->value('name'); } if (!empty($data['Created By'])) { $indicators[] = 'Created By: ' . $data['Created By']; } if (!empty($data['From Patrol Time'])) { $indicators[] = 'From: ' . $data['From Patrol Time']; } if (!empty($data['To Patrol Time'])) { $indicators[] = 'To: ' . $data['To Patrol Time']; } return $indicators; }) ]) ->filtersFormMaxHeight('280px') ->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(), ]), ]) ->headerActions([ Tables\Actions\Action::make('guard_patrol_entries') ->label('Import Guard Patrol Entries') ->form([ Select::make('plant_id') // ->options(Plant::pluck('name', 'id')->toArray()) ->options(function (callable $get) { $userHas = Filament::auth()->user()->plant_id; return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::pluck('name', 'id')->toArray(); }) ->label('Select Plant') ->reactive() ->required() ->default(function () { return optional(GuardPatrolEntry::where('created_by', Filament::auth()->user()?->name)->latest()->first())->plant_id; }) ->afterStateUpdated(function ($state, callable $set, callable $get) { $plantId = $get('plant_id'); $set('guard_patrol_entry', null); if (!$plantId) { $set('gPeSelectPlantError', 'Please select a plant first.'); return; } else { $set('gPeSelectPlantError', null); } }) ->extraAttributes(fn ($get) => [ 'class' => $get('gPeSelectPlantError') ? 'border-red-500' : '', ]) ->hint(fn ($get) => $get('gPeSelectPlantError') ? $get('gPeSelectPlantError') : null) ->hintColor('danger'), FileUpload::make('guard_patrol_entry') ->label('Import Guard Patrol Entries') // ->required() ->preserveFilenames() // <- this keeps the original filename ->storeFiles(false) ->reactive() ->required() ->disk('local') ->visible(fn (Get $get) => !empty($get('plant_id'))) ->directory('uploads/temp') // Allow only .xlsx and .xls ->acceptedFileTypes(['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel']) // Optional: Custom error message if needed ->validationMessages([ 'mimes' => 'Only .xlsx and .xls files are allowed.', ]) // Server-side validation for extra safety ->rules(['mimes:xlsx,xls']), ]) ->action(function (array $data) { $uploadedFile = $data['guard_patrol_entry']; $plantId = $data['plant_id']; $user = Filament::auth()->user()->name; $disk = Storage::disk('local'); $originalName = $uploadedFile->getClientOriginalName(); // e.g. 3RA0018732.xlsx $uploadedFileName = pathinfo($originalName, PATHINFO_FILENAME); // $uploadedFileName = trim(str_replace('.xlsx', '', $originalName)); $folderPath = Configuration::where('c_name', 'GUARD_PATROL_ENTRY_FOLDER_PATH')->where('plant_id', $plantId)->value('c_value'); if(!$folderPath) { Notification::make() ->title('Upload Folder Path Not Found!') ->body('Please set the folder path in configuration for Guard Patrol Entry.') ->danger() ->send(); return; } $fullFolderPath = "uploads/$folderPath"; // Check if the folder exists, if not, create it if (!Storage::disk('local')->exists($fullFolderPath)) { Storage::disk('local')->makeDirectory($fullFolderPath); } $path = $uploadedFile->storeAs($fullFolderPath, $originalName, 'local'); $fullPath = Storage::disk('local')->path($path); if ($fullPath && file_exists($fullPath)) { $rows = Excel::toArray(null, $fullPath)[0]; if((count($rows) - 1) <= 0) { Notification::make() ->title('Invalid Guard Patrol Entry Found') ->body('Uploaded excel sheet is empty or
contains no valid data.') ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } return; } $invalidRows=[]; $invalidGuardCheckPoints=[]; $unknownGuards=[]; $unknownCheckPoints=[]; $invalidPatrolTimes=[]; $validRowsFound = false; foreach ($rows as $index => $row) { if ($index === 0) continue; // Skip header $rowNumber = trim($row[0]); $guardName = trim($row[1]); $checkPointName = trim($row[2]); $readerCode = trim($row[3]); $patrolTime = trim($row[4]); if (empty($rowNumber)) { continue; } if (empty($guardName) || empty($checkPointName) || empty($readerCode) || empty($patrolTime)) { $invalidRows[] = $rowNumber; continue; } else { if(Str::length($guardName) < 3 || Str::length($checkPointName) < 3 || Str::length($readerCode) < 3 || Str::length($patrolTime) < 3) { $invalidGuardCheckPoints[] = $rowNumber; continue; } else { $isValidRow = true; $guardNames = GuardName::where('plant_id', $plantId)->where('name', $guardName)->first(); if (!$guardNames) { $unknownGuards[] = $guardName; $isValidRow = false; } $checkPointNames = CheckPointName::where('plant_id', $plantId)->where('name', $checkPointName)->first(); if (!$checkPointNames) { $unknownCheckPoints[] = $checkPointName; $isValidRow = false; } $formats = ['d-m-Y H:i:s', 'd-m-Y H:i']; //'07-05-2025 08:00' or '07-05-2025 08:00:00' $patrolDateTime = null; foreach ($formats as $format) { try { $patrolDateTime = Carbon::createFromFormat($format, $patrolTime); break; } catch (\Exception $e) { $invalidPatrolTimes[] = $rowNumber; $isValidRow = false; } } if (!isset($patrolDateTime)) { $invalidPatrolTimes[] = $rowNumber; $isValidRow = false; //$warnMsg[] = "Invalid 'Patrol DateTime' format. Expected DD-MM-YYYY HH:MM:SS"; } if ($isValidRow && !$validRowsFound) { $validRowsFound = true; } } } } $uniqueInvalidRows = array_unique($invalidRows); if (!empty($uniqueInvalidRows)) { Notification::make() ->title('Invalid Guard Patrol Entry Found') ->body('The following rows contain empty values (Guard name or Check point name or Reader code or Patrol time):
' . implode(', ', $uniqueInvalidRows)) ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } return; } //should contain minimum 13 digit alpha numeric values $uniqueInvalidGuardCheckPoints = array_unique($invalidGuardCheckPoints); if (!empty($uniqueInvalidGuardCheckPoints)) { Notification::make() ->title('Invalid Guard Patrol Entry Found') ->body('The following rows contain invalid values (Guard name or Check point name or Reader code or Patrol time):
' . implode(', ', $uniqueInvalidGuardCheckPoints)) ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } return; } $invalidDataFound = false; $uniqueUnknownGuards = array_unique($unknownGuards); if (!empty($uniqueUnknownGuards)) { Notification::make() ->title('Unknown Guard Names Found') ->body("The following guard names aren't exist in master data:
" . implode(', ', $uniqueUnknownGuards)) ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } $invalidDataFound = true; } $uniqueUnknownCheckPoints = array_unique($unknownCheckPoints); if (!empty($uniqueUnknownCheckPoints)) { Notification::make() ->title('Unknown Check Point Names Found') ->body("The following check point names aren't exist in master data:
" . implode(', ', $uniqueUnknownCheckPoints)) ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } $invalidDataFound = true; } $uniqueInvalidPatrolTimes = array_unique($invalidPatrolTimes); if (!empty($uniqueInvalidPatrolTimes)) { Notification::make() ->title('Invalid Patrol Time Format Found') ->body("The following rows contains invalid patrol time format (Expected 'DD-MM-YYYY HH:MM:SS'):
" . implode(', ', $uniqueInvalidPatrolTimes)) ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } $invalidDataFound = true; } if ($invalidDataFound) { return; } if (!$validRowsFound) { Notification::make() ->title('Invalid Guard Patrol Entry Found') ->body('Uploaded excel sheet is empty or
contains no valid data.') ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } return; } $validCnt = 0; $dupCnt = 0; $validRowsFound = false; foreach ($rows as $index => $row) { if ($index === 0) continue; // Skip header $rowNumber = trim($row[0]); $guardName = trim($row[1]); $checkPointName = trim($row[2]); $readerCode = trim($row[3]); $patrolTime = trim($row[4]); if (empty($rowNumber)) { continue; } if (empty($guardName) || empty($checkPointName) || empty($readerCode) || empty($patrolTime)) { continue; } $isValidRow = true; $formats = ['d-m-Y H:i:s', 'd-m-Y H:i']; //'07-05-2025 08:00' or '07-05-2025 08:00:00' $patrolDateTime = null; foreach ($formats as $format) { try { $patrolDateTime = Carbon::createFromFormat($format, $patrolTime); break; } catch (\Exception $e) { $isValidRow = false; } } if (!isset($patrolDateTime)) { $isValidRow = false; } if ($isValidRow) { $guardNames = GuardName::where('plant_id', $plantId)->where('name', $guardName)->first(); $checkPointNames = CheckPointName::where('plant_id', $plantId)->where('name', $checkPointName)->first(); $guardEntryFound = GuardPatrolEntry::where('plant_id', $plantId)->where('guard_name_id', $guardNames->id)->where('check_point_name_id', $checkPointNames->id)->where('patrol_time', $patrolDateTime->format('Y-m-d H:i:s'))->first(); if ($guardEntryFound) { //$warnMsg[] = "Duplicate guard patrol entry found"; $dupCnt++; continue; } else { $validCnt++; GuardPatrolEntry::updateOrCreate([ 'plant_id' => $plantId, 'guard_name_id' => $guardNames->id, 'check_point_name_id' => $checkPointNames->id, 'patrol_time' => $patrolDateTime->format('Y-m-d H:i:s') ], [ 'reader_code' => $readerCode, 'created_by' => $user, 'updated_by' => $user ] ); $validRowsFound = true; } } } if (!$validRowsFound && $dupCnt > 0) { Notification::make() ->title('Duplicate Guard Patrol Entry Found') ->body("Uploaded excel sheet contains '{$dupCnt}' duplicate entries!
Please check the uploaded file and try again.") ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } } else if ($validRowsFound && $validCnt > 0) { //session(['guard_patrol_entry_path' => $fullPath]); Notification::make() ->title("Success: '{$validCnt}' guard patrol entries imported successfully.") ->success() ->send(); if ($disk->exists($path)) { $disk->delete($path); } } else { Notification::make() ->title('Failed: Something went wrong while uploading guard patrol entries!') ->danger() ->send(); if ($disk->exists($path)) { $disk->delete($path); } } } }) ->visible(function() { return Filament::auth()->user()->can('view import guard patrol entries'); }), ImportAction::make() ->label('Import Guard Patrol Entry') // ->hidden() ->color('warning') ->importer(GuardPatrolEntryImporter::class) ->visible(function() { return Filament::auth()->user()->can('view import guard patrol entry'); }), ExportAction::make() ->label('Export Guard Patrol Entry') ->color('warning') ->exporter(GuardPatrolEntryExporter::class) ->visible(function() { return Filament::auth()->user()->can('view export guard patrol entry'); }), ]); } public static function getRelations(): array { return [ // ]; } public static function getPages(): array { return [ 'index' => Pages\ListGuardPatrolEntries::route('/'), 'create' => Pages\CreateGuardPatrolEntry::route('/create'), 'view' => Pages\ViewGuardPatrolEntry::route('/{record}'), 'edit' => Pages\EditGuardPatrolEntry::route('/{record}/edit'), ]; } public static function getEloquentQuery(): Builder { return parent::getEloquentQuery() ->withoutGlobalScopes([ SoftDeletingScope::class, ]); } }