Skip to content
Extensões

Guia do Componente FK V2

Versão: 2.0.0 Localização: /public/css/design-system/components/fk-v2.*Documentação: /public/doc/architect/fk/evolucao_componente_fk/

Índice

  1. Visão Geral
  2. Instalação
  3. Uso Básico
  4. Configuração do Painel
  5. API PHP
  6. API JavaScript
  7. Eventos
  8. Exemplos Completos
  9. Migração do FK Legado

Visão Geral

O FK V2 é o novo componente de Foreign Key do Design System SIDASP. Diferente do FK legado que usa modal centralizado, o FK V2 renderiza no Sidebar Right com um painel deslizante para adicionar/editar.

Comparativo

AspectoFK LegadoFK V2
LocalizaçãoModal centralizadoSidebar Right
SerializaçãoDelimitadoresJSON
Validaçãoeval()JSON.parse()
Dark ModeParcial100%
AcessibilidadeBásicaWCAG 2.1 AA

Arquivos

/public/css/design-system/components/
├── fk-v2.css          # Estilos do trigger e cards
├── fk-v2-panel.css    # Estilos do painel deslizante
└── fk-v2.js           # Lógica JavaScript

/vendor/Agrodefesa/library/
├── Form/Element/FkV2.php              # Classe de renderização
└── Controller/Crud/ForeignKeyV2.php   # Trait para controllers

Instalação

1. Incluir CSS

html
<!-- No layout ou view -->
<link rel="stylesheet" href="/css/design-system/components/fk-v2.css">
<link rel="stylesheet" href="/css/design-system/components/fk-v2-panel.css">

2. Incluir JavaScript

html
<!-- Antes de fechar </body> -->
<script src="/css/design-system/components/fk-v2.js"></script>

3. Usar no PHP

php
<?php
// Instanciar diretamente
$fk = new \Agrodefesa\Form\Element\FkV2();

// Ou importar
use Agrodefesa\Form\Element\FkV2;
$fk = new FkV2();

// Ou método estático
$fk = FkV2::create();

Uso Básico

Na View (sidebar.phtml)

php
<?php
$fkEndereco = new \Agrodefesa\Form\Element\FkV2();
$fkEndereco->setId('fk_endereco')
    ->setNome('fk_endereco')
    ->setTitulo('Endereço(s)')
    ->setDescricao('Adicione um ou mais endereços')
    ->setRota($this->url('modulo/default', [
        'controller' => 'controlador',
        'action' => 'fk-endereco-v2'
    ]))
    ->setIcone('mdi-home')
    ->setDados($this->fkEnderecos ?? []);

echo $fkEndereco->render();
?>

No Controller (Trait Fk)

php
<?php
namespace MeuModulo\Controller\MeuControlador;

trait Fk
{
    /**
     * Action do painel FK
     * Rota: /meu-modulo/meu-controlador/fk-endereco-v2
     */
    public function fkEnderecoV2Action()
    {
        // Dados para edição vêm via query string
        $this->viewModel->dados = \Agrodefesa\Util\Util::arrayToObject(
            $this->params()->fromQuery()
        );

        // Listas via Model
        $this->viewModel->tiposEndereco = $this->negocio->getTiposEndereco();
    }

    /**
     * Validação do FK
     * Rota: /meu-modulo/meu-controlador/fk-endereco-v2-validar
     */
    public function fkEnderecoV2ValidarAction()
    {
        $dados = $this->params()->fromPost();
        $erros = $this->negocio->validarFkEndereco($dados);

        return $this->jsonResponse([
            'status' => empty($erros) ? 'ok' : 'erro',
            'erros' => $erros,
        ]);
    }
}

View do Painel (fk-endereco-v2.phtml)

php
<?php $dados = $this->dados ?? new \stdClass(); ?>

<div class="fk-v2-panel__header">
    <h3 class="fk-v2-panel__title">
        <?= empty($dados->oid) ? 'Adicionar endereço' : 'Editar endereço' ?>
    </h3>
    <button type="button" class="fk-v2-panel__close" data-fk-v2-panel-close>
        <i class="mdi mdi-close"></i>
    </button>
</div>

<div class="fk-v2-panel__body">
    <div data-fk-v2-errors style="display: none;"></div>

    <form class="fk-v2-panel__form">
        <input type="hidden" name="oid" value="<?= $dados->oid ?? '' ?>">

        <div class="fk-v2-panel__form-group">
            <label class="fk-v2-panel__label">Logradouro *</label>
            <input type="text" name="logradouro" class="form-control"
                   value="<?= $dados->logradouro ?? '' ?>">
        </div>

        <!-- Mais campos... -->
    </form>
</div>

<div class="fk-v2-panel__footer">
    <button type="button" class="btn btn-outline" data-fk-v2-panel-cancel>
        Cancelar
    </button>
    <button type="button" class="btn btn-primary" data-fk-v2-panel-save>
        Salvar
    </button>
</div>

Configuração do Painel

Largura Personalizada

php
// Via pixels
$fk->setLargura(700);

// Via classe CSS
$fk->setLarguraClasse('lg');  // sm=400px, md=520px, lg=700px, xl=900px, full=100%

Template da Ficha

php
// Customizar campos exibidos na ficha
$fk->setTemplateFicha([
    'primary' => '{{logradouro}}, {{numero}}',
    'secondary' => '{{tipo_endereco}}',
    'tertiary' => '{{municipio}}-{{uf}}',
    'icon' => 'home'
]);

Configurações Adicionais

php
$fk->setConfig([
    'required' => true,           // FK obrigatório
    'minItems' => 1,              // Mínimo de itens
    'maxItems' => 5,              // Máximo de itens
    'customValidation' => true,   // Validação customizada
]);

API PHP

Classe FkV2

php
use Agrodefesa\Form\Element\FkV2;

$fk = new FkV2();

// Métodos de configuração (fluent interface)
$fk->setId(string $id): self
$fk->setNome(string $nome): self
$fk->setTitulo(string $titulo): self
$fk->setDescricao(string $descricao): self
$fk->setRota(string $rota): self
$fk->setIcone(string $icone): self
$fk->setLargura(int $largura): self
$fk->setLarguraClasse(string $classe): self
$fk->setDados(array $dados): self
$fk->setConfig(array $config): self
$fk->setTemplateFicha(array $template): self

// Renderização
$fk->render(): string
echo $fk;  // __toString()

Trait ForeignKeyV2

php
use Agrodefesa\Controller\Crud\ForeignKeyV2;

class MeuController extends Crud
{
    use ForeignKeyV2;

    // Métodos disponíveis:

    // Configura actions automaticamente
    $this->configurarFkV2(): void

    // Respostas JSON
    $this->fkV2Ok(array $dados = []): JsonModel
    $this->fkV2Erro(array $erros): JsonModel

    // Processar dados do POST
    $this->processarFkV2(string $campo): array
    $this->processarMultiplosFkV2(array $campos): array

    // Separar itens novos/existentes/removidos
    $this->separarItensFkV2(array $items, array $idsOriginais = []): array

    // Validação
    $this->validarCamposObrigatoriosFkV2($item, array $campos, array &$erros): bool

    // Preparar para banco
    $this->prepararDadosInsercaoFkV2($item, array $camposPermitidos = []): array
}

API JavaScript

Objeto Global FkV2

javascript
// Inicializar (automático no DOMContentLoaded)
FkV2.init('[data-fk-v2]');

// Obter instância
const fk = FkV2.get('fk_endereco');

// Obter todas instâncias
const todas = FkV2.getAll();

// Destruir instância
FkV2.destroy('fk_endereco');

// Destruir todas
FkV2.destroyAll();

Instância FkV2Controller

javascript
const fk = FkV2.get('fk_endereco');

// Obter itens
const items = fk.getItems();

// Definir itens
fk.setItems([
    { oid: '1', logradouro: 'Rua A', numero: '100' },
    { oid: '2', logradouro: 'Rua B', numero: '200' }
]);

// Adicionar item
fk.addItem({ logradouro: 'Rua C', numero: '300' });

// Atualizar item
fk.updateItem('1', { numero: '150' });

// Remover item
fk.removeItem('1');

// Limpar todos
fk.clear();

// Contagem
fk.count();      // número de itens
fk.isEmpty();    // true se vazio

Eventos

O componente dispara eventos customizados no container:

javascript
const container = document.querySelector('[data-fk-v2-id="fk_endereco"]');

// Item alterado (adicionado, editado ou removido)
container.addEventListener('fk:change', (e) => {
    console.log('Itens:', e.detail.items);
    console.log('FK ID:', e.detail.fkId);
});

// Painel aberto
container.addEventListener('fk:panel:open', (e) => {
    console.log('Editando:', e.detail.editingOid);
});

// Painel fechado
container.addEventListener('fk:panel:close', (e) => {
    console.log('Painel fechado');
});

// Item salvo
container.addEventListener('fk:item:save', (e) => {
    console.log('Item:', e.detail.item);
    console.log('É novo?', e.detail.isNew);
});

// Item deletado
container.addEventListener('fk:item:delete', (e) => {
    console.log('OID removido:', e.detail.oid);
});

Exemplos Completos

Exemplo 1: FK de Endereço

Controller:

php
// Controller/Pessoa/Fk.php
trait Fk
{
    public function fkEnderecoV2Action()
    {
        $this->viewModel->dados = \Agrodefesa\Util\Util::arrayToObject(
            $this->params()->fromQuery()
        );
        $this->viewModel->tiposEndereco = $this->negocio->getTiposEndereco();
        $this->viewModel->listaMunicipio = $this->negocio->getListaMunicipio();
    }

    public function fkEnderecoV2ValidarAction()
    {
        $erros = $this->negocio->validarFkEndereco($this->params()->fromPost());
        return $this->fkV2Ok() ?: $this->fkV2Erro($erros);
    }
}

View:

php
<!-- sidebar.phtml -->
<?php
$fk = new \Agrodefesa\Form\Element\FkV2();
echo $fk->setId('fk_endereco')
    ->setNome('fk_endereco')
    ->setTitulo('Endereço(s)')
    ->setDescricao('Adicione um ou mais endereços')
    ->setRota($this->url('cadastros-agropecuarios/default', [
        'controller' => 'pessoa',
        'action' => 'fk-endereco-v2'
    ]))
    ->setIcone('mdi-home')
    ->setLargura(600)
    ->setTemplateFicha([
        'primary' => '{{logradouro}}, {{numero}}',
        'secondary' => '{{tipo_endereco}}',
        'tertiary' => '{{municipio}}-{{uf}}',
        'icon' => 'home'
    ])
    ->setDados($this->fkEnderecos ?? [])
    ->render();
?>

Exemplo 2: FK de Contato com Seletor de Tipo

php
<!-- fk-contato-v2.phtml -->
<?php $dados = $this->dados ?? new \stdClass(); ?>

<div class="fk-v2-panel__header">
    <h3 class="fk-v2-panel__title">
        <?= empty($dados->oid) ? 'Adicionar contato' : 'Editar contato' ?>
    </h3>
    <button type="button" class="fk-v2-panel__close" data-fk-v2-panel-close>
        <i class="mdi mdi-close"></i>
    </button>
</div>

<div class="fk-v2-panel__body">
    <div data-fk-v2-errors style="display: none;"></div>

    <form class="fk-v2-panel__form">
        <input type="hidden" name="oid" value="<?= $dados->oid ?? '' ?>">

        <div class="fk-v2-panel__form-group">
            <label class="fk-v2-panel__label">Tipo de contato</label>
            <div class="fk-v2-type-selector">
                <?php foreach ($this->tiposContato as $tipo): ?>
                <label class="fk-v2-type-card <?= ($dados->id_tipo_contato ?? '') == $tipo->id ? 'is-selected' : '' ?>">
                    <input type="radio" name="id_tipo_contato" value="<?= $tipo->id ?>"
                           <?= ($dados->id_tipo_contato ?? '') == $tipo->id ? 'checked' : '' ?>>
                    <span class="fk-v2-type-card__icon">
                        <i class="mdi <?= $tipo->icone ?>"></i>
                    </span>
                    <span class="fk-v2-type-card__label"><?= $tipo->nome ?></span>
                </label>
                <?php endforeach; ?>
            </div>
        </div>

        <div class="fk-v2-panel__form-group">
            <label class="fk-v2-panel__label">Número *</label>
            <input type="text" name="numero" class="form-control"
                   value="<?= $dados->numero ?? '' ?>"
                   placeholder="(00) 00000-0000"
                   data-mask="telefone">
        </div>
    </form>
</div>

<div class="fk-v2-panel__footer">
    <button type="button" class="btn btn-outline" data-fk-v2-panel-cancel>
        Cancelar
    </button>
    <button type="button" class="btn btn-primary" data-fk-v2-panel-save>
        Salvar
    </button>
</div>

Migração do FK Legado

Diferenças Principais

FK LegadoFK V2
nti.abrirFK()Clique automático no trigger
Delimitadores ; e =JSON
Modal jQuery UIPainel deslizante
eval() para parseJSON.parse()

Passos de Migração

  1. Criar action do painel (fk*V2Action)
  2. Criar action de validação (fk*V2ValidarAction)
  3. Criar view do painel (fk-*-v2.phtml)
  4. Substituir na view o FK legado pelo FkV2
  5. Atualizar Model para validação JSON

Coexistência

O FK V2 pode coexistir com o FK legado indefinidamente. Use FK V2 em novos formulários e migre os existentes gradualmente.


Troubleshooting

Painel não abre

  1. Verificar se a rota está correta
  2. Verificar se o JavaScript foi carregado
  3. Verificar console do navegador

Dados não salvam

  1. Verificar se o input hidden está dentro do form
  2. Verificar se a validação está retornando status: 'ok'
  3. Verificar formato JSON dos dados

Estilos não aplicados

  1. Verificar se os CSS foram carregados
  2. Verificar se não há conflitos com CSS legado
  3. Verificar se as variáveis CSS do design system estão definidas

Documentação completa: /public/doc/architect/fk/evolucao_componente_fk/PROJETO_FK_V2_SIDEBAR.md

Design System interno do GALES