Új erőforrás: berendezések
Hozzuk létre neki a model-t és a migrációs fájlt.
php artisan make:model Device -m
Kezdjük a migrációs fájllal és annak mezőivel a devices adattáblában:
$table->string('name')->unique();
$table->string('erp_code')->unique();
$table->foreignId('type_id')->constrained('device_types')->onUpdate('cascade')->onDelete('cascade');
$table->string('plant');
$table->boolean('active')->default(false);
$table->string('history')->nullable();
$table->string('note')->nullable();
Az itt látható mezők természetesen lehetnek mások, de ez egy jó kiindulási pont lehet:
- name: minden berendezésnek van egy egyedi neve, amit kötelező megadni,
- az erp_code jelöli, hogy a cég vállalatirányítási rendszerében (vagy más rendszereiben) milyen egyedi azonosítóval rendelkezik az adott berendezés, ez szintén egyedi és kötelezően kitöltendő mező,
- type_id: külső kulcs, ami a berendezés típusát adja meg a device_types adattáblából, a kapcsolat szabályait kaszkádolással definiáltuk, így, ha törlésre vagy módosításra kerül egy berendezés típus, akkor az tovább gyűrűzik a berendezéseire is. Ez a megfontolás a későbbiekben módosítható, mivel nem biztos, hogy így szeretnénk majd megvalósítani, vagy például, ha integráljuk a rendszert más rendszerekkel, akkor nem biztos, hogy ilyen továbbgyűrűző törléseket végre szeretnénk hajtani...
- plant: üzem megadása szintén kötelező,
- active: aktivitást jelző kapcsoló, ami alapértelmezetten hamis értéket tartalmaz azért, hogy a későbbiekben lehessen be-ki kapcsolni az esetleges berendezéseket, ha elromlanak / megjavításra vagy beüzemelésre kerülnek.
- history: főleg a berendezés korábbi történetét tárolhatjuk itt el röviden, de a karbantartási rendszer működésbe lépésétől majd az új rendszer fogja már nyomon követni a berendezés történetét.
- note: bármilyen megjegyzés beírható ide, de üresen is lehet hagyni.
Mentés után hajtsuk végre a mingrálást:
php artisan migrate
Ezek alapján az Eloquent ORM-mel kitölthető mezői és az egy-több-es (1-n) kapcsolat a berendezés típusokhoz kapcsolódóan:
protected $fillable = [
'name',
'erp_code',
'type_id',
'plant',
'active',
'history',
'note'
];
public function type(): BelongsTo
{
return $this->belongsTo(DeviceType::class, 'type_id');
}
A BelongsTo visszatérési típushoz importáljuk a fájl tetején ezt: Illuminate\Database\Eloquent\Relations\BelongsTo
A berendezés típusokhoz több berendezés is tartozhat, így arra az oldalra is csináljuk meg a kapcsolatot:
public function devices(): HasMany
{
return $this->hasMany(Device::class, 'type_id');
}
A HasMany visszatérési típushoz importáljuk a fájl tetején ezt: Illuminate\Database\Eloquent\Relations\HasMany
Berendezés erőforrás
Hozzuk létre a berendezés erőforrást, állítsuk be az alapvető értékeit, definiáljuk a létrehozási/szerkesztési űrlapját és a listázó nézetét.
Erőforrás létrehozása és alapbeállításai
php artisan make:filament-resource Device --view
Itt már úgy hoztuk létre az erőforrást, hogy legyen adott hozzá a "show"
nézet is, mivel ezt fogják tudni böngészni azok, akik az erőforrás adataihoz
(később dokumentumaihoz) hozzáférhetnek, de szerkeszteni / törölni nem szabad majd nekik a berendezéseket.
Alapbeállítások módosítása és hozzáadása a DeviceResource.php-ban:
protected static ?string $navigationIcon = 'heroicon-o-wrench';
public static function getNavigationGroup(): string
{
return __('module_names.navigation_groups.administration');
}
public static function getModelLabel(): string
{
return __('module_names.devices.label');
}
public static function getPluralModelLabel(): string
{
return __('module_names.devices.plural_label');
}
A lang mappa, en és hu almappáiban lévő module_names.php fájlokban pedig adjuk hozzá a berendezésekhez tartozó szótár bejegyzéseket. Példaként a magyart ideteszem, de az angol párjáról se feledkezzünk meg (ha nem menne, akkor érdemes csekkolni a bejegyzés végén linkelt GitHub commit-et):
'devices' => [
'label' => 'Berendezés',
'plural_label' => 'Berendezések',
],
Létrehozási és szerkesztési űrlap mező újdonságok
Magán az űrlapon számos elem van, amelyeknek különböző a típusa és az egyes beállításai, finomságai. Előbb bemásolom ide magát az űrlapot, majd utána végigvesszük, hogy melyik elemnél mik a különlegességek. A DeviceResource.php form() metódusának visszatérési form sémájába illesszük be ezt:
Forms\Components\Section::make()
->schema([
Forms\Components\TextInput::make('name')->label(__('fields.name'))
->required()
->unique(ignoreRecord: true)
->maxLength(255),
Forms\Components\TextInput::make('erp_code')->label(__('fields.erp_code'))
->required()
->unique(ignoreRecord: true)
->maxLength(255),
Forms\Components\Select::make('type_id')->label(__('fields.type'))
->relationship('type', 'name')
->searchable()
->preload()
->createOptionForm([
Forms\Components\TextInput::make('name')->label(__('fields.name'))
->required()
->unique()
->maxLength(255)
])
->required(),
Forms\Components\TextInput::make('plant')->label(__('fields.plant'))
->required()
->maxLength(255),
Forms\Components\Toggle::make('active')->label(__('fields.active'))
->onColor('success')
->offColor('danger')
->columnSpan('full'),
Forms\Components\TextInput::make('history')->label(__('fields.history'))
->maxLength(255),
Forms\Components\TextInput::make('note')->label(__('fields.note'))
->maxLength(255),
])
Űrlap komponensek:
- name: ezt már láttuk a berendezés típusoknál, nincs újdonság vele kapcsolatban.
- erp_code: ugyanaz vonatkozik rá, mint a name-re.
- type_id: következik a kapcsolatért felelős mező, amely egy select lesz. A relationship() metódusban defináltuk hozzá, hogy melyik model kapcsolatot használja az értékek lekéréséhez (első paraméter) és milyen mező értékei jelenjenek meg a select-ben: berendezés típusok name mezője (második paraméter). A select úgy lett létrehozva, hogy keresőmező szerepel benne alapértelmezetten, ezt a searchable() metódus biztosítja nekünk. A preload() metódussal azt érjük el, hogy a select opciói már hamarabb betöltődjenek, ezáltal egy picit gyorsabb a működése. A createOptionForm() metódussal pedig egy "+" jel fog megjelenni a lenyíló lista jobb szélén, amivel itt a berendezés erőforrás űrlapján "on-the-fly" tudunk új berendezés típust definiálni neki, ha előtte elfelejtettük volna létrehozni neki a típust és így nem muszáj elhagynunk a berendezés létrehozási / szerkesztési űrlapot, mert helyben létre tudjuk ezt neki hozni.
- plant: nagyjából ugyanaz vonatkozik rá, mint az erp_code-ra, leszámítva az egyediséget.
- active: az aktivitás jelző egy kapcsoló lesz az űrlapon, az "on" színe a sikert jelző zöld lesz, az "off" színe a veszélyt jelző piros lesz. Magát a kapcsolót egy egész sorban (full) jelenítjük meg az űrlapon.
- history és note: ugyanaz vonatkozik rájuk, mint a plant-re.
Így néz ki jelenleg bal oldalon a navigációs menü, jobb oldalon pedig a létrehozási űrlap a berendezésekhez. Ha létrehozunk egy új berendezést (validációs hibákat elkerülve), akkor létrehozás utána nézet oldalra jutunk, nem úgy, mint a berendezés típusoknál a szerkesztésre. Ez egy alapbeállítás a Filament-ben, hogy ha van az erőforrásnak View nézete, akkor először azt fogja megjeleníteni a létrehozás után és nem az Edit nézetet. Ugyanez vonatkozik majd a lista megjelenítésben található táblázatra is, tehát ha rákattintunk egy sorra, akkor alapból a View nézet fog majd bejönni és nem a szerkesztési nézet.
Az iménti dolog azért van, mert az erőforrás létrehozásánál a --view kapcsolóval jön létre hozzá a CRUD témaköréből ismert "show" nézet: ez alapértelmezetten
azokat a mezőket fogja tartalmazni, amit a létrehozási / szerkesztési
űrlap is tartalmaz, csak az űrlap mezői nem lesznek szerkeszthetőek. Ha azt szeretnénk, hogy ne csak "disabled" (nem szerkeszthető) űrlapelemeket szöveges formában láthassunk a "show" nézetben, akkor egy infolist() nevű metódust kell definiálnunk itt:
public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
Infolists\Components\TextEntry::make('name')->label(__('fields.name')),
Infolists\Components\TextEntry::make('erp_code')->label(__('fields.erp_code')),
Infolists\Components\TextEntry::make('type.name')->label(__('fields.type')),
Infolists\Components\TextEntry::make('plant')->label(__('fields.plant')),
Infolists\Components\TextEntry::make('active')->label(__('fields.active'))
->state(function (Model $record): string {
return $record->active ? __('fields.yes') : __('fields.no');
}),
Infolists\Components\TextEntry::make('history')->label(__('fields.history')),
Infolists\Components\TextEntry::make('notes')->label(__('fields.note')),
]);
}
A szükséges importálásokat (Infolist, TextEntry) hajtsuk végre ahhoz, hogy működjön.
Ezek közül talán a type.name, ami nem érthető egyből, az arra vonatkozik, hogy a Device model osztályban lévő type kapcsolaton keresztül a device_types táblából érkező name mező kerüljön megjelenítésre. Míg az aktivitást jelző mezőnél, ha azt szeretnénk, hogy ne csak 0-t vagy 1-et jelezzen nekünk, akkor az állapotra vonatkozóan egy visszajelzést tudunk adni, hogy igen vagy nem (a megfelelő használathoz a fields.php szótáraiba vegyük fel a yes és no értékeket angolul és magyarul is, illetve importáljuk ezt: Illuminate\Database\Eloquent\Model).
Így a következő nézetet kapjuk a devices/1 útvonalon:
Így a mezők értékét könnyebben tudjuk "kimásolni", mint egy szöveget.
Ha azt szeretnénk, hogy jobb felülről eltűnjön a szerkesztés gomb, akkor a DeviceResource / Pages / ViewDevice.php-ban kell kivennünk az EditAction-t tartalmazó sort.
Én most is azt javaslom, hogy a létrehozás / szerkesztés után léptessen át a rendszer a lista nézetre, úgyhogy a CreateDevice és az EditDevice osztályhoz is hozzáadom ezt a metódust:
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
Berendezések lista nézete
Ismét kihangsúlyozom, hogy ne feledjük el azokat, amiket a berendezés típusoknál már megismertünk és használtunk. Most pedig igyekszem új dolgokat mutatni, amelyek szintén hasznosak lehetnek a jövőben.
A DeviceResource.php fájl table() metódusában a columns() metódus visszatérési tömbjét töltsük fel az alábbi tartalommal, utána pedig elmagyarázom a nehezebben érthető részeket:
Tables\Columns\TextColumn::make('id')->searchable()->sortable(),
Tables\Columns\TextColumn::make('name')->label(__('fields.name'))
->searchable()->sortable(),
Tables\Columns\TextColumn::make('erp_code')->label(__('fields.erp_code'))
->searchable()->sortable(),
Tables\Columns\TextColumn::make('type.name')->label(__('fields.type'))
->searchable()->sortable(),
Tables\Columns\IconColumn::make('active')->label(__('fields.active'))
->boolean()
->action(function($record, $column) {
$name = $column->getName();
$record->update([
$name => !$record->$name
]);
}),
Tables\Columns\TextColumn::make('created_at')->label(__('fields.created_at'))
->dateTime('Y-m-d H:i')
->searchable()->sortable()
->toggleable(isToggledHiddenByDefault: true),
Az id, name, erp_code mezőknél nincs újdonság és talán a type.name paramétert is már tudjuk értelmezni, hogy a type kapcsolaton keresztüli másik tábla name mezője fog itt megjelenni. Az aktivitást jelző mező egy ikon típusú mező lesz és a boolean() metódushívás miatt az igaz esetben egy zöld pipa, a hamis esetben egy piros x fog megjelenni az ikonban. Az utána lévő action() metódusban pedig azt definiáljuk, hogy az ikonra kattintással meg tudjuk már itt a táblázatos nézetben változtatni, hogy az adott berendezés az aktív vagy inaktív legyen.
A created_at mezőnél mindössze egy újdonság van, amely a toggleable() metódus és annak paramétere: ez arra vonatkozik, hogy ha túl sok oszlopot szeretnénk itt megjeleníteni a táblázatos nézetben és nem minden fér majd ki rögtön a táblázatban, akkor lehetőségünk van ezeket az oszlopokat elrejteni, ha igazra állítjuk a paramétert. Ekkor a mező csak akkor kerül megjelenítésre, ha a táblázat jobb felső részén lévő három vonal ikonra kattintva előhívjuk az elrejtett oszlopot.
Itt rögtön látható, hogy hiányzik egy fordítás a szótárból, úgyhogy ezt a hiányzó elemet adjuk hozzá a lang / vendor / filament-tables / hu / table.php fájlhoz:
'column_toggle' => [
'heading' => 'Oszlopok',
],
Ha most elmentjük, akkor a "Columns" felirat helyett már az "Oszlopok" fog ott szerepelni és a "Létrehozva" oszlopnév előtti checkbox bepipálásával megjeleníthető a táblázatban ez az oszlop is.
Bár még csak egy darab aktív/inaktív berendezésünk van, valószínűleg az éles használat során ennél sokkal több lesz majd, úgyhogy hozzunk létre egy szűrő mezőt a táblázat fölé, amivel rögtön szűrhetünk az adott aktivitás értékre. Nyissuk meg szerkesztésre a ListDevices.php fájlt és adjuk hozzá ezeket az osztályhoz:
public function getTabs(): array
{
return [
'all' => Tab::make()->label(__('fields.all'))
->icon('heroicon-o-list-bullet')
->badge(Device::query()->count()),
'active' => Tab::make()->label(__('fields.active'))
->modifyQueryUsing(fn (Builder $query) => $query->where('active', true))
->icon('heroicon-o-check-circle')
->badge(Device::query()->where('active', true)->count()),
'inactive' => Tab::make()->label(__('fields.inactive'))
->modifyQueryUsing(fn (Builder $query) => $query->where('active', false))
->icon('heroicon-o-x-circle')
->badge(Device::query()->where('active', false)->count()),
];
}
public ?string $activeTab = 'active';
Három tab (lapfül) lesz, amiket már testre is szabtunk itt (a hiányzó szótár bejegyzéseket - all, inactive - vegyük fel a fields.php-ba). A teljes lekérdezéshez (all) nem kell módosítani a lekérdezést, mivel a teljes tartalmat lekérjük. Az aktívnál és az inaktívnál erre szűrünk a where metódussal. Különböző ikonokat állítunk be a három tab-hoz, illetve a badge() metódus segítségével egy számlálót teszünk oda a tab-okhoz, hogy hány berendezés van összessen, továbbá hány aktív és inaktív. A getTabs() metódus után vagy előtt pedig beállíthatjuk azt a szöveges elemet, hogy melyik legyen az aktív tab (nálunk a második lesz, tehát oldalbetöltődés után a második tab lesz kiválasztva).
Widget
Hozzuk létre a widget-et, ami a berendezésekről nyújt nekünk információkat:
php artisan make:filament-widget DeviceOverview --stats-overview
A getStats() metódus visszatérési tömbje pedig legyen ez:
Stat::make('Device', Device::query()->count())->label(__('module_names.devices.plural_label') . ': ' . __('fields.all')),
Stat::make('Device', Device::query()->where('active', true)->count())->label(__('module_names.devices.plural_label') . ': ' . __('fields.active')),
Stat::make('Device', Device::query()->where('active', false)->count())->label(__('module_names.devices.plural_label') . ': ' . __('fields.inactive')),
Az eredmény így fog kinézni a vezérlőpulton:
Erőforrás kapcsolat menedzser
Milyen hasznos is lenne, ha a kiválasztott (szerkesztett) berendezés típus alatt látható lenne a hozzá kapcsolódó berendezések listája is. Ehhez egy erőforrás kapcsolat menedzsert kell létrehoznunk.
php artisan make:filament-relation-manager DeviceTypeResource devices name
Az utasítás első paraméterében a "szülő" erőforrást kell megadni (DeviceTypeResource), második paraméterében azt a táblát, ami "gyerekként" kapcsolódni fog hozzá (devices), harmadik paraméterében pedig azt, hogy ennek a "gyereknek" melyik mezője kerüljön megjelenítésre a lista táblázatban és a létrehozási/szerkesztési űrlapban. Az utasítás kiadásának hatására létrejött a DeviceTypeResource mappában a RelationManagers mappa, amin belül a DevicesRelationManager.php
Ennek az új fájlnak a tartalma nagyon hasonlít az erőforrás fájlok szerkezetére (form() és table() metódus az ismert funkcionalitásaikkal). De mielőtt rátérnénk ennek a fájlnak a szerkesztésére, előtte (ahogy a terminal is figyelmeztetett rá) regisztráljuk be a kapcsolatot a DeviceTypeResource getRelations() metódusába, annak visszatérési tömbjébe helyezzük el ezt:
RelationManagers\DevicesRelationManager::class,
A kapcsolat már elő is állt a kiválasztott berendezés típus szerkesztési oldalán:
Az rögtön látszódik, hogy az alsó kapcsolati táblázatban csak a berendezés neve látható, mivel ezt adtuk meg az erőforrás kapcsolat menedzsert létrehozó utasításban. Az is látszik még, hogy a többnyelvűsítés itt még nem működik teljesen jól, de ezt is mindjárt orvosoljuk.
Visszatérhetünk a DevicesRelationManager.php-hoz: kezdjük azzal, hogy az új berendezés létrehozása űrlap megjelenésekor csak egy név mező jelenne meg, ami nyilván kevés és reklamálna is a rendszer, hogy ha csak a név mezőt töltenénk ki a létrehozáshoz a többi kötelező mező nélkül. Ami még felmerülhetne, hogy a berendezés létrehozási űrlap elemeit ide is bemásoljuk (illetve kiszervezhetnénk egy metódusba, amit utána mindkét helyen csak meghívnánk). De csináljuk inkább meg úgy, hogy itt a DevicesRelationManager-ben a form() metódus magját figyelmen kívül hagyjuk és a table() akcióira koncentrálunk: ha a felhasználó rákattint az adott erőforrás létrehozására, szerkesztésére, törlésére, akkor a már létező űrlapok jelenjenek meg neki. Így ha például itt a berendezés létrehozási vagy szerkesztési űrlapon a "Mégsem" gombra nyomunk, akkor újra visszairányít minket az aktuálisan szerkesztett berendezés típushoz. Itt a berendezés törlést véleményem szerint ne engedélyezzük, úgyhogy az erre vonatkozó akciókat vegyük ki a táblázat akciói közül. A táblázat oszlopaiban pedig csak annyi mezőt helyezzünk el, amennyi szükséges az azonosításhoz (persze a mezők köre szabadon bővíthető).
return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')->label(__('fields.name'))
->searchable()->sortable(),
Tables\Columns\TextColumn::make('erp_code')->label(__('fields.erp_code'))
->searchable()->sortable(),
])
->filters([
])
->headerActions([
Tables\Actions\CreateAction::make()->url(fn (): string => DeviceResource::getUrl('create')),
])
->actions([
Tables\Actions\ViewAction::make()->url(fn (Model $record): string => DeviceResource::getUrl('view', ['record' => $record])),
Tables\Actions\EditAction::make()->url(fn (Model $record): string => DeviceResource::getUrl('edit', ['record' => $record])),
])
->bulkActions([])
->emptyStateActions([
Tables\Actions\CreateAction::make()->url(fn (): string => DeviceResource::getUrl('create')),
]);
A headerActions() és az emptyStateActions() metódusokban az új berendezés létrehozásához definiálom az akciót. Az actions() részben pedig megtekintésre és szerkesztésre van lehetőség a definiált URL-eken keresztül. Ehhez persze szükség van a fájl elején a DeviceResource és a Model importálására:
use Illuminate\Database\Eloquent\Model;
use App\Filament\Resources\DeviceResource;
Többnyelvűsítés kapcsán itt magának a kapcsolatnak a kezelése adja alapértelmezetten a bal felső sarokban lévő táblázat címét, amit így tudunk átírni (a getTitle() metódus felüldefiniálásával):
public static function getTitle(Model $ownerRecord, string $pageClass): string
{
return __('module_names.devices.plural_label');
}
Míg az új elem felvétele gomb ugyanúgy működik, mint korábban az erőforrásnál, definiáljuk felül a getModelLabel() metódust:
public static function getModelLabel(): string
{
return __('module_names.devices.label');
}
Így most már megfelelő lesz a kinézete és az akciógombjainak az útvonalai is a szerkesztett berendezés típus alatti táblázatban:
Összegzés
A bejegyzésben létrehoztunk egy új Filament erőforrást, ami a berendezések kezelését teszi könnyebbé, hatékonyabbá, kényelmesebbé. Bővítettük tudásunkat a Filament-tel kapcsolatban úgy, hogy a "régi" dolgokat gyakoroltuk, az újakat pedig kipróbáltuk, hogy utána majd ezeket is csak gyakorlásként tudjuk alkalmazni a további erőforrásoknál. Új űrlap típusokat ismertünk meg, táblázat akciókat, szűrési lehetőségeket. Végül pedig egy erőforrás kapcsolat (berendezés típusok - berendezések 1-n) kezelését is hatékonyan végrehajtottuk. A többnyelvűsítésre mindvégig figyeltünk és ahol csak lehetett, alkalmaztuk azt.
A bejegyzésben végrehajtott változtatások GitHub commit-je itt található meg.
A közvetlen folytatásban: most már lesz értelme a QR kódoknak, hiszen azok alapján fogjuk tudni majd elirányítani a karbantartókat az adott berendezéshez (és a berendezés alatt kilistázásra kerülő dokumentumokhoz, de ez majd csak utána következik). Így tehát a QR kódok beépítésével fogjuk folytatni a karbantartás menedzser rendszer építését.
Dolgozzunk együtt: egyéni oktatás, mentorálás, fejlesztés, tanácsadás
Ha
egyéni oktatás, mentorálás, vagy fejlesztési projekt kapcsán szeretnél
segítséget kérni tőlem, esetleg együttműködni velem, akkor keress meg a weboldalamon található elérhetőségeken keresztül!