Skip to content

Latest commit

 

History

History
executable file
·
727 lines (577 loc) · 21.6 KB

RESOURCES.md

File metadata and controls

executable file
·
727 lines (577 loc) · 21.6 KB

Vstack Resources

Índice




Chamamos de resource o arquivo de configuração de crud.
Nela podemos definir TUDO do crud em questão, desde ícones e títulos, até métodos de gravação e comportamento pré-listagem ou exportação de relatório.

Criando um Resource

Para criar um novo resource, você precisa executar o comando especificando o do resource, model e tabela, respectivamente.

php artisan vstack:make-resource {resource} {model} {table}

Lembrando que este comando também criará o model, então caso o model já exista, faça um backup pois o antigo será subscrito.

Leia mais sobre Vstack Models

Acessando /admin/nome-do-resource, você já verá uma crud funcional, porém pouco detalhado.


### Customizando seu Resource Inicialmente, com o comando citado o resource ( funcional ) criado fica como no exemplo abaixo.

<?php

//COMANDO EXECUTADO : php artisan vstack:make-resource Carros Car cars

namespace App\Http\Resources;
use marcusvbda\vstack\Resource;

class Carros extends Resource
{
    public $model = \App\Http\Models\Car::class;
}

Nas imagens abaixo podemos ver como o crud e tela de listagem ficam inicialmente, veremos agora que com pouca customização deixamos de ser um crud simples pra algo mais avançado.

Resource Inicial

Crud Inicial

Adicionando seguintes métodos já daremos uma nova cara a listagem do resource

public function label()
{
    return "Veículos";
}

public function singularLabel()
{
    return "Veículo";
}

// https://element.eleme.io/#/en-US/component/icon#icon
public function icon()
{
    return "el-icon-truck";
}

public function nothingStoredText()
{
    return "<h4>Nenhum {$this->singularLabel()} cadastrado ainda ...</h4>";
}

public function nothingStoredSubText()
{
    return "<span>Clique em cadastrar e efetue o primeiro cadastro !!!</span>";
}

public function storeButtonlabel()
{
    return "<span class='el-icon-plus mr-2'></span>Cadastrar {$this->singularLabel()}";
}

Crud Inicial

Podemos também configurar as permissões deste resource utilizando os métodos "CAN", são eles

  • canViewList (define se o usuário pode visualizar a listagem);
  • canView (define se o usuário pode acessar as telas de visualização de item);
  • canCreate (define se o usuário pode cadastrar itens);
  • canUpdate (define se o usuário pode editar items);
  • canDelete (define se o usuário pode excluir itens);
  • canDelete (define se o usuário pode excluir itens);
  • canClone (define se o usuário pode clonar itens);
  • canImport (define se o usuário acessar o recurso de importação de planilha);
  • canViewReport (define se o usuário acessar a listagem em modo de relátorio);
  • canExport (define se o usuário exportar o relatório em forma de planilha excel);

Também é possivel configurar para row específico utilizando

  • canUpdateRow($item) (define se o usuário pode editar o $item especifico);
  • canDeleteRow($item) (define se o usuário pode excluir o $item especifico);
  • canCloneRow($item) (define se o usuário pode clonar o $item especifico);
  • canViewRow($item) (define se o usuário pode acessar as telas de visualização do item especifico);

Abaixo exemplo de como podemos definir as permissões de ação e ação por item para o usuário logado.

Lembrado que a rotina que definirá a resposta dessa verificação está sujeita a regra de negócio da aplicação;

public function canUpdate()
{
    return Auth::user()->hasPermissionTo('create');
}
public function canUpdateRow($item)
{
    return Auth::user()->hasPermissionTo('create') && !$item->permanent;
}


Definindo os inputs da tela de cadastro/edição

Adicionando poucos campos ao método fields, podemos customizar nossa tela de cadastro/edição.

Exemplo abaixo :

public function fields()
{
    $cards = [
        new Card("Informações", [
            new Text([
                "label" => "Nome",
                "field" => "name",
                "description" => "Nome do veículo",
                "rules" => ["max:255", "required"],
            ]),
            new TextArea([
                "label" => "Observação",
                "field" => "obs",
                "show_value_length" => true,
                "rules" => ["max:255", "required"],
            ])
        ])
    ];
    return $cards;
}

Note que foi adicionado os campos na estrutura card -> fields e podemos adicionar multiplos cards

- CARD
--- FIELD
--- FIELD
- CARD
--- FIELD
--- FIELD
--- FIELD
--- FIELD
--- FIELD
- CARD
--- FIELD
--- FIELD
--- FIELD
...

Para saber mais sobre todos os inputs disponíveis e suas configurações Leia mais sobre Vstack Inputs

Outro de detalhe que podemos customizar é o método de gravação do sistema. Podemos substitui-lo completamente ou apenas adicionar algum processo ou validação extra antes ou depois do store, como no exemplo abaixo;

public function storeMethod($id, $data)
{
    $result = parent::storeMethod($id, $data);
    // exemplo de rotina extra
    Notifications::sendEmailNotification("Novo veículo cadastrado", "Novo carrado cadastrado");
    // exemplo de rotina extra
    return $result;
}

O mesmo pode-se fazer com o método destroyMethod, para exclusão no caso.




Definindo as colunas da tabela de listagem

Em resources que não possui nenhum item cadastrado recebemos apenas uma mensagem dizendo que nada ainda está cadastrado e devemos clicar para adicionar o primeiro registro.

Em caso de resource que possui itens, uma listagem aparece, que por padrão é mostrado apenas o id e o campo de ações, como na imagem abaixo :

Crud Inicial

Podemos configurar quais colunas deve ser mostrada e configura-las utilizando o método table, como no exemplo abaixo.

public function table()
{
    return [
        "code" => ["label" => "Código", "sortable_index" => "id"],
        "name" => ["label" => "Nome"],
        "created_at" => ["label" => "Data de Cadastro", "sortable" => false, "handler" => function ($row) {
            return $row->created_at->format("d/m/Y H:i:s");
        }]
    ];
}

Note que foram adicionados 3 colunas, sendo elas :

  • code, trará do model o valor de code e será nomeada com Código e ao clicado para ordernar será ordernado pela coluna id da tabela;
  • name, trará do model o valor de name e será nomeada com Nome e mais nada foi definido, então neste caso, o sortable default é true e o sortable_index default será considerado o índice da coluna, name no caso;
  • created_at, que traria do model o valor de created_at, porém como definimos um handler, o resultado deste método que será o valor mostrado ( no caso formatamos a data ), será nomeada com Data de Cadastro e sortable foi definido como false, isso significa que não será possivel ordernar a tabela por está coluna;

Podemos também ao invés de utilizar o handler, criar um append pro model e chama-lo no index

por exemplo no caso da coluna created_at:

"formated_created_at" => ["label" => "Data de Cadastro", "sortable" => false]

considerando que temos um append chamado formated_created_at no model Car

Após a customização exemplificada a listagem ficou assim : Crud Inicial

Note que o títula das colunas que podem ser ordenadas (Código e Nome) são links diferente das demais que são apenas textos não clicáveis.



Definindo lentes de listagens

Lentes são filtros pré-definidos que incrementarão ainda mais sua listagem de resource, basta adicionar o método lenses ao resource como no exemplo abaixo :

public function lenses()
{
    return [
        "Apenas Ativos" => ["field" => "active", "value" => true],
        // "Apenas Inativos" => ["field" => "active", "value" => false],
        "Apenas Inativos" => ["field" => "active", "value" => false, "handler" => function ($q) {
            return $q->where("active", false);
        }],
    ];
}

Note que definimos o índice da lente como Apenas Ativos e Apenas Inativos e configuramos os seguintes parâmetros :

  • field, nome do campo na url;
  • value, valor do campo na url;
  • handler, oque fazer caso esta lente seja selecionada ( caso não definido o sistema automáticamente considerará na query a condição 'where $field = $value');

Após configurado as lentes a listagem ficará da seguinte forma : Lentes

Note que além das lentes adicionadas, o sistema adicionou um indíce nomeado Todos que trará o valor de consultada padrão.

caso clicado na lente que configuramos Apenas Ativos a página recarregará com o query parameter active=true e adicionará a query a condição "where active = true"

caso clicado na lente que configuramos Apenas Inativos a página recarregará com o query parameter active=false e executará o handler ao query builder da listagem antes de completar o carregamento da página.




Adicionando conteúdo em slots

Podemos também adicionar algum conteúdo antes ou depois da tabela de listagem apenas utilizando os métodos beforeListSlot e afterListSlot, como no exemplo :

public function beforeListSlot()
{
    return '<el-alert
                title="success alert"
                type="success"
                center
                show-icon>
            </el-alert>';
}
public function afterListSlot()
{
    return '<el-alert
                title="warning alert"
                type="warning"
                center
                show-icon>
            </el-alert>';
}

No caso do exemplo, foi adicionado um html, porém podemos adicionar um texto comúm ou até renderizar uma blade da seguinte forma :

return view('slots.example')->render();

Após a adição de conteudo nesses slots a tela ficará da seguinte forma : Slot

Pode-se também adicionar slots como este na tela de visualização, cadastro, edição e relatório, da mesma forma apenas utilizando os métodos beforeReportListSlot, afterReportListSlot, beforeListSlot, afterListSlot, beforeEditSlot, afterEditSlot, beforeCreateSlot, afterCreateSlot, beforeViewSlot, beforeViewSlot e afterViewSlot




### Adicionando filtros ao seu resource

Podemos adicionar 2 tipos diferentes de filtros no seus resources, busca simples e filtros específicos.

Para configurar a busca simples, basta definir no método search as colunas que deseja realizar a busca, como no exemplo :

public function search()
{
    return ["name", "description"];
}

Search

Note que assim que adicionado os indíces de busca, o campo Pesquisar aparecerá na listagem e o sistema adicionará a condição "where {$index} like "%{$value}%" para cada um dos índices adicionados.

Podemos também passar um handler function ao invés de um índice, como no exemplo :

public function search()
{
    return ['name', function ($query, $val) {
        return $query->where("name", "like", "%{$val}%");
    }];
}

Neste caso o sistema fará o filtro like para a coluna name e executará o handler com $val sendo o valor digitado no input.



Para configurar os filtros específicos, basta definir no método fielters os filtros que deseja adicionar.

Temos 2 formas de fazer isso.

Com filtros mais básicos como, filtros que não exijam joins ou processamentos mais pesados, podemos utilizar os filtros pré-definidos, da seguinte forma :

public function filters()
{
    return [
        new FilterByName(),
        //new FilterByText([
        //    "column" => "name",
        //    "label" => "Nome"
        //]),
        new FilterByText([
            "column" => "description",
            "label" => "Descrição"
        ]),
        new FilterByPresetDate([
            "column" => "created_at",
            "label" => "Cadastrado em"
        ]),
        new FilterByOption([
            "label" => "Nível",
            "column" => "level",
            "multiple" => true,
            "options" => [
                ["value" => 1, "label" => '⭐'],
                ["value" => 2, "label" => '⭐⭐'],
                ["value" => 3, "label" => '⭐⭐⭐'],
                ["value" => 4, "label" => '⭐⭐⭐⭐'],
                ["value" => 5, "label" => '⭐⭐⭐⭐⭐'],
            ]
        ]),
    ];
}

Search

Para filtros mais complexos você pode executar o comando php artisan vstack:make-filter {resource} {name} {type}

Tipos de filtros disponíveis

text-filter, select-filter, check-filter, date-filter, rangedate-filter, custom-filter

que será criado uma classe em /Http/Filters/NOME_DO_RESOURCE/NOME_DO_FILTER.php nos padrões abaixo :

<?php 
// COMANDO EXECUTADO : php artisan vstack:make-filter Carros FilterByName text-filter
namespace App\Http\Filters\Carros;
use  marcusvbda\vstack\Filter;

class FilterByName extends Filter
{
    
    public $component   = "text-filter";
    public $label       = "FilterByName";
    public $placeholder = "";
    public $index = "filter_by_name";
    
    public function apply($query, $value)
    {
        //filter logic here...
        return $query;
    }
}

Note que esta classe possui variáveis de configuração e um método chamado apply, é neste método que será executado o filtro quando chamado.

Para adiciona-lo aos filtros do resource, basta adiciona-lo em filters da seguinte forma :

...
//new FilterByText([
//    "column" => "name",
//    "label" => "Nome"
//]),
new FilterByName(),
...

Para entender melhor sobre os tipos de filtros disponíveis e exemplos mais avançados Leia mais sobre Filtros




Relatórios de resource

Para habilitar o relatório para o resource é necessário que o método canViewReport return true.

Você pode utilizar um rotina para capturar permissões deste usuário ou apenas retornar true diretamente como no exemplo :

public function canViewReport()
{
    // Auth::user()->hasPermission("view-cars-report");
    return true;
}

Um link para acessar o relatório aparecerá na listagem Report

Clicando neste link, você terá acesso a rota /admin/relatórios/NOME_DO_RESOURCE e visualizará uma listagem parecido com apenas id e created_at como colunas padrão. Report


Para customizar estas colunas você deve editar a resposta do método exportColumns de maneira semelhante a que fizemos em table, abaixo um exemplo :
public function exportColumns()
{
    return [
        ["label" => "Código", "handler" => function ($row) {
            return @$row->code;
        }],
        ["label" => "Nome", "handler" => function ($row) {
            return @$row->name;
        }],
        ["label" => "Data de Cadastro", "handler" => function ($row) {
            return @$row->created_at->format("d/m/Y H:i:s");
        }],
    ];
}

note que diferente de em tables, passamos apenas um array com label e o handler e essas colunas aparecerão no relatório na ordem que configuramos.

Outra que podemos configurar e definir canExport retornando true, desta forma além de apenas listagem, temos uma exportação de planilha podendo selecionar qual das colunas queremos incluir na exportação.

P.s: Os filtros da listagem comum são reaproveitados aqui no relatório. Report

Importação de planilhas

Para habilitar no resource a importação de planilhas, basta definir que o método canImport retorne true, desta forma na tela de listagem do resource aparecerá o link para direcionar para a tela de importação. Report

Exemplo completo de resource

Abaixo como ficou o resource completo e configurado que utilizamos de exemplo nesta documentação

<?php

namespace App\Http\Resources;

use marcusvbda\vstack\Fields\Card;
use marcusvbda\vstack\Fields\Text;
use marcusvbda\vstack\Fields\TextArea;
use marcusvbda\vstack\Filters\FilterByOption;
use marcusvbda\vstack\Filters\FilterByPresetDate;
use marcusvbda\vstack\Filters\FilterByText;
use marcusvbda\vstack\Resource;

class Carros extends Resource
{
    public $model = \App\Http\Models\Car::class;

    public function label()
    {
        return "Veículos";
    }

    public function singularLabel()
    {
        return "Veículo";
    }

    // https://element.eleme.io/#/en-US/component/icon#icon
    public function icon()
    {
        return "el-icon-truck";
    }

    public function nothingStoredText()
    {
        return "<h4>Nenhum {$this->singularLabel()} cadastrado ainda ...</h4>";
    }

    public function nothingStoredSubText()
    {
        return "<span>Clique em cadastrar e efetue o primeiro cadastro !!!</span>";
    }

    public function storeButtonlabel()
    {
        return "<span class='el-icon-plus mr-2'></span>Cadastrar {$this->singularLabel()}";
    }

    public function table()
    {
        return [
            "code" => ["label" => "Código", "sortable_index" => "id"],
            "name" => ["label" => "Nome"],
            "created_at" => ["label" => "Data de Cadastro", "sortable" => false, "handler" => function ($row) {
                return $row->created_at->format("d/m/Y H:i:s");
            }]
        ];
    }

    public function beforeListSlot()
    {
        return '<el-alert
                    title="success alert"
                    type="success"
                    center
                    show-icon>
                </el-alert>';
    }

    public function afterListSlot()
    {
        return '<el-alert
                    title="warning alert"
                    type="warning"
                    center
                    show-icon>
                </el-alert>';
    }

    public function lenses()
    {
        return [
            "Apenas Ativos" => ["field" => "active", "value" => true],
            // "Apenas Inativos" => ["field" => "active", "value" => false],
            "Apenas Inativos" => ["field" => "active", "value" => false, "handler" => function ($q) {
                return $q->where("active", false);
            }],
        ];
    }

    public function fields()
    {
        $cards = [
            new Card("Informações", [
                new Text([
                    "label" => "Nome",
                    "field" => "name",
                    "description" => "Nome do veículo",
                    "rules" => ["max:255", "required"],
                ]),
                new TextArea([
                    "label" => "Observação",
                    "field" => "obs",
                    "show_value_length" => true,
                    "rules" => ["max:255", "required"],
                ])
            ])
        ];
        return $cards;
    }

    public function search()
    {
        return ['name', function ($query, $val) {
            return $query->where("name", "like", "%{$val}%");
        }];
    }

    public function filters()
    {
        return [
            new FilterByText([
                "column" => "name",
                "label" => "Nome"
            ]),
            new FilterByText([
                "column" => "description",
                "label" => "Descrição"
            ]),
            new FilterByPresetDate([
                "column" => "created_at",
                "label" => "Cadastrado em"
            ]),
            new FilterByOption([
                "label" => "Nível",
                "column" => "level",
                "multiple" => true,
                "options" => [
                    ["value" => 1, "label" => '⭐'],
                    ["value" => 2, "label" => '⭐⭐'],
                    ["value" => 3, "label" => '⭐⭐⭐'],
                    ["value" => 4, "label" => '⭐⭐⭐⭐'],
                    ["value" => 5, "label" => '⭐⭐⭐⭐⭐'],
                ]
            ]),
        ];
    }

    public function canViewReport()
    {
        // Auth::user()->hasPermission("view-cars-report");
        return true;
    }


    public function canImport()
    {
        return true;
    }

    public function extraActionButtons($row)
    {
        $buttons[] = [
            "title" => "Dashboard",
            "bg_color" => "#efefef",
            "icon" => "el-icon-pie-chart",
            "action_type" =>  "redirect",
            "url" => "/admin/resource/{$row->id}/dashboard"
        ];

        return $buttons;
    }

    public function exportColumns()
    {
        return [
            ["label" => "Código", "handler" => function ($row) {
                return @$row->code;
            }],
            ["label" => "Nome", "handler" => function ($row) {
                return @$row->name;
            }],
            ["label" => "Data de Cadastro", "handler" => function ($row) {
                return @$row->created_at->format("d/m/Y H:i:s");
            }],
        ];
    }
}