# Legislação — Views e UX

> Views do CMS em `v6/resources/views/prefecture/**`; views do portal em
> `v6/resources/views/portal/**` (variantes `modelo1..modelo6`, `default` e
> `newsbit`).

---

## 1. Visão geral das telas

| Superfície | Arquivo | Quem vê |
|---|---|---|
| CMS — lista de normas | `v6/resources/views/prefecture/contents/legislacao/index.blade.php` | Operador |
| CMS — formulário | `v6/resources/views/prefecture/contents/legislacao/form.blade.php` | Operador |
| CMS — criar / editar | `.../legislacao/create.blade.php`, `.../legislacao/edit.blade.php` | Operador |
| CMS — categorias | `v6/resources/views/prefecture/category-contents/legislacao/index.blade.php` | Operador |
| CMS — form de categoria | `v6/resources/views/prefecture/category-contents/legislacao/form.blade.php` | Operador |
| Portal — lista | `v6/resources/views/portal/layouts/modelo1/legislacao/list.blade.php` (idem em `modelo2`..`modelo6`) | Cidadão |
| Portal — página da categoria | `v6/resources/views/portal/layouts/modelo1/legislacao/show.blade.php` (idem nos demais modelos) | Cidadão |
| Portal — documento | `v6/resources/views/portal/layouts/modelo1/legislacao/view.blade.php` (idem nos demais modelos) | Cidadão |
| Portal — menu lateral | `v6/resources/views/portal/components/categories-contents/legislacoes/nav.blade.php` | Cidadão |
| Portal — PDF/export | `v6/resources/views/exports/contents.blade.php` | Cidadão (download) |

## 2. CMS — tela de listagem

`v6/resources/views/prefecture/contents/legislacao/index.blade.php` estende
`admin.layouts.app` (AdminLTE 1.22 / Bootstrap 3) e monta:

- Cabeçalho "Lista de Legislação".
- Botão **Cadastrar Legislação** → `cadastrar/legislacao` (protegido por
  `has_access(['legislacao.create'])`).
- Botão **Categorias de Legislação** → `route('cadastrar-categorias', 'legislacao')`.
- Botão de **exclusão em lote** (`prefecture.components.deleteByLoteSubmitButton`
  → `ajax.generic.content.deleteAll`), visível com `legislacao.delete`.
- Tabela `#tabela_legislacao` com colunas: *(checkbox de lote)*, `ID`, `Título`
  (50%), `Categoria`, `Data de publicação`, `Ações`.

### Grade (DataTable / Yajra)

`v6/public/assets/js/custom/contents/legislacao/datatables.js`:

```js
let collums = [
    { data: 'titulo',  render: function (data) { return `<div class='vertical-middle p-10-0' >${data}</div>`; } },
    { data: 'categoria', render: function (data) { return `<div class='font-weight-bold'>${data}</div>`; } },
    { data: 'data_publicacao', render: function (data) { return data ? `<div class="text-center">${data}</div>` : ' '; } },
    {
        data: null, name: "actions", orderable: false,
        render: function (data) {
            return `<div class='flex justify-center'>`
                + createSingleButton(data.id + 'Link',`/legislacao/${data.categoria_slug}/${data.slug}`, true, 'glyphicon glyphicon-link text-primary form_link', 'Acessar legislação', '_blank')
                + createBTN(data.id, `editar/legislacao/${data.id}`, data.edit, data.delete)
                + `</div>`;
        }
    }
];
runDatatables('legislacao', collums, true, 'ASC', null, true);
```

Pontos de atenção:

- O botão "Acessar legislação" monta a URL com
  `/legislacao/{categoria_slug}/{slug}` — depende de `categoria_slug`, que só
  existe na view `legislacao` a partir da v6; no PHP reimplementado
  (`DatatableController@get_legislacao`) o campo é montado com
  `CategoryContent::find($item->category_contents_id)->slug`.
- `runDatatables` (`v6/public/assets/js/custom/utilities/table.js`) já
  prepende as colunas `value` e (por causa de `deleteAll = true`) a de seleção
  em lote.
- Os botões de editar/excluir são habilitados pelos flags `data.edit` e
  `data.delete`, injetados por `struct_views()`.

## 3. CMS — formulário de conteúdo

`v6/resources/views/prefecture/contents/legislacao/form.blade.php`:

| Campo | Controle | Observações |
|---|---|---|
| `category_slug` | hidden | Valor = `$categoryContent->slug` (a raiz `legislacao`); usado pelo `ContentController@store` para escolher o redirect. |
| `category_contents_id` | `Form::select` | Opções vindas de `getAllItemsToCategoryContents($id, true)` (descendentes ordenados por `order`, prefixados com o pai). |
| `slug` | texto (só no edit) | Exibe o "Link Permanente" montado como `url("legislacao/$category->slug")` + slug; botão "Acessar". `maxlength=120` no HTML, `max:200` na validação. |
| `title` | texto | `maxlength=120`. |
| `publication_date` | texto + datetimepicker | Formato `d/m/Y H:i`, máscara `00/00/0000 00:00`, locale `pt-BR`. |
| `text` | textarea Summernote | `assets/plugins/summernote/summernote.min.js` + `assets/js/custom/utilities/richtext/summernote.js`. |
| Arquivos | `components.inputs.archive` | Upload via `assets/js/custom/utilities/helpers/add_archive.js`; o `ContentController` monta o caminho `storage/content/{slug}/{id}/...`. |

`create.blade.php` submete em `route('conteudo.index')`; `edit.blade.php` submete
em `route('conteudo.update', $content->id)` com `method => PUT` e tem uma
função `deleteArchive()` que apaga anexos via `ajax.delete.files`.

> **Artefato morto:** `v6/resources/views/prefecture/contents/legislacao/partials/link-row.blade.php`
> implementa um formulário de "normas vinculadas" (`norma_links[...]`) que
> referencia `\App\Prefecture\LegislationLink`. **Esse model não existe** na v6
> e a partial **não é incluída em lugar nenhum** (verificado por `grep`).
> É resíduo de uma funcionalidade abandonada — não reproduzir na v7.

## 4. CMS — categorias de legislação

`v6/resources/views/prefecture/category-contents/legislacao/index.blade.php`:

- Lista as subcategorias ordenadas por `order`.
- Cada linha tem o "alça" de arraste (`<span class="grabbable" data-title="Arraste para ajustar">`).
- Ao soltar, o JS envia a nova ordem para `POST /cms/ajax/ajust-categorias`
  (`sortable_data` = array de ids), que chama
  `CategoryContentController@ajustOrder` → `reorder(new CategoryContent(), $array_items)`.
- Botão editar → `route('editar-categorias', ['legislacao', $content->id])`.
- Botão excluir → `Form::open(['route' => ['categoria-conteudo.destroy', $content->id], 'method' => 'DELETE'])`.

`v6/resources/views/prefecture/category-contents/legislacao/form.blade.php` é a
tela de cadastro em **modal/lista dinâmica**: o JS
`v6/public/assets/js/custom/contents/category-contents/legislacao/script.js`
carrega `GET /cms/categories/legislacao` (JSON), ordena por `parentId`/`order` e
monta a árvore; `listeners.js` persiste via `POST/PUT/DELETE /cms/categories`
(JSON) e `structure.js` implementa o `sortable`.

`edit.blade.php` é uma tela à parte: quando a rota é `editar-categorias`, ela
recalcula a árvore de descendentes em PHP (funções recursivas `$getDescendants`
e `$getContentDescendants`) e monta o `<select>` de categoria-pai **excluindo a
própria categoria e seus descendentes** (prevenção de referência circular).

## 5. Portal — o que o cidadão vê

### 5.1 Layouts

O layout ativo vem de `configurations.key = 'layout'`
(`GetInformationsPrefectureTrait::getTemplateActived()`,
`v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92-102`). A função aborta
404 se `portal.layouts.<layout>.home.index` não existir. Verificado:

| Layout | `home/index.blade.php` | Alcançável |
|---|---|---|
| `modelo1` .. `modelo6` | sim | **sim** |
| `default` | **não** | não (404) |
| `newsbit` | **não** | não (404) |

Consequência: as views `default` e `newsbit` de legislação são **código morto**
(elas são as únicas que usam a paginação do servidor — ver 5.3).

### 5.2 Menu lateral — **não existe `pub-nav.blade.php` para legislação**

| Layout | Menu lateral da legislação | Arquivo |
|---|---|---|
| `modelo1`, `modelo2` | lista recursiva | `v6/resources/views/portal/components/categories-contents/legislacoes/nav.blade.php` |
| `modelo3`, `modelo4` | abas com submenu (dropdown) | `v6/resources/views/portal/layouts/modelo3/legislacao/elements/leg-nav.blade.php`, `v6/resources/views/portal/layouts/modelo4/legislacao/elements/leg-nav.blade.php` |
| `modelo5`, `modelo6` | lista recursiva | `v6/resources/views/portal/layouts/modelo5/legislacao/elements/leg-nav.blade.php`, `v6/resources/views/portal/layouts/modelo6/legislacao/elements/leg-nav.blade.php` |
| `default`, `newsbit` | — | views inacessíveis |

`nav.blade.php` (modelo1/modelo2) delega para o componente genérico:

```blade
<div class="col-md-4 col-sm-12 btn-group-vertical nav-pub d-table mb-4 align-top" id="cabecalho">
    <div class="card">
        @include('portal.components.categories-contents.recursive-list', [
            'route' => 'conteudo.legislacao.list',
            'categories' => $categories,
        ])
    </div>
</div>
```

`v6/resources/views/portal/components/categories-contents/recursive-list.blade.php`
renderiza a árvore recursivamente usando `rightWayToGetCategoryContents` e
aplica o `padding-left` proporcional à profundidade (`1.05rem` por nível);
marca a categoria ativa comparando `$cat->slug == $category->slug`.

Os `leg-nav.blade.php` de `modelo3`/`modelo4` **não** usam o componente
recursivo: montam abas e, quando a categoria tem filhos
(`$cat->rightWayToGetCategoryContents->isNotEmpty()`), um dropdown de 1º nível.

> **View quebrada:** `v6/resources/views/portal/layouts/modelo1/legislacao/index.blade.php:10`
> e `.../modelo2/legislacao/index.blade.php:10` incluem
> `portal.components.categories-contents.legislacao.nav` (singular), mas o
> diretório existente é `legislacoes` (plural). Essas duas views de índice
> nunca são renderizadas (o controller só devolve `legislacao.list`,
> `legislacao.show` e `legislacao.view`), então o erro não aparece em produção
> — mas são armadilhas para quem for reusar o markup.

### 5.3 Página da categoria (`show`) e lista (`list`)

`.../modeloN/legislacao/show.blade.php`:
breadcrumb `category-legislacao`, spinner `sk-spinner`, inclui o menu lateral e
faz `@include('portal.layouts.modeloN.legislacao.list')` dentro de
`#include-list`.

`.../modeloN/legislacao/list.blade.php` (idêntico em estrutura nos 6 modelos)
contém:

| Elemento | Atributo | Enviado como |
|---|---|---|
| Categoria | hidden `#sub_category` | `sub_category` / `slug` |
| Módulo | hidden | `category=legislacao` |
| Data inicial | `#DataINI` (`type="date"`) | `data_ini` no formulário, `data_inicial` no JS |
| Data final | `#DataFIM` (`type="date"`) | `data_fim` no formulário, `data_final` no JS |
| Texto | `#Search_termo` | `termo` |
| Botão | `.consultarFiltro` | dispara `refreshData(...)` |
| Lista | `<ul id="legislacaoZona">` | preenchida por JS |
| Paginação | `<div class="links">` | montada por JS |

**A view não usa `$contents`.** A lista vem inteiramente do JS. Os únicos
layouts que usam a paginação do servidor são:

| View | Uso |
|---|---|
| `v6/resources/views/portal/layouts/default/pages/contents/legislacao/list.blade.php:92-112` | `@foreach($contents ...)` + `{{$contents->links()}}` — **layout inalcançável**. |
| `v6/resources/views/portal/layouts/newsbit/legislacao/list.blade.php:44-87` | `@foreach($contents ...)` + `{{$contents->links('vendor.pagination.newsbit')}}` — **layout inalcançável**. |

Além disso, `show.blade.php` (modelo1..6) contém um bloco `getPosts(page)`
que faz `$.ajax({url: '?page=' + page + ...})` contra a própria rota e
substitui `#include-list` — herança de uma versão que paginava no servidor.
Hoje ele retorna a view da lista **sem itens** (porque `$contents` não é
renderizado). Além disso lê `$('input[id="search"]')`, id que **não existe**
nessas views (o campo é `#Search_termo`), logo `texto` chega como `undefined`.

### 5.4 Documento (`view`)

`.../modeloN/legislacao/view.blade.php` mostra:

- Título (`$content->title`).
- "Publicado em dd/mm/yyyy às HH:mi" (`date('d/m/Y', ...)` + `date('H:i', ...)`).
- Botão de compartilhamento `addthis_inline_share_toolbox`.
- Corpo `{!! $content->text !!}` (HTML **sem escape**).
- Lista de arquivos via `@include('portal.components.view_pdf.files-list')` —
  o componente embute o PDF (`view_pdf`) em `<iframe>` e lista os demais para
  download, filtrando por extensão.
- Coluna lateral com `@include('portal.layouts.modeloN.layout.sidebar.layout')`.

### 5.5 Exportação

Botões `xls`, `pdf`, `csv`, `txt` chamam a função inline
`exportarLegislacao(type)` (definida no `list.blade.php`):

```js
function exportarLegislacao(type) {
    var params = {
        type: type,
        sub_category: '{{ $category->slug }}',
        data_inicial: $('#DataINI').val(),
        data_final: $('#DataFIM').val(),
        termo: $('#Search_termo').val()
    };
    window.location.href = '{{ route('legislacao.export') }}?' + $.param(params);
}
```

O servidor monta o arquivo com
`ExportsContentTrait::exportContents($data, $type, $filename, $reportTitle)`:

| Tipo | Mecanismo | Extensão |
|---|---|---|
| `pdf` | `\PDF::loadView('exports.contents', ...)` (dompdf), papel A4 paisagem | `.pdf` |
| `xls` | `\Excel::download($export, $filename . '.xlsx')` | `.xlsx` |
| `txt` | `\Excel::download(..., Excel::TSV)` | `.txt` |
| `csv` (default) | `\Excel::download(...)` | `.csv` |

Nome do arquivo: `legislacao_{slug_da_categoria}_{Ymd_His}`; título do
relatório: `Legislação - {nome da categoria}`. A view PDF é
`v6/resources/views/exports/contents.blade.php` (colunas: Título, Data de
Publicação, Descrição — esta última truncada em 300 caracteres via `str_limit`).
O cabeçalho do PDF usa a cor `configurations.key = 'cor_1'` (fallback `#1075ad`).

> A exportação **não é paginada nem limitada**: `$data = $query->...->get()`
> carrega todas as normas da categoria filtrada em memória.

## 6. JS/Ajax do portal

`v6/public/assets/js/custom/contents/legislacao/legislacao_portal.js`:

| Endpoint | Método | Payload | Retorno |
|---|---|---|---|
| `/legislacao/filtro` | POST (JSON) | `{slug}` na 1ª carga; `{data_inicial, data_final, termo, slug}` ao filtrar | JSON array de conteúdos |

Dependências: `v6/public/assets/js/custom/utilities/functions.js`
(`myFetch`, `createChunks`, `create_element`, `formatDateBr`, `formatDateTimeBr`,
`stripTags`).

A paginação é **inteiramente no cliente**:

```js
// v6/public/assets/js/custom/contents/legislacao/legislacao_portal.js:33-39
let pagination = data.chunk(10);

let resultado = ``;

if (data[0]) {
    if (select_page >= pagination.length) select_page = pagination.length - 1;
    pagination[select_page].map(value => {
```

`createChunks()` (linhas 35-44 de `functions.js`) define
`Array.prototype.chunk`. Cada item renderizado traz o link
`/legislacao/${value.categoria}/${value.slug}` — o atributo `categoria` é
injetado pelo controller em `filtroGET` (`$item['categoria'] = $categoria`).

Há ainda um filtro de segurança no cliente: itens com `publication_date` futura
são descartados (`if (value.publication_date < dataFormatada)`).

> **Armadilha:** `legislacao_portal.js` redefine `createLink` globalmente
> (linha 135), sobrescrevendo a função homônima de `functions.js`. Em
> `modelo1..6` não há outro consumidor na mesma página, mas o conflito existe.

## 7. JS do CMS

| Arquivo | Papel |
|---|---|
| `v6/public/assets/js/custom/contents/legislacao/datatables.js` | Grade da lista de normas. |
| `v6/public/assets/js/custom/utilities/table.js` | `runDatatables()`, `createBTN()`, `createSingleButton()`, botões de exportação do DataTable. |
| `v6/public/assets/js/custom/utilities/deleteAll.js` | Exclusão em lote (`ajax.generic.content.deleteAll`). |
| `v6/public/assets/js/custom/utilities/helpers/add_archive.js` | Upload de anexos do formulário. |
| `v6/public/assets/js/custom/contents/category-contents/legislacao/script.js` | Carrega `/cms/categories/legislacao` e monta a árvore. |
| `v6/public/assets/js/custom/contents/category-contents/legislacao/edit.js` | Edição pontual da categoria (formulário do `edit.blade.php`). |
| `v6/public/assets/js/custom/contents/category-contents/listeners.js` | `POST/PUT/DELETE /cms/categories`. |
| `v6/public/assets/js/custom/contents/category-contents/helpers.js` | Componentes de formulário (`Field`, `Input`, `Select`). |
| `v6/public/assets/js/custom/contents/category-contents/structure.js` | `ListController`, sortable, montagem da árvore. |
| `v6/public/assets/js/modulos/sortableJs-min.js` | Sortable (usado na tela de categorias). |
| `v6/public/assets/js/custom/utilities/richtext/summernote.js` | Summernote (corpo do texto). |
