# Requerimentos — Views e UX

> **Versão analisada:** `v6` — módulo **novo na v6**
> **Slug:** `requerimentos` (`modulos.id = 41`)

---

## 1. Portão de entrada: escolha do tema

O controller resolve o template ativo em tempo de execução:

```php
// v6/app/Http/Controllers/Admin/Controller.php:39-49
protected function getTemplateActived()
{
    $templateActived = Configuration::where('key', 'layout')->first()->value;

    if (!View::exists('portal.layouts.'.$templateActived.'.home.index')) {
        DB::table('configurations')->where('key', 'pagina_em_manutencao')->update(['value' => 1]);
        abort(404, 'view_inexistente');
    }

    return $templateActived;
}
```

Todas as views do módulo são resolvidas como
`portal.layouts.{template}.requerimentos.*`. O módulo possui blades em **seis**
temas (`modelo1`…`modelo6`), todos com o mesmo conjunto de arquivos.

## 2. Blades do portal

| Arquivo (exemplo `modelo1`) | Função |
|---|---|
| `v6/resources/views/portal/layouts/modelo1/requerimentos/show.blade.php` | Página de listagem: inclui o menu lateral, o container `#include-list` e todo o JavaScript de Ajax |
| `v6/resources/views/portal/layouts/modelo1/requerimentos/list.blade.php` | Bloco **parcial** com os filtros e os itens da lista (renderizado também na resposta Ajax) |
| `v6/resources/views/portal/layouts/modelo1/requerimentos/view.blade.php` | Página de detalhe do requerimento |
| `v6/resources/views/portal/layouts/modelo1/requerimentos/nav.blade.php` | Menu lateral com as subcategorias (modelo1, modelo2, modelo5, modelo6) |
| `v6/resources/views/portal/layouts/modelo3/requerimentos/pub-nav.blade.php` | Menu lateral em formato de abas (modelo3, modelo4) |

Equivalentes em `modelo2`, `modelo3`, `modelo4`, `modelo5` e `modelo6` — os
arquivos `nav.blade.php` existem em modelo1, modelo2, modelo5 e modelo6;
`pub-nav.blade.php` existe em modelo3 e modelo4. Cada `show.blade.php` inclui o
arquivo de menu **do próprio tema** (ex.: `@include('portal.layouts.modelo3.requerimentos.pub-nav')`).

> Não existe pasta `requerimentos` nos outros temas do portal
> (`default`, `newsbit`, `calendar-events`, `diario-oficial`, `surveys`,
> `dynamics-forms`, `contratos`, `busca-diarias`, `barra-privacidade`): se o
> `Configuration.key = 'layout'` apontar para um deles, o módulo **quebra com
> 404** (`view_inexistente`).

## 3. Tela de listagem (`show.blade.php` + `list.blade.php`)

### 3.1 Estrutura

- **Menu lateral** (`nav.blade.php`): uma entrada por subcategoria, com link
  `route('requerimentos.show', $cat->slug)` e classe `active` na categoria atual.
- **Área de filtros** (dentro de `list.blade.php`): `select` de **ano**,
  `select` de **mês** e `input` de texto (`id="search"`), com botões de limpar
  (`apagaTexto()`) e de aplicar (`getPostsByTexto()`).
- **Lista** (`#pubs`): cada item mostra data, título e um resumo de 200
  caracteres do texto, com link para `requerimentos.view`.

Trecho real da listagem (`v6/resources/views/portal/layouts/modelo1/requerimentos/list.blade.php`):

```blade
<a href='{{ route('requerimentos.view', [$category->slug, $content->slug]) }}' class='text-muted d-table'>
    ...
    <h6 class='d-inline-block mb-0' style="text-align: left;"><span>
            {{ date('d/m/Y', strtotime($content->publication_date)) }} -
        </span>{{ $content->title }}</h6>
    <div class='small content-pub'>
        {{ str_limit(strip_tags(stripslashes($content->text)), 200) }}
    </div>
```

Observações:

- O resumo usa `str_limit()`, **removido no Laravel 6+** — exige substituto
  (`Str::limit()`) na v7.
- O link usa **a categoria ativa** (`$category->slug`), não a categoria real do
  conteúdo — coerente com a unificação da listagem.
- O `<select>` de meses só é preenchido quando há `ano` selecionado
  (`@if (isset($meses) && !empty($meses))`).
- O `<select>` de anos filtra `@if ($ano->ano <= date('Y'))`.

### 3.2 Padrão Ajax (idêntico nos 6 temas)

Bloco de script dentro de `show.blade.php` (`@section('other-scripts')`):

| Função | Endpoint chamado | Efeito |
|---|---|---|
| `getPosts(page)` | `?page={page}&ano={ano}&mes={mes}&texto={texto}` | Recarrega a lista trocando `#include-list` e grava `location.hash = page` |
| `getPostsByAno()` | `?ano={ano}` | Filtra por ano; se o ano for vazio, `location.reload()` |
| `getPostsByMes()` | `?ano={ano}&mes={mes}` | Filtra por mês |
| `getPostsByTexto()` | `?ano={ano}&mes={mes}&texto={texto}` | Filtra por texto |
| `apagaTexto()` | — | Limpa o campo e reexecuta `getPostsByTexto()` |

Comportamento:

```js
$.ajax({
    url: '?page=' + page + '&ano=' + ano + '&mes=' + mes + '&texto=' + text,
    dataType: 'json',
}).done(function (data) {
    $('#pub-content').toggleClass('sk-loading');
    $('#include-list').html(data);
    location.hash = page;
}).fail(function () {
    $('#pub-content').toggleClass('sk-loading');
    alert('Os requerimentos não puderam ser carregadas.');
});
```

- O `hashchange` do navegador refaz a requisição da página indicated no hash
  (navegação por histórico sem recarregar).
- O clique nos links de paginação é interceptado (`e.preventDefault()`) e
  dispara `getPosts()`.
- A resposta do servidor é **JSON contendo HTML** (`Response::json(view(...)->render())`).
- O parâmetro `texto` é concatenado **sem `encodeURIComponent`**, o que quebra
  a requisição quando o termo contém `&`, `#` ou espaços reservados.

## 4. Página de detalhe (`view.blade.php`)

- Breadcrumb: `Breadcrumbs::view('portal.layouts.modelo1.layout.breadcrumb', 'view-requerimentos', $content)`.
- Metatags Open Graph (`og:url`, `og:type`, `og:title`, `og:description`).
- Cabeçalho com o nome da categoria, o título, a data e a hora de publicação.
- Corpo: `{!! $content->text !!}` — HTML **sem escape** (conteúdo do Summernote).
- Bloco de anexos:

```blade
@if (count($archives) > 0 || $view_pdf != null)
    @include('portal.components.view_pdf.files-list')
@endif
```

### 4.1 Componente de anexos

Arquivo: `v6/resources/views/portal/components/view_pdf/files-list.blade.php`.

| Comportamento | Detalhe |
|---|---|
| Detecção de mobile | `preg_match('/(iPhone|iPad|iPod|Android|webOS|BlackBerry|Windows Phone)/i', request()->header('User-Agent'))` |
| PDF/arquivo principal | Se `view_pdf->type` for `image/jpeg` ou `image/png`, mostra `<img>`; senão embute em `<iframe>` dentro de `embed-responsive embed-responsive-16by9` |
| Extensões de download | `['rar','zip','doc','docx','pdf','odt','xls','xlsx','xlsb','xslm','ppt','pptx']` |
| Filtro desktop | mantém anexos com `highlight !== true` **e** `view_pdf !== true` |
| Filtro mobile | mantém anexos com `highlight !== true` (inclui o `view_pdf`, que vira link) |
| Exibição | link `url($archive->path)` em `target="_blank"`, com `file_exist($archive->path)` como guarda |
| Vazio | exibe “Nenhum arquivo encontrado” quando nada é listado |

> O componente é **genérico** e compartilhado por outros módulos (publicações,
> atas, etc.). Ele espera as variáveis `$archives` e `$view_pdf` no escopo.

## 5. Blades do CMS (AdminLTE)

### 5.1 Conteúdo

| Arquivo | Função |
|---|---|
| `v6/resources/views/prefecture/contents/requerimentos/index.blade.php` | Listagem (DataTable `#tabela_requerimentos`), botões “Categorias de Requerimento” / “Cadastrar Requerimento” e exclusão em lote |
| `v6/resources/views/prefecture/contents/requerimentos/create.blade.php` | Formulário de cadastro (`Form::open(['route' => ['conteudo.index']])`) |
| `v6/resources/views/prefecture/contents/requerimentos/edit.blade.php` | Formulário de edição (`Form::model($content, ['route' => ['conteudo.update', $content->id], 'method' => 'PUT'])`) |
| `v6/resources/views/prefecture/contents/requerimentos/form.blade.php` | Campos do requerimento (compartilhado entre create/edit) |

Campos do formulário (`form.blade.php`):

| Campo | Tipo | Observação |
|---|---|---|
| `category_slug` | hidden | `{{ $categoryContent->slug }}` |
| `category_contents_id` | select | Subcategorias (`categoryContentSelect`), obrigatório |
| `title` | text | Obrigatório, `maxlen` 150 na UI (a validação aceita 120) |
| `publication_date` | date+hora | Máscara `br-data-tempo`, formato `d/m/Y H:i` |
| `councilmen_id` | select | Vereadores com `status = true`, ordenados por nome; `alt_default` = “Selecione o requerente” |
| `text` | textarea | Editor Summernote, obrigatório |
| `archive` | upload | Componente `components.inputs.archive`; “Tamanho máximo: 128MB” |

> O `councilmen_id` **não** é obrigatório: o select permite “0” (nenhum
> requerente), e a validação aceita vazio/`0`.

### 5.2 Categorias

| Arquivo | Função |
|---|---|
| `v6/resources/views/prefecture/category-contents/requerimentos/index.blade.php` | Listagem das subcategorias |
| `v6/resources/views/prefecture/category-contents/requerimentos/create.blade.php` | Cadastro (`Form::open(['route' => ['categoria-conteudo.store']])`) + tabela com reordenação por Ajax (`route('ajust.categorias')`) |
| `v6/resources/views/prefecture/category-contents/requerimentos/edit.blade.php` | Edição (`categoria-conteudo.update`) |
| `v6/resources/views/prefecture/category-contents/requerimentos/form.blade.php` | Campos da categoria |

### 5.3 JavaScript do DataTable

Arquivo: `v6/public/assets/js/custom/contents/requerimentos/datatables.js`.

```js
let collums = [
    { data: 'titulo', render: ... },
    { data: 'requerente', render: function (data) { return `<div class="text-center">${data || '-'}</div>`; } },
    { data: 'categoria', render: ... },
    { data: 'data_publicacao', render: ... },
    { data: null, orderable: false, render: function (data) {
        return `<div class='flex justify-center'>`
            + createBTN(data.id, `editar/requerimentos/${data.id}`, data.edit, data.delete)
            + `</div>`;
    }},
];

runDatatables('requerimentos', collums, true, 'ASC', null, true);
```

- `runDatatables()` está em `v6/public/assets/js/custom/utilities/table.js:3`.
- A coluna `requerente` exibe `-` quando o valor é vazio (requerimento sem
  vereador ou vereador removido).
- O botão de edição aponta para `editar/requerimentos/{id}` (relativo) e
  respeita as flags `edit`/`delete` vindas de `struct_views()`.

## 6. Exportações

O módulo **não** possui exportação (Excel/PDF/CSV). O único “download” é o
arquivo anexado ao requerimento, servido como link para o `path` do
`archives`.

## 7. O que o usuário vê — resumo

**Cidadão**: página com menu lateral de subcategorias, três filtros, lista
paginada de 12 itens (data + título + resumo) e, ao clicar, a página do
requerimento com o texto integral, a data/hora e a lista de arquivos para
download (com o PDF embutido em `iframe`).

**Operador**: tabela no AdminLTE com título, requerente, categoria e data;
formulário com categoria, título, data/hora, requerente, texto rico e anexos;
tela separada para gerenciar as subcategorias (com arrastar-e-soltar para
reordenar).
