Karbantartás menedzsment rendszer - 5. rész: Berendezés dokumentumainak kezelése

Attila | 2023. 09. 25. 18:59 | Olvasási idő: 5 perc

Címkék: #Component #Digitalizáció (Digitalization) #Érvényesítés (Validation) #Filament #Laravel #Nézet (View) #Többnyelvűsítés (Localization) #Űrlap (Form)

Folytassuk a Karbantartás Menedzsment Rendszer építését a dokumentum kezeléssel. Ebben a bejegyzésben számos dolgot újra megvalósítunk, csak most már a dokumentumokra vonatkozóan. Újdonságként a dokumentumok, mint fájlok kezelésével foglalkozunk az űrlapon, illetve a kapcsolódó részeivel, mint például a fájlok letöltésével a lista (táblázat) és a megtekintés (view) nézeten keresztül is. De haladjunk csak szép sorban...
filament-file-upload

Egy kis emlékeztető...

Dokumentumokra azért lesz szükség, mert ezek alapján lehet karbantartani, javítani a berendezéseket. Ezzel rögtön meg is van, hogy a berendezésekkel kell majd összekapcsolnunk őket (egy berendezéshez több dokumentum is tartozhat).


Dokumentumok: Model, migrációs fájl

Hozzuk létre a model és a migrációs fájlt hozzá!

php artisan make:model Document -m

A migrációs fájl up() metódusának tartalma:

Schema::create('documents', function (Blueprint $table) {
  $table->id();
  $table->string('name')->unique();
  $table->foreignId('device_id')->constrained()->onUpdate('cascade')->onDelete('cascade');
  $table->text('attachment');
  $table->timestamps();
});

Migráljuk a táblát (php artisan migrate)! Majd ezek alapján a Document model fájl tartalma:

protected $fillable = ['name', 'device_id', 'attachment'];

public function device(): BelongsTo
{
  return $this->belongsTo(Device::class);
}

A kapcsolat másik oldalán, a Device model fájlban is definiáljuk a kapcsolatot:

public function documents(): HasMany
{
  return $this->hasMany(Document::class);
}

A helyes működéshez az importálást (HasMany) hajtsuk végre az osztály előtt!


Dokumentum

Hozzuk létre az erőforrást hozzá megtekintés nézettel együtt!

php artisan make:filament-resource Document --view

A fő erőforrás

Alapinformációk:

protected static ?string $navigationIcon = 'heroicon-o-document-text';

public static function getNavigationGroup(): string
{
  return __('module_names.navigation_groups.administration');
}

public static function getModelLabel(): string
{
  return __('module_names.documents.label');
}

public static function getPluralModelLabel(): string
{
  return __('module_names.documents.plural_label');
}

Ehhez nyilván kellenek a module_names szótárban az új bejegyzések például a hu mappában lévő module_names.php-ban:

'documents' => [
  'label' => 'Dokumentum',
  'plural_label' => 'Dokumentumok',
],

És ugyanígy az en mappában lévő változatnál is, csak természetesen angolul.

Létrehozási és szerkesztési űrlap

Először tekintsük át, hogy mit érdemes eltárolni a dokumentumokról:

  1. Legyen neki neve, hogy utána könnyen azonosítható legyen maga a fájl, de ennek nem kell feltétlenül ugyanannak lennie, mint ami a feltöltendő fájlnak a neve.
  2. Kapcsoljuk hozzá a berendezésekhez egy lenyíló listán keresztül.
  3. Végül, de nem utolsó sorában, a fájlfeltöltési bemeneti mező, amelyet eddig még nem használtunk, úgyhogy részleteivel most fogunk megismerkedni.

Magának az erőforrásnak a form() metódusa, illetve a visszatérési $form eleme így nézhet ki:

return $form->schema([
  Forms\Components\Section::make()->schema([
    Forms\Components\TextInput::make('name')->label(__('fields.name'))
      ->required()
      ->unique(ignoreRecord: true)
      ->maxLength(255),
    Forms\Components\Select::make('device_id')->label(__('module_names.devices.label'))
      ->relationship('device', 'name')
      ->searchable()
      ->preload()
      ->required(),
    Forms\Components\FileUpload::make('attachment')->label(__('fields.attachment'))
      ->required()
      ->preserveFilenames()
      ->openable()
      ->downloadable()
      ->maxSize(20000),
  ])
]);

Nézzük meg őket részletesen:

  1. A neve legyen többnyelvűsített címkével ellátva. Legyen kötelezően kitöltendő, egyedi és maximum 255 karakter hosszú. Ez eddig így ismert és érthető már.
  2. A berendezés azonosítónál is legyen többnyelvűsített a címke a module_names szótárból. A kapcsolat a model-ben definiált kapcsolaton keresztül lesz elérhető és a berendezés neve fog megjelenni a lenyíló listában. Előre betöltjük (preload()), így egy kicsit gyorsabban tudnak megjelenni az elemek. Végül legyen kötelezően kitöltendő mező ez is, hiszen az adattáblában ez egy elvárás.
  3. Végül következik a fájlfeltöltési mező, aminek a többnyelvűsített címkéje még nem létezik, úgyhogy ezt adjuk hozzá a fields.php szótárakhoz (a magyarban: 'attachment' => 'Csatolt fájl'). Legyen kötelezően kitöltendő mező. A további paraméterekhez nagyon hasznos, ha áttekintjük a Filament dokumentáció vonatkozó részét: https://filamentphp.com/docs/3.x/forms/fields/file-upload Ebben is megtalálhatjuk, hogy a preserveFilenames() metódussal megtartjuk a feltöltött fájl fájlnevét és kiterjesztését. A fájl maga megnyitható lesz: openable(), illetve letölthető is: downloadable(). A feltöltendő fájl maximális méretét 20000 KiloByte-ra állítottam be. Számos további paramétert be lehetne állítani, de azokra itt most talán nincsen szükség, például: nem szeretnénk korlátozni a fájl típusát, hiszen fel lehet tölteni akár pdf, docx, doc stb. leíró fájlokat is. Nem szeretnénk itt több fájlt feltölteni, mert minden egyes dokumentumot külön szeretnénk kezelni.

Az eredmény pedig itt látható:

Az űrlap teljesen jól néz így ki, a fájlfeltöltéshez használt mezőt a Filament alakítja a számunkra ilyen barátságosra.

A mezők kitöltése után a (tetszőlegesen kiválasztott) fájlt drag&drop is rá lehet helyezni a fájl mezőre és a következőt kell látnunk:

A "Feltöltés" befejezése után pedig megváltozik a színe és kiírja, hogy "Sikeres feltöltés":

Megnyomhatjuk a "Létrehozás" gombot és el is menti így a dokumentumot.

Utána bár még nem látszódik jól a táblázat oszlopai és sorai, de az igen, hogy egy sor van benne és ha annak a Szerkesztés gombjára kattintunk akkor újra megnyílik az imént létrehozott dokumentum bejegyzés. Ekkor viszont már megjelennek további gombok a fájl neve mellett balra, amelyekkel sorrendben: 1. törölni, 2. új lapon megnyitni, és 3. letölteni tudjuk ezt a fájlt (kiemeltem ezeket a gombokat alább).

Ha 404-es Not Found HTTP státuszkódot kapunk a megnyitáskor és a letöltéskor is, akkor szükség van arra, hogy összekössük a feltöltött fájlok helyét (storage / app / public mappa) a nyilvánosan elérhető public mappával:

php artisan storage:link

Lista nézet

Kezdjük a táblázat oszlopaival ($table->columns([])).

Tables\Columns\TextColumn::make('id')->searchable()->sortable(),
Tables\Columns\TextColumn::make('name')->label(__('fields.name'))
  ->searchable()->sortable(),
Tables\Columns\TextColumn::make('device.name')->label(__('module_names.devices.label'))
  ->searchable()->sortable(),
Tables\Columns\TextColumn::make('created_at')->label(__('fields.created_at'))
  ->dateTime('Y-m-d H:i')
  ->searchable()->sortable(),

Újdonság ezekben nincsen, mert már korábban is így csináltuk.

A "Megtekintés" gomb lesz, ami ahhoz a "view" nézethez vezet, ami majd publikus lesz a felhasználók számára és le tudják tölteni a dokumentumot, illetve látható lesz rajta a QR kód is, ami ide vezeti majd a karbantartó kollégákat, ha kinn az üzemben problémájuk adódna a géppel és erre a dokumentumra lenne szükségük.

További akciók és nézeteik

A létrejövő CreateDocument.php és EditDocument.php fájlokhoz adjuk hozzá a szokásost: mentés után visszairányítjuk a felhasználót a lista nézethez:

protected function getRedirectUrl(): string
{
  return $this->getResource()::getUrl('index');
}

Innentől egy kicsit kényelmi funkciókra megyünk rá: új akciók lesznek a táblázatos nézetben

  1. Közvetlen dokumentum letöltési lehetőség.
  2. Közvetlen QR kód nyomtatási lehetőség.

A letöltési linket a view és edit action-ök után adjuk hozzá a táblázat action-jeit tartalmazó tömbhöz:

Tables\Actions\Action::make('download')
  ->label(__('actions.download'))
  ->action(function ($record) {
    return Storage::download('public/' . $record->attachment);
  })
  ->icon('heroicon-o-document-arrow-down')
  ->color('primary'),

A többnyelvű működéshez hozzunk létre egy új szótárat a funkcióknak: actions.php néven az en és hu mappákban is: helyezzük el bennük a 'download' kulcsot 'Download' és 'Letöltés' értékekkel. A letöltés akció felépítéséhez és végrehajtásához itt a kódban a Storage::download() metódust használtuk, mivel a fájlok alapértelmezetten ide kerülnek feltöltésre a felhasználó által, de nyilván ha valaki máshova szeretné elhelyezni a fájlokat, esetleg egy külön tárhelyre vagy a felhőbe, akkor ezt a részt még finomhangolni kellene. Ezen felül letöltési ikont és színt is állítottunk be a letöltési linknek.

QR kód elérését a táblázatban funkcióhoz először a DocumentResource.php-ban a getPages() metódusban hozzuk létre a "create" után ezt a sort:

'qr' => Pages\QRDocument::route('/qr/{record}'),

A table() metódusban pedig ugyanúgy, ahogy a berendezéseknél csináltuk korábban az actions() részben a ViewAction és az EditAction után hozzuk létre az új QR kódos akciót, amit akár a berendezésektől is idemásolhatunk, de a "device" nézet nevet cseréljük ki "document"-re, így:

Tables\Actions\Action::make('QR')->label(__('fields.qr_code'))
  ->modalContent(fn ($record): View => view('filament.resources.document-resource.pages.q-r-document', ['record' => $record]))
  ->modalSubmitAction(false)
  ->modalCancelAction(false)
  ->icon('heroicon-o-printer')
  ->color('secondary')
  ->tooltip(__('actions.print') . ': ' . __('fields.qr_code'))

A tooltip() rész egy kicsit bővebb leírást ("Nyomtatás: QR kód") ad arról, hogy mit is csinál a QR kód link, de a táblázat sorában mégsem foglalunk így neki olyan sok helyet, mint ha a hosszabb verziót teljesen kiírnánk.

Így a QR kód akció is bekerül a táblázat soraihoz. Az eredmény itt látható:

A QR kódhoz tartozó új nézetet a resources / views / filament / resources / document-resource / pages mappába hozzuk létre q-r-document.blade.php névvel. A tartalma pedig legyen a másik QR kódos (device) nézethez nagyon hasonló:

Megjegyzés 1.: az actions.php szótárakhoz a 'print' kulcsot is adjuk hozzá (en: 'Print', hu: 'Nyomtatás') értékekkel és akkor a két akciót már szintén többnyelvűsítve tudjuk használni a jövőben.

Megjegyzés 2.: a Filament QR kódot nyomtató két nézet fájlja rettentő módon hasonlít egymásra, mindössze néhány adattagban (linkben) különböznek egymástól, így érdemes lehet készíteni hozzájuk egy nézet komponenst, amelyet utána felparaméterezve tudunk használni.

Utóbbi megoldáshoz hozzunk létre egy komponenst a resources / views mappában egy új components mappában: print-layout.blade.php névvel.

Ez a kód három helyen tartalmazott dinamikusan változó paramétereket: a <h1> tag-en belül rögtön kettőt is, az egyik az oldal "típusa" lesz, hogy most a berendezéshez vagy a dokumentumhoz irányítjuk majd oda a felhasználót, aki bescanneli a QR kódot. Végül a QrCode generáló metóduson belül, hogy maga az útvonal ($route), amit a QR kód rejt, az mi legyen. Ezekre itt a kódban már létre is hoztam a három változót: $type, $name, $route. A komponens használata során ezeket a változókat fogjuk átadni az attribútumokon keresztül.

Utána pedig a két nagyon hasonló nézetünket módosítsuk: előbb a q-r-document.blade.php-t majd ez alapján önállóan próbáljuk megoldani a q-r-device.blade.php-t is. Töröljük (vagy kommenteljük ki) a fájl eddigi tartalmát és helyette szúrjuk be ezt:

Ez mindössze annyi, hogy használjuk a print-layout.blade.php nézet komponens fájlt, az attribútumokban pedig átadjuk neki a változók értékeit. Kipróbálás után tapasztalhatjuk, hogy tökéletesen működik!


Kapcsolat: berendezés - dokumentum (1 - n)

Hozzuk létre a kapcsolatot a berendezéshez, hiszen egy berendezés több dokumentumot is "tartalmazhat", ezért a berendezések megtekintési oldalán hasznos lenne látni a hozzá tartozó dokumentumokat.

php artisan make:filament-relation-manager DeviceResource documents name

Az utasítás hatására létrejött a DeviceResource mappában a RelationManagers mappa és benne a DocumentsRelationManager.php fájl. Mielőtt ezt a fájlt elkezdenénk szerkeszteni, előtte a DeviceResource.php erőforrás fájl getRelations() metódusának visszatérési tömbjéhez adjuk hozzá:

RelationManagers\DocumentsRelationManager::class,

Magát a DocumentsRelationManager osztályt a korábban ismertetett megoldás szerint bővítjük ki.

A kapcsolatot jelző szekció fejlécének címét, illetve majd magát az innen létrehozható új dokumentum helyes feliratát mutató gomb címkéjét mutató funkciókkal kezdjük az osztály szerkesztését:

public static function getTitle(Model $ownerRecord, string $pageClass): string
{
  return __('module_names.documents.plural_label');
}
public static function getModelLabel(): string
{
  return __('module_names.documents.label');
}

Utána nem a form() metódusának visszatérési tömbjét szerkesztjük, illetve azt üresen hagyjuk (kitöröljük a benne lévő mezőt), hanem a táblázat akcióin (table() metóduson) keresztül irányítjuk a funkciókhoz a felhasználót.

return $table
  ->recordTitleAttribute('name')
  ->columns([
    Tables\Columns\TextColumn::make('name')->label(__('fields.name'))
      ->searchable()->sortable(),
  ])
  ->filters([
    //
  ])
  ->headerActions([
    Tables\Actions\CreateAction::make()->url(fn (): string => DocumentResource::getUrl('create')),
  ])
  ->actions([
    Tables\Actions\ViewAction::make()->url(fn (Model $record): string => DocumentResource::getUrl('view', ['record' => $record])),
    Tables\Actions\EditAction::make()->url(fn (Model $record): string => DocumentResource::getUrl('edit', ['record' => $record])),
    Tables\Actions\Action::make('download')
      ->label(__('actions.download'))
      ->action(function ($record) {
        return Storage::download('public/' . $record->attachment);
      })
      ->icon('heroicon-o-document-arrow-down')
      ->color('primary'),
    Tables\Actions\Action::make('QR')->label(__('fields.qr_code'))
      ->modalContent(fn ($record): View => view('filament.resources.document-resource.pages.q-r-document', ['record' => $record]))
      ->modalSubmitAction(false)
      ->modalCancelAction(false)
      ->icon('heroicon-o-printer')
      ->color('secondary')
      ->tooltip(__('actions.print') . ': ' . __('fields.qr_code'))
  ])
  ->bulkActions([])
  ->emptyStateActions([
    Tables\Actions\CreateAction::make()->url(fn (): string => DocumentResource::getUrl('create')),
  ]);

Egyetlen oszlopot (mezőt) jelenítünk meg a táblázatban, a dokumentum nevét, itt ennél több nem is kell. A headerActions() részben új dokumentumot létrehozó gombot helyezünk el. Az akciók között pedig a megtekintési (view) és szerkesztési (edit) akciókon keresztül irányítjuk el a felhasználót a megfelelő nézetekhez. Az utána jövő akciók: letöltés és QR kód nyomtatás funkciók a korábbi fő DocumentResource fájlból már ismerősek lehetnek. BulkActions() részt hagyjuk üresen, ne adjunk itt lehetőséget a "tömeges" törlésre. Illetve végül, ha valamelyik berendezésnek nem lenne még egyetlen dokumentuma sem, akkor itt az emptyStateActions() részben látható részben a dokumentum létrehozó nézethez irányítjuk a felhasználót.

Egy adott berendezés szerkesztése során így fog kinézni a berendezés adatait szerkesztő űrlap alatti dokumentumokat tartalmazó táblázat:

Ha ugyanezt nem a berendezés szerkesztési oldalán (például: devices/1/edit), hanem csak a megjelenítési oldalán (például: devices/1) keresztül nézzük meg, akkor az iménti táblázatból hiányozni fog az "Új Dokumentum" gomb jobb felülről, illetve a "Szerkesztés" gomb a táblázat sorából.

Menü

Logikailag a menü struktúrájában érdemes felcserélni a berendezéseket és a berendezés típusokat. Ezt a fő erőforrásoknál egy új attribútum felülírással tudjuk módosítani. Adjuk hozzá mindegyik erőforrás fő fájljához ezt (talán a navigationIcon után érdemes beszúrni):

protected static ?int $navigationSort = 3;

A DeviceTypeResource.php-ban legyen az 1, a DeviceResource.php-ban legyen a 2, míg a most készülő DocumentResource.php-ban legyen a 3. Így már a logikának megfelelő lesz a menüpontok sorrendje.


Összegzés, továbblépés

A bejegyzés elején úgy gondoltam, hogy ez egy rövidebb írás lesz, de mégsem így lett, mivel elég sokmindent elvégeztünk közben és 22 fájl is újonnan jött létre vagy módosult a munka során. Ezek a változások ebben a GitHub commit-ben érhetők el.

A közvetlen folytatásban következik a jogosultsági rendszer kiépítése a jogosultságokkal.


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!