# A Instituição — Views e UX

> Versão analisada: `v6`. Cada bloco Blade citado é cópia literal do repositório.

---

## 1. Visão geral das telas

| Contexto | Arquivo | O que é |
|---|---|---|
| CMS — listagem | `v6/resources/views/prefecture/contents/a-instituicao/index.blade.php` | Tabela "Lista de conteudos da Instituição" com DataTable. |
| CMS — cadastro | `v6/resources/views/prefecture/contents/a-instituicao/create.blade.php` | Wrapper do formulário de criação. |
| CMS — edição | `v6/resources/views/prefecture/contents/a-instituicao/edit.blade.php` | Wrapper do formulário de edição. |
| CMS — formulário | `v6/resources/views/prefecture/contents/a-instituicao/form.blade.php` | Campos comuns a create/edit. |
| Portal — página | `v6/resources/views/portal/layouts/modelo1/informacoes-instituicao/a-instituicao/show.blade.php` (e o equivalente em modelo2, modelo3, modelo4, modelo5, modelo6) | Slot-filling do componente do modelo. |
| Portal — componente | `v6/resources/views/portal/layouts/modelo1/informacoes-instituicao/components/components-a-prefeitura/index.blade.php` (e o equivalente em modelo2, modelo3, modelo4, modelo5, modelo6) | Corpo visual da página. |

## 2. CMS — tela de listagem

Estrutura (AdminLTE 1.22 / Bootstrap 3):

```blade
{{-- v6/resources/views/prefecture/contents/a-instituicao/index.blade.php --}}
@extends('admin.layouts.app')

@section('content')
    <div class="row">
        <div class="col-xs-12">
            <div class="box box-success">
                <div class="box-header with-border">
                    <h3 class="box-title align-middle">Lista de conteudos da Instituição</h3>
                    <div class="float-right">
                        @if (has_access(['a-instituicao.create']))
                            <a href="cadastrar/a-instituicao" class="btn btn-success font-weight-bold btn-sm" role="button"><i
                                    class="fa fa-plus"></i> Cadastrar Conteúdo</a>
                        @endif
                    </div>
                    @if (has_access(['a-instituicao.delete']))
                        <div class="float-right pr-3">
                            @component('prefecture.components.deleteByLoteSubmitButton', ['route' => 'ajax.generic.content.deleteAll'])
                            @endcomponent
                        </div>
                    @endif
                </div>
                <div class="box-body table-responsive">
                    <table id="tabela_a_instituicao" class="table table-striped w-100">
                        <thead>
                            <tr>
                                <th>
                                    @if (has_access(['a-instituicao.delete']))
                                        @component('prefecture.components.deleteByLoteMasterCheckbox')
                                        @endcomponent
                                    @endif
                                </th>
                                <th>ID</th>
                                <th width="75%">Título</th>
                                <th class="column-actions text-center" width="20%">Ações</th>
                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
@endsection
```

| Elemento | Comportamento |
|---|---|
| Botão "Cadastrar Conteúdo" | Aparece apenas com `a-instituicao.create`; navega para `cadastrar/a-instituicao` (link relativo, resolvido pelo prefixo `/cms/conteudos/`). |
| "Excluir selecionados" | Componente `prefecture.components.deleteByLoteSubmitButton`, `route = ajax.generic.content.deleteAll`; exige `a-instituicao.delete`. |
| Master checkbox | `prefecture.components.deleteByLoteMasterCheckbox`; exige `a-instituicao.delete`. |
| Tabela | `id="tabela_a_instituicao"` — o `id` é o contrato com o JS (`runDatatables('a_instituicao', ...)` monta `#tabela_a_instituicao`). |
| Coluna "Ações" | Classe `column-actions` — excluída das exportações Excel/PDF. |

Scripts carregados no rodapé:

```blade
@section('other-scripts')
    @parent
    <script src="{{ url('assets/js/custom/utilities/deleteAll.js') }}"></script>
    <script src="{{ asset('assets/js/custom/contents/a-instituicao/datatable.js') }}"></script>
@endsection
```

## 3. CMS — grade (JavaScript)

```js
// v6/public/assets/js/custom/contents/a-instituicao/datatable.js
let collums = [
    {
        data: 'titulo',
        render: function (data) {
            return `<div class="p-10-0">${data}</div>`;
        }
    },
    {
        data: null,
        name: "actions",
        orderable: false,
        render: function (data) {
            return `<div class='flex justify-center vertical-middle'>`
                + createSingleButton(data.id + 'Link',`/a-instituicao/${data.slug}`, true, 'glyphicon glyphicon-link text-primary form_link', 'Acessar Instituto', '_blank')
                + createGaleryButton(`editar/a-instituicao/${data.id}#galeria`, data.galeria, data.edit)
                + createBTN(data.id, 'editar/a-instituicao/' + data.id, data.edit, data.delete)
                + `</div>`;
        }
    }
];

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

| Botão | Destino | Condição |
|---|---|---|
| "Acessar Instituto" (link) | `/a-instituicao/${data.slug}` em nova aba | sempre (usa o `slug` vindo da view SQL) |
| Galeria | `editar/a-instituicao/<id>#galeria` | depende de `data.galeria` |
| Editar | `editar/a-instituicao/<id>` | depende de `data.edit` (permissão) |
| Excluir | ação do `createBTN` | depende de `data.delete` (permissão) |

O helper `runDatatables` está em `v6/public/assets/js/custom/utilities/table.js:3`:

```js
// v6/public/assets/js/custom/utilities/table.js:181-185
ajax: {
    contentType: 'application/json',
    url: `/cms/datatable/${url}`,
    type: 'GET'
},
```

Pontos relevantes:

- **`serverSide` não é configurado** → o DataTable pagina, filtra e ordena **no
  cliente**. O endpoint devolve a view SQL inteira em JSON.
- A exportação Excel/PDF usa `exportOptions: { columns: ':visible:not(.column-actions)' }`
  — a coluna "Ações" nunca é exportada.
- O título da exportação é
  `Prefeitura de ${cidadePrefeitura} - ${new Date().toLocaleDateString('pt-BR')}`,
  com `cidadePrefeitura` lido de `.user-panel .info p` (nome da prefeitura).
- `stateSave: true` — o estado (página, busca, ordenação) persiste no `localStorage`.

## 4. CMS — formulário

`v6/resources/views/prefecture/contents/a-instituicao/form.blade.php`:

```blade
@if(isset($content) && ($content->galery == 1))
    <li class="float-left float-sm-right tabs"><a data-toggle="tab" href="#galeria">Galeria</a></li>
@endif
<li class="active float-left float-sm-right tabs"><a data-toggle="tab" href="#home">A Instituição</a></li>
</ul>
<input type="hidden" name="category_contents_id" value="{{$categoryContent->id}}">
```

### 4.1 Campo "Link Permanente" (bug)

```blade
{{-- v6/resources/views/prefecture/contents/a-instituicao/form.blade.php:13-34 --}}
@if (isset($content))
    <div class="form-group has-feedback {{ $errors->has('slug') ? 'has-error' : '' }} custom-flex-center col-md-12 px-0">
        <label for="name" class="col-md-12 pl-0 pb-0">
            Link Permanente
            <strong class="text-red " style="bottom: 8px; font-size: 7px; position: relative;">
                <i class="fa fa-asterisk"></i>
            </strong>
        </label>

        <div class="mr-2">{{ url('a-cidade') }}/</div>

        @include('components.inputs.input', [
            'size' => 4,
            'id' => 'slug',
            'data' => isset($content) ? $content->slug : old('slug'),
        ])

        <div class="mr-2">
            <a href="{{ route('a-cidade.index', $content->slug) }}" target="_blank">
                Acessar <i class="fa fa-external-link fa-1x"></i>
            </a>
        </div>
    </div>
@endif
```

**Bug confirmado:** as linhas 21 e 30 apontam para `a-cidade`. O formulário de
A Cidade (`v6/resources/views/prefecture/contents/a-cidade/form.blade.php:21,30`)
é idêntico nesses trechos — é copy/paste. O operador vê o prefixo
`https://<dominio>/a-cidade/` e um link "Acessar" que abre a página de A Cidade
(o slug pode nem existir lá). O valor gravado, porém, está correto: o campo
`slug` é o do próprio conteúdo.

| Campo | Tipo | Origem | Observações |
|---|---|---|---|
| `category_contents_id` | hidden | `$categoryContent->id` (6) | Injetado por `returnData()`. |
| `title` | texto | `$content->title` | `maxlength=120`, obrigatório. |
| `text` | textarea Summernote | `$content->text` | `class="summernote"`; o HTML é renderizado com `{!! !!}` no portal. |
| `link` | texto | `$content->linksUnique['link']` | Vídeo do YouTube (link ou ID). `maxlength=200`. |
| Imagem Principal | `components.inputs.image` | `$content`, `$destaque` | Alimenta `archive_highlight`. |
| `galery` | checkbox | `$content->galery` | Marca "1" quando ativo; habilita a aba "Galeria". |
| Galeria | `prefecture.contents.components.galerias.index` | `$content` | Só aparece quando `galery == 1`. |

A aba "Galeria" é habilitada por JavaScript no `create.blade.php` (o botão muda
de "Salvar" para "Salvar e ir para galeria") e o `edit.blade.php` abre a aba
correspondente pelo hash da URL:

```js
// v6/resources/views/prefecture/contents/a-instituicao/edit.blade.php
var locHash = location.hash;
if (locHash != '') {
    var locHash = '#myTab a[href="' + locHash + '"]';
    $(locHash).tab('show');
}
```

O `edit.blade.php` também inicializa a galeria:

```js
chargeGallery({{ $content->id }}, 'contents_id');
setupGallery({{ $content->id }}, 'content');
```

e expõe a função `deleteHighlightArchive(archive_id)` que faz `POST` em
`route('ajax.delete.files')` para apagar a imagem de destaque.

## 5. Portal — página pública

### 5.1 Camada 1: `show.blade.php` do modelo

```blade
{{-- v6/resources/views/portal/layouts/modelo1/informacoes-instituicao/a-instituicao/show.blade.php --}}
@component('portal.layouts.modelo1.informacoes-instituicao.components.components-a-prefeitura.index', [
    'sidenews' => isset($sidenews) ? $sidenews : null,
    'eventsList' => isset($eventsList) ? $eventsList : null,
])
    @slot('title')
        {{ isset($contentNew->title) ? $contentNew->title : '' }}
    @endslot

    @slot('text')
        {!! isset($contentNew->text) ? $contentNew->text : '' !!}
    @endslot

    @section('side-bar')
        <div class="col-xl-3 col-lg-3 col-sm-12 ml-auto pr-lg-0 pl-lg-0">
            <div class="container-fluid px-md-0 mr-0 row justify-content-sm-center justify-content-md-end justify-content-xs-center mx-auto">
                @include("portal.layouts.modelo1.layout.sidebar.layout")
            </div>
        </div>
    @endsection
@endcomponent
```

O trecho acima é parcial: os slots `breadcrumb`, `imageHighlight`, `gallery`,
`noticias` e `video` foram omitidos e estão documentados na tabela abaixo.

Slots e o que eles controlam:

| Slot | Origem do dado | Efeito na tela |
|---|---|---|
| `breadcrumb` | `$contentNew->categoryContents->slug` | Breadcrumb renderizado pelo `Breadcrumbs::view()` do modelo. |
| `title` | `$contentNew->title` | Cabeçalho "h4 text-uppercase". |
| `imageHighlight` | `$contentNew->archiveHighlight->path` | Imagem principal (com `getLargFile()` e variante `.webp`). Só aparece se `file_exist()`. |
| `text` | `$contentNew->text` | Corpo HTML **sem escape** (`{!! !!}`). |
| `gallery` | `$contentNew->archiveGallery` | Bloco lightGallery, apenas se `galery` e galeria não vazia. |
| `noticias` | `$news_secretary` (coleção paginada) | Bloco "Últimas notícias". Só se a coleção não for vazia. |
| `video` | `$contentNew->linksUnique->link` | `<iframe>` do YouTube. |
| `side-bar` | — | Coluna lateral. |

### 5.2 Camada 2: o componente de cada modelo

`v6/resources/views/portal/layouts/modelo1/informacoes-instituicao/components/components-a-prefeitura/index.blade.php`
(e o equivalente em modelo2, modelo3, modelo4, modelo5, modelo6) — o arquivo é
praticamente idêntico nos 6 modelos; muda o `@extends` e o include da lateral:

```blade
@if (!empty($text))
    <p class="h4 text-uppercase txt-color-2 font-or">{{ $title }}</p>
    <i class="border-title d-block bg-color-1 mb-4"></i>
    {{-- bloco addtoany com 5 botões: whatsapp, facebook, copy_link, email, printfriendly --}}
    <div class="col-lg-8 col-12 col-sm-12 mx-auto mb-4 text-center">
        @if (isset($imageHighlight) && file_exist($imageHighlight))
            <img class="img-fluid"
                src="{{ isset($imageHighlight) ? url(getLargFile($imageHighlight)) : '' }}"
                data-webp='{{ isset($imageHighlight) ? url($imageHighlight) : '' }}.webp'
                alt="{{ $title }}" title="{{ $title }}">
        @endif
    </div>
    <p class="text-secondary text-content"> {{ $text }}</p>
    {{ isset($gallery) ? $gallery : '' }}
    {{ isset($noticias) ? $noticias : '' }}
    {{ isset($video) ? $video : '' }}
@else
    <div class="text-center  p-4 ">
        <h4 style="color: #ef6464;">CONTEÚDO NÃO ENCONTRADO.</h4>
```

Quando `$text` está vazio, a página mostra "CONTEÚDO NÃO ENCONTRADO." — fallback
visual para conteúdo inexistente ou sem corpo.

## 6. Blocos auxiliares

**Notícias paginadas** — `v6/resources/views/portal/components/secretarias/noticias.blade.php`:

```blade
<div class="card p-3 news-details mt-4">
    <h6 class="text-secondary mb-4">Últimas notícias</h6>
    <ul class="list-unstyled more-news row">
        @foreach($news_secretary as $newList)
        <li class="col-md-6 mb-2 mx-sm-auto m-md-0 mb-md-3">
            <a href="{{ route('noticia.show', $newList->slug) }}" class="row m-0 p-1">
        </li>
        @endforeach
    </ul>
    {{$news_secretary->links()}}
</div>
```

`{{$news_secretary->links()}}` renderiza o paginador do Laravel — a paginação é
**do servidor** (`paginate(6)`).

**Galeria** — `v6/resources/views/portal/components/galerias/index.blade.php`:
filtra por extensão (imagens), monta `<ul id="lightgallery">` e inicializa:

```js
$('#lightgallery').lightGallery({
    getCaptionFromTitleOrAlt: false,
})
```

**Vídeo** — `v6/resources/views/portal/statics/video.blade.php`:

```blade
<div class=" card p-3 my-4">
    <div class="embed-responsive embed-responsive-16by9">
        <iframe class="embed-responsive-item iframe-video w-100" src="{{ $linkVideo }}" allowfullscreen></iframe>
    </div>
</div>
```

O `$linkVideo` é produzido por `embedVideo()` (`v6/app/Helpers/functions.php:607`).

**Lateral** — `v6/resources/views/portal/layouts/modelo1/layout/sidebar/layout.blade.php`:
transmissão, banners, clima, enquetes, mais populares, VLibras e newsletter.

**Notícias do órgão** — `v6/resources/views/portal/layouts/modelo1/layout/sidebar/orgaosNews.blade.php`:
bloco "Notícias de {{ $title }}", condicionado por
`getStatusDisplayNews()['displayNews'] == 0` e pelo `isset($sidenews)`. É este
arquivo que recebe `$modulos` do `ModulosServiceProvider`.

## 7. Divergências entre os modelos

| Aspecto | modelo1 | modelo2 | modelo3 | modelo4 | modelo5 | modelo6 |
|---|---|---|---|---|---|---|
| Componente próprio | sim | sim | sim | sim | sim | sim |
| `sidebar/orgaosNews` próprio | sim | **não** (inclui o do modelo1) | sim | sim | sim | sim |
| `sidebar/layout` próprio | sim | sim | sim | sim | sim | sim |

O modelo2 **não possui** o arquivo `orgaosNews.blade.php` dentro de `v6/resources/views/portal/layouts/modelo2/layout/sidebar/`;
por isso seu componente inclui o do modelo1:

```blade
{{-- v6/resources/views/portal/layouts/modelo2/informacoes-instituicao/components/components-a-prefeitura/index.blade.php:97 --}}
@include('portal.layouts.modelo1.layout.sidebar.orgaosNews')
```

Consequência: ao trocar o layout ativo para `modelo2`, o bloco "Notícias de
&lt;título&gt;" usa o componente do modelo1 (mesmo markup, mas sem o
`view()->composer` do modelo2 — o que é irrelevante hoje, já que o composer do
modelo2 aponta para um arquivo inexistente).

O modelo6 é o único que inclui a lateral **dentro** do componente
(`@include('portal.layouts.modelo6.layout.sidebar.layout')` na linha 102) em vez
de fazê-lo via `@section('side-bar')` no `show.blade.php`.

## 8. Layouts inalcançáveis

`getTemplateActived()` valida `portal.layouts.<layout>.home.index`. Só
`modelo1..modelo6` têm `home/index.blade.php`. Portanto:

- `default` e `newsbit` resultam em `abort(404, 'view_inexistente')` e ainda
  marcam `configurations.pagina_em_manutencao = 1`.
- Existem views órfãs de A Instituição nesses layouts:
  `v6/resources/views/portal/layouts/default/pages/contents/a-prefeitura/show.blade.php`
  (nome antigo preservado) e
  `v6/resources/views/portal/layouts/newsbit/informacoes-instituicao/components/components-a-prefeitura/index.blade.php`.
  Nenhuma é alcançável: o controller sempre monta `informacoes-instituicao.a-instituicao.show`.

## 9. Exportações

| Exportação | Onde | Como é gerada |
|---|---|---|
| Excel (`.xlsx`) | Grade do CMS | DataTables Buttons `extend: 'excel'`, `exportOptions.columns = ':visible:not(.column-actions)'`, gerado **no cliente** (sem passar pelo PHP). |
| PDF | Grade do CMS | DataTables Buttons `extend: 'pdf'`, `orientation: 'landscape'`, `pageSize: 'A4'`, larguras de coluna calculadas por `customize`. |
| CSV | — | Não há exportador CSV configurado neste módulo. |

Não há exportação de relatório próprio do módulo.
