diff --git a/app/Filament/Resources/LeakTestReadingResource.php b/app/Filament/Resources/LeakTestReadingResource.php new file mode 100644 index 0000000..2432f21 --- /dev/null +++ b/app/Filament/Resources/LeakTestReadingResource.php @@ -0,0 +1,452 @@ +schema([ + Section::make('') + ->schema([ + Forms\Components\Select::make('plant_id') + ->required() + ->label('Plant Name') + ->relationship('plant', 'name') + ->searchable() + ->reactive() + // ->columnSpan(1) + ->options(function (callable $get) { + $userHas = Filament::auth()->user()->plant_id; + + return ($userHas && strlen($userHas) > 0) ? Plant::where('id', $userHas)->pluck('name', 'id')->toArray() : Plant::orderBy('code')->pluck('name', 'id')->toArray(); + }) + ->default(function () { + return optional(LeakTestReading::latest()->first())->plant_id; + }) + ->disabled(fn (Get $get) => ! empty($get('id'))) + ->afterStateUpdated(function ($state, $set, callable $get) { + $plantId = $get('plant_id'); + + $set('lsrPlantError', null); + $set('item_code', null); + $set('serial_number', null); + $set('test_status', null); + if (! $plantId) { + $set('lsrPlantError', 'Please select a plant first.'); + + return; + } + }) + ->extraAttributes(fn ($get) => [ + 'class' => $get('lsrPlantError') ? 'border-red-500' : '', + ]) + ->hint(fn ($get) => $get('lsrPlantError') ? $get('lsrPlantError') : null) + ->hintColor('danger'), + Forms\Components\TextInput::make('item_code') + ->required() + ->label('Item Code') + ->placeholder('Scan the valid item code') + ->autofocus(true) + ->alphaNum() + ->minLength(6) + ->reactive() + ->disabled(fn (Get $get) => ! empty($get('id'))) + ->afterStateUpdated(function ($state, callable $set, callable $get) { + $code = $get('item_code'); + // Ensure `linestop_id` is not cleared + if (! $code) { + $set('lsrCodeError', 'Scan the valid item code.'); + + return; + } else { + if (strlen($code) < 6) { + $set('lsrCodeError', 'Item code must be at least 6 digits.'); + + return; + } elseif (! ctype_alnum($code)) { + $set('item_code', null); + $set('lsrSerialError', 'Item code must contain only alpha-numeric characters!'); + } elseif (! preg_match('/^[a-zA-Z1-9][a-zA-Z0-9]{5,}$/', $code)) { + $set('item_code', null); + $set('lsrCodeError', "Item code should not begin with '0' or letter!"); + + return; + } + $set('lsrCodeError', null); + } + }) + ->extraAttributes(fn ($get) => [ + 'class' => $get('lsrCodeError') ? 'border-red-500' : '', + ]) + ->hint(fn ($get) => $get('lsrCodeError') ? $get('lsrCodeError') : null) + ->hintColor('danger'), + Forms\Components\TextInput::make('serial_number') + ->required() + ->label('Serial Number') + ->placeholder('Scan the valid serial number') + ->alphaNum() + ->minLength(9) + ->reactive() + ->disabled(fn (Get $get) => ! empty($get('id'))) + ->afterStateUpdated(function ($state, callable $set, callable $get) { + $serial = $get('serial_number'); + // Ensure `linestop_id` is not cleared + if (! $serial) { + $set('lsrSerialError', 'Scan the valid serial number!'); + + return; + } else { + if (strlen($serial) < 9) { + $set('lsrSerialError', 'Serial number must be at least 9 digits!'); + + return; + } elseif (! ctype_alnum($serial)) { + $set('serial_number', null); + $set('lsrSerialError', 'Serial number must contain only alpha-numeric characters!'); + } elseif (! preg_match('/^[1-9][a-zA-Z0-9]{8,}$/', $serial)) { + $set('serial_number', null); + $set('lsrSerialError', "Serial number should not begin with '0' or letter!"); + + return; + } + $set('lsrSerialError', null); + } + }) + ->extraAttributes(fn ($get) => [ + 'class' => $get('lsrSerialError') ? 'border-red-500' : '', + ]) + ->hint(fn ($get) => $get('lsrSerialError') ? $get('lsrSerialError') : null) + ->hintColor('danger'), + Forms\Components\Select::make('test_status') + ->required() + ->label('Test Status') + ->searchable() + ->reactive() + ->options(function (callable $set, callable $get) { + $plantId = $get('plant_id'); + $itemCode = $get('item_code'); + $serialNumber = $get('serial_number'); + + if (! $plantId || ! $itemCode || ! $serialNumber) { + $set('test_status', null); + + return []; + } + + return [ + 'P' => 'PASS', + 'F' => 'FAIL', + ]; + }) + ->default(function () { + $userHas = Filament::auth()->user()->plant_id; + + return ($userHas && strlen($userHas) > 0) ? 'F' : 'P'; + // return optional(CharacteristicApproverMaster::latest()->first())->approver_type ?? null; + }), + Forms\Components\TextInput::make('id') + ->hidden() + ->readOnly(), + ]) + ->columns(['default' => 1, 'sm' => 2]), + ]); + } + + 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('plant.name') + ->label('Plant Name') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('item_code') + ->label('Item Code') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('serial_number') + ->label('Serial Number') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('test_status') + ->label('Test Status') + ->alignCenter() + ->searchable() + ->sortable() + ->formatStateUsing(function ($state) { + return match ($state) { + 'P' => 'PASS', + 'F' => 'FAIL', + default => '-', + }; + }), + Tables\Columns\TextColumn::make('created_at') + ->label('Created At') + ->alignCenter() + ->dateTime() + ->sortable(), + Tables\Columns\TextColumn::make('updated_at') + ->label('Updated At') + ->alignCenter() + ->dateTime() + ->sortable(), + Tables\Columns\TextColumn::make('deleted_at') + ->label('Deleted At') + ->alignCenter() + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->searchPlaceholder('Search Serial Number') + ->filters([ + Tables\Filters\TrashedFilter::make(), + Filter::make('advanced_filters') + ->label('Advanced Filters') + ->form([ + Select::make('Plant') + ->label('Search by Plant Name') + ->nullable() + ->options(function (callable $get) { + $userHas = Filament::auth()->user()->plant_id; + + if ($userHas && strlen($userHas) > 0) { + return Plant::where('id', $userHas)->pluck('name', 'id')->toArray(); + } else { + return Plant::whereHas('leakTestReadings', function ($query) { + $query->whereNotNull('id'); + })->orderBy('code')->pluck('name', 'id')->toArray(); + } + }) + ->searchable() + ->reactive() + ->afterStateUpdated(function ($state, callable $set, callable $get): void { + $set('item_code', null); + $set('serial_number', null); + $set('test_status', null); + }), + Select::make('item_code') + ->label('Search by Item Code') + ->nullable() + ->options(function (callable $get) { + $plantId = $get('Plant'); + + return $plantId ? LeakTestReading::where('plant_id', $plantId)->pluck('item_code', 'item_code')->toArray() : []; + }) + ->searchable() + ->reactive() + ->afterStateUpdated(function ($state, callable $set, callable $get): void { + $set('serial_number', null); + $set('test_status', null); + }), + TextInput::make('serial_number') + ->label('Search by Serial Number') + ->placeholder(placeholder: 'Enter Serial Number') + ->afterStateUpdated(function ($state, callable $set, callable $get): void { + $set('test_status', null); + }), + Select::make('test_status') + ->label('Search by Test Status') + ->nullable() + ->options(function () { + return [ + 'P' => 'PASS', + 'F' => 'FAIL', + ]; + }) + ->searchable() + ->reactive(), + DateTimePicker::make(name: 'created_from') + ->label('Created From') + ->placeholder(placeholder: 'Select From DateTime') + ->reactive() + ->native(false), + DateTimePicker::make('created_to') + ->label('Created To') + ->placeholder(placeholder: 'Select To DateTime') + ->reactive() + ->native(false), + DateTimePicker::make(name: 'updated_from') + ->label('Updated From') + ->placeholder(placeholder: 'Select From DateTime') + ->reactive() + ->native(false), + DateTimePicker::make('updated_to') + ->label('Updated To') + ->placeholder(placeholder: 'Select To DateTime') + ->reactive() + ->native(false), + ]) + ->query(function ($query, array $data) { + // Hide all records initially if no filters are applied + if (empty($data['Plant']) && empty($data['item_code']) && empty($data['serial_number']) && empty($data['test_status']) && empty($data['created_from']) && empty($data['created_to']) && empty($data['updated_from']) && empty($data['updated_to'])) { + return $query->whereRaw('1 = 0'); + } + + if (! empty($data['Plant'])) { // $plant = $data['Plant'] ?? null + $query->where('plant_id', $data['Plant']); + } else { + $userHas = Filament::auth()->user()->plant_id; + + if ($userHas && strlen($userHas) > 0) { + return $query->whereRaw('1 = 0'); + } + } + + if (! empty($data['item_code'])) { + $query->where('item_code', $data['item_code']); + } + + if (! empty($data['serial_number'])) { + $query->where('serial_number', 'like', '%'.$data['serial_number'].'%'); + } + + if (! empty($data['test_status'])) { + $query->where('test_status', $data['test_status']); + } + + if (! empty($data['created_from'])) { + $query->where('created_at', '>=', $data['created_from']); + } + + if (! empty($data['created_to'])) { + $query->where('created_at', '<=', $data['created_to']); + } + + if (! empty($data['updated_from'])) { + $query->where('updated_at', '>=', $data['updated_from']); + } + + if (! empty($data['updated_to'])) { + $query->where('updated_at', '<=', $data['updated_to']); + } + }) + ->indicateUsing(function (array $data) { + $indicators = []; + + if (! empty($data['Plant'])) { + $indicators[] = 'Plant: '.Plant::where('id', $data['Plant'])->value('name'); + } else { + $userHas = Filament::auth()->user()->plant_id; + + if ($userHas && strlen($userHas) > 0) { + return 'Plant: Choose plant to filter records.'; + } + } + + if (! empty($data['item_code'])) { + $indicators[] = 'Item Code: '.$data['item_code']; + } + + if (! empty($data['serial_number'])) { + $indicators[] = 'Serial Number: '.$data['serial_number']; + } + + if (! empty($data['test_status'])) { + $indicators[] = 'Test Status: '.$data['test_status']; + } + + if (! empty($data['created_from'])) { + $indicators[] = 'From: '.$data['created_from']; + } + + if (! empty($data['created_to'])) { + $indicators[] = 'To: '.$data['created_to']; + } + + if (! empty($data['updated_from'])) { + $indicators[] = 'From: '.$data['updated_from']; + } + + if (! empty($data['updated_to'])) { + $indicators[] = 'To: '.$data['updated_to']; + } + + 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([ + ExportAction::make() + ->label('Export Leak Test Readings') + ->color('warning') + ->exporter(LeakTestReadingExporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view export leak test reading'); + }), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListLeakTestReadings::route('/'), + 'create' => Pages\CreateLeakTestReading::route('/create'), + 'view' => Pages\ViewLeakTestReading::route('/{record}'), + 'edit' => Pages\EditLeakTestReading::route('/{record}/edit'), + ]; + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +}