# Sessões da Câmara Municipal — Views e UX

> Módulo `sessoes` na `v6`.
> Portal: Bootstrap 4 em `v6/resources/views/portal/`.
> CMS: AdminLTE 1.22 / Bootstrap 3 em `v6/resources/views/prefecture/`.

---

## 1. Inventário de views

### 1.1 Portal — listagem (`index`)

| Layout | Arquivo |
|---|---|
| modelo1 | `v6/resources/views/portal/layouts/modelo1/sessoes/index.blade.php` |
| modelo2 | `v6/resources/views/portal/layouts/modelo2/sessoes/index.blade.php` |
| modelo3 | `v6/resources/views/portal/layouts/modelo3/sessoes/index.blade.php` |
| modelo4 | `v6/resources/views/portal/layouts/modelo4/sessoes/index.blade.php` |
| modelo5 | `v6/resources/views/portal/layouts/modelo5/sessoes/index.blade.php` |
| modelo6 | `v6/resources/views/portal/layouts/modelo6/sessoes/index.blade.php` |

**Não existem** as pastas `sessoes/` em `default/` nem em `newsbit/`. Como
`getTemplateActived()` aborta 404 quando o layout ativo não tem
`home/index.blade.php` (`v6/app/Http/Controllers/Admin/Controller.php:39-49`),
e apenas `modelo1`..`modelo6` têm `home/index.blade.php`, o tema `default` e o
`newsbit` resultam em **404 `view_inexistente`** para `/sessoes`.

### 1.2 Portal — detalhe (`show`)

| Layout | Arquivo |
|---|---|
| modelo1 | `v6/resources/views/portal/layouts/modelo1/sessoes/show.blade.php` |
| modelo2 | `v6/resources/views/portal/layouts/modelo2/sessoes/show.blade.php` |
| modelo3 | `v6/resources/views/portal/layouts/modelo3/sessoes/show.blade.php` |
| modelo4 | `v6/resources/views/portal/layouts/modelo4/sessoes/show.blade.php` |
| modelo5 | `v6/resources/views/portal/layouts/modelo5/sessoes/show.blade.php` |
| modelo6 | `v6/resources/views/portal/layouts/modelo6/sessoes/show.blade.php` |

### 1.3 Portal — widget da home

| Arquivo | Papel |
|---|---|
| `v6/resources/views/portal/layouts/modelo5/home/sessoes/sessoes-list.blade.php` | Bloco "Sessões" da home do `modelo5`, com os 4 registros mais recentes. |
| `v6/app/Http/Controllers/Portal/PortalController.php:145-150` | Monta `$sessions` (conteúdos da categoria `sessoes`, `orderBy('publication_date','desc')`, `limit(4)`) e o passa à view na linha 237. |

### 1.4 Portal — parciais reaproveitados

| Arquivo | Papel |
|---|---|
| `v6/resources/views/portal/components/galerias/index.blade.php` | Galeria de fotos (lightGallery). Filtra `$archives` por extensão e ignora os que têm `highlight` ou `view_pdf`. |
| `v6/resources/views/portal/statics/video.blade.php` | `<iframe>` do vídeo, usando a variável `$linkVideo`. |

### 1.5 CMS

| Arquivo | Papel |
|---|---|
| `v6/resources/views/prefecture/contents/sessoes/index.blade.php` | Listagem (DataTable) com botões "Cadastrar Sessão" e exclusão em lote. |
| `v6/resources/views/prefecture/contents/sessoes/create.blade.php` | Wrapper do formulário de criação (título "Cadastrar Sessão"). |
| `v6/resources/views/prefecture/contents/sessoes/edit.blade.php` | Wrapper do formulário de edição (título "Editar Sessão"), com upload de galeria. |
| `v6/resources/views/prefecture/contents/sessoes/form.blade.php` | Formulário compartilhado entre create e edit. |
| `v6/resources/views/prefecture/contents/components/galerias/index.blade.php` | Aba de galeria (somente quando `galery == 1`). |
| `v6/resources/views/prefecture/contents/components/preview/index.blade.php` | Modal de pré-visualização do conteúdo. |
| `v6/resources/views/prefecture/contents/components/editor-de-imagem/index.blade.php` | Editor/crop de imagem. |

## 2. O que o cidadão vê

### 2.1 Listagem — `modelo5` (referência)

Estrutura do card (`v6/resources/views/portal/layouts/modelo5/sessoes/index.blade.php`):

```blade
<a href="{{route('sessoes.show', $session->slug)}}" class="row m-0 p-1">
    <div class="news-items col-md-3 col-lg-4 col-sm-3 col-3" style="height:125px; background: url('{{getMedFile( $session->present()->getArchiveContentHighlightOrThumbnail(1, 1) )}}') no-repeat center center;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;"></div>
    <div class="col-md-9 col-lg-8 col-sm-9 col-9 text-secondary ">
        <p class="badge bg-color-4 txt-color-dinamic-4 p-2 mb-1">{{date('d.m.Y', strtotime($session->publication_date))}}</p>
        <h6 title="{{$session->title}}">{{str_limit($session->title, 50)}}</h6>
    </div>
</a>
```

Elementos visíveis:

| Elemento | Origem |
|---|---|
| Miniatura 125 px | `getArchiveContentHighlightOrThumbnail(1, 1)` — imagem de destaque (`highlight = 1`) ou, na ausência, a thumbnail do vídeo (YouTube/Vimeo) gerada pelo presenter; sem nenhuma, `storage/img/exemplos/sem-imagem.png`. |
| Badge de data | `publication_date` formatado `d.m.Y`. |
| Título truncado | `str_limit($session->title, 50)`. |
| Paginação | `$sessions->links()` (12 por página). |
| Estado vazio | `Nenhuma registro encontrado.` |

### 2.2 Detalhe — `modelo5` (referência)

| Bloco | Condição | Conteúdo |
|---|---|---|
| Meta tags OG | sempre | `og:type = article`; `og:image` e `og:description` só se houver destaque. |
| Breadcrumb | sempre | `Breadcrumbs::view('portal.layouts.modelo5.layout.breadcrumb', 'content', $session)`. |
| Cabeçalho | sempre | Título da sessão. |
| Linha de metadados | sempre | `Publicado em DD/MM/YYYY às HH:MM` (mais `source` e `author`, se preenchidos). |
| Compartilhamento | sempre | `addthis_inline_share_toolbox`. |
| Imagem de destaque | se `archives` tiver `highlight = 1` | `<img>` com `data-webp` para a versão WebP. |
| Texto | sempre | `{!! $session->text !!}` — HTML bruto do Summernote. |
| Galeria | **se `$session->galery === true`** | `@include('portal.components.galerias.index')`. |
| Vídeo | se `isset($session->linksUnique->link)` | `@include("portal.statics.video")`. |

Trecho real do detalhe (`v6/resources/views/portal/layouts/modelo5/sessoes/show.blade.php:36-43`):

```blade
@if ($session->galery === true)
    @include('portal.components.galerias.index')
@endif

@if(isset($session->linksUnique->link))
    @php $content = $session @endphp
    @include("portal.statics.video")
@endif
```

O partial de vídeo (`v6/resources/views/portal/statics/video.blade.php`) é
literalmente:

```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>
```

### 2.3 Widget da home (`modelo5`)

`v6/resources/views/portal/layouts/modelo5/home/sessoes/sessoes-list.blade.php`:
seção com título "Sessões", até 4 cards com miniatura de 220 px, badge de data,
título com clamp de 4 linhas e o link "Mais conteúdos de sessões…" para
`route('sessoes.index')`. A seção só é renderizada se `$sessions` não for vazio.

**Observação:** o bloco usa `$sessions`, que na home vem do
`PortalController` **sem** os filtros `status = true` e
`publication_date <= now`. Uma sessão em rascunho com data passada **aparece
na home**, mas **não** em `/sessoes`.

## 3. O que o operador vê (CMS)

### 3.1 Listagem

`v6/resources/views/prefecture/contents/sessoes/index.blade.php`:

| Elemento | Condição |
|---|---|
| Título "Lista de Sessões" | sempre (`$title` = "Sessões"). |
| Botão "Cadastrar Sessão" | `has_access(['sessoes.create'])`. |
| Exclusão em lote | `has_access(['sessoes.delete'])` — componente `prefecture.components.deleteByLoteSubmitButton` com a rota `ajax.generic.content.deleteAll`. |
| Cabeçalho da tabela | ID, Título, Status, Data de publicação, Ações. |
| Tabela | `#tabela_sessoes`, preenchida por DataTables server-side. |

### 3.2 Colunas e ações (JavaScript)

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

| Coluna | Campo da view | Render |
|---|---|---|
| ID | `value` | Injetado automaticamente por `runDatatables` (`v6/public/assets/js/custom/utilities/table.js:9-24`). |
| Título | `titulo` | `<div class='vertical-middle' style='padding:8px'>`. |
| Status | `status_name_sessoes` | `<div class='font-weight-bold'>` — "Publicado" ou "Rascunho". |
| Data de publicação | `data_publicacao` | `<div class="text-center">` — já vem em `DD/MM/YYYY às HH24:MI`. |
| Ações | `data: null` | Botão Facebook, botão galeria, botão editar/excluir. |

Código real das ações:

```javascript
render: function (data) {
    return `<div class='flex justify-center'>`
        + createSingleButton(data.id, `https://www.facebook.com/sharer/sharer.php?u=${window.location.origin}/sessoes/${data.slug}`, true, 'fa fa-facebook text-primary', 'Compartilhar no facebook', '_blank')
        + createGaleryButton(`editar/sessoes/${data.id}#galeria`, data.galeria, data.edit)
        + createBTN(data.id, `editar/sessoes/${data.id}`, data.edit, data.delete)
        + `</div>`;
}
```

| Botão | Destino | Condição |
|---|---|---|
| Compartilhar no Facebook | `https://www.facebook.com/sharer/sharer.php?u={origin}/sessoes/{slug}` (nova aba) | sempre |
| Galeria | `editar/sessoes/{id}#galeria` (abre a aba de galeria) | desabilitado se `data.galeria` for falso; oculto se `data.edit` for falso |
| Editar / Excluir | `editar/sessoes/{id}` | conforme `data.edit` / `data.delete` |

A listagem também expõe **exportação Excel e PDF** (botões padrão de
`runDatatables`), com as colunas visíveis exceto a de ações.

### 3.3 Formulário (create e edit)

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

| Campo | Componente | Observações |
|---|---|---|
| `category_slug` | hidden | Fixo `sessoes`. |
| `category_contents_id` | hidden | Id da categoria raiz. |
| `title` | `components.inputs.input` | Obrigatório, `maxlen` 150. |
| `publication_date` | `components.inputs.date` | Máscara `br-data-tempo`, formato `d/m/Y H:i`, valor inicial = agora. |
| `text` | `components.inputs.textarea` | Editor Summernote, obrigatório. |
| `link` | `components.inputs.input` | Rótulo "Video (Youtube)"; aceita URL completa ou apenas o ID (`Exemplo: https://www.youtube.com/watch?v=YbJOTdZBX1g ou YbJOTdZBX1g`); `maxlen` 2999. |
| Imagem principal | `components.inputs.image` | Alimenta `archive_highlight`. |
| `galery` | `components.inputs.checkbox` | "Ativar Galeria". |
| `status` | `components.inputs.checkbox` | "Publicar" — desmarcado = rascunho. |

Comportamento do vídeo no CMS: o campo `link` é gravado na tabela `links` pelo
`store`/`update`, sempre passes por `normalizeVideoLink()`
(`v6/app/Helpers/functions.php:683`) e `sanitizeLink()`
(`v6/app/Http/Controllers/Prefecture/ContentController.php:595`).

### 3.4 Aba de galeria

Quando `contents.galery == 1`, o `form.blade.php` acrescenta a aba "Galeria" e
inclui `prefecture.contents.components.galerias.index`. Na edição,
`v6/resources/views/prefecture/contents/sessoes/edit.blade.php` inicializa a
galeria por JavaScript:

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

e abre a aba correspondente quando a URL traz a hash:

```javascript
var locHash = location.hash;
if (locHash != '') {
    var locHash = '#myTab a[href="' + locHash + '"]';
    $(locHash).tab('show');
}
```

É esse pequeno script que faz o botão de galeria da listagem (âncora
`#galeria`) cair na aba correta.

## 4. JavaScript e assets

| Arquivo | Papel |
|---|---|
| `v6/public/assets/js/custom/contents/sessoes/datatables.js` | Colunas e ações da listagem do CMS. |
| `v6/public/assets/js/custom/utilities/table.js` | `runDatatables`, `createBTN`, `createSingleButton`, `createGaleryButton`, `createDeleteAllButton`; monta o AJAX em `/cms/datatable/{url}` (linha 183). |
| `v6/public/assets/js/custom/utilities/deleteAll.js` | Envio do formulário de exclusão em lote. |
| `v6/public/assets/js/custom/utilities/helpers/add_archive.js` | Upload de arquivos (destaque/galeria). |
| `v6/public/assets/js/custom/utilities/helpers/preview.js` | Pré-visualização do conteúdo. |
| `v6/public/assets/js/custom/utilities/helpers/select-image.js` | Seleção de imagem para o editor. |
| `v6/public/assets/js/custom/utilities/helpers/editor-de-imagem.js` | Editor de imagem. |
| `v6/public/assets/js/modulos/cropper-min.js` | Recorte de imagem. |
| `v6/public/assets/js/custom/utilities/richtext/summernote.js` | Inicialização do Summernote. |

**Arquivo referenciado e ausente:**
`v6/resources/views/prefecture/contents/sessoes/form.blade.php` carrega
`assets/js/custom/contents/sessoes/edit.js`, mas o diretório
`v6/public/assets/js/custom/contents/sessoes/` contém **apenas**
`datatables.js`. O `404` no console não quebra a tela, mas a lógica de
pré-visualização específica de sessões não é carregada.

## 5. Diferenças entre layouts do portal

| Aspecto | modelo1 | modelo2 | modelo3 | modelo4 | modelo5 | modelo6 |
|---|---|---|---|---|---|---|
| Link do card | `conteudo.portal.show` | `conteudo.portal.show` | `sessoes.show` | `sessoes.show` | `sessoes.show` | `sessoes.show` |
| Breadcrumb | `Breadcrumbs::render` | `Breadcrumbs::render` | `Breadcrumbs::view` | `Breadcrumbs::view` | `Breadcrumbs::view` | `Breadcrumbs::view` |
| Condição de exibição | — | — | `getStatusDisplayNews()['displayNews'] == 0` | idem | — | — |
| Miniatura | `archives->where('highlight',1)` com fallback fixo | idem | `getArchiveContentHighlightOrThumbnail(1,1)` | idem | idem | idem |
| Paginação | `$sessions->links()` | idem | `links('vendor.pagination.modelo3')` | `$sessions->links()` | `$sessions->links()` | — |

Duas consequências relevantes:

1. **Duas URLs públicas** para a mesma sessão, dependendo do tema.
2. **O `modelo3` e o `modelo4` escondem a lista de sessões** quando a
   configuração `noticias` (`getStatusDisplayNews()`) estiver desligada — um
   Acoplamento indevido entre o módulo de Sessões e o de Notícias.

## 6. Acessibilidade e pontos de atenção de UX

| Ponto | Situação |
|---|---|
| Idioma | Todo o texto do módulo (títulos, botões, mensagens) está em português; não há i18n no Blade das sessões. |
| Data | Formatada em pt-BR no portal (`d.m.Y` na listagem, `d/m/Y às H:i` no detalhe) e em `DD/MM/YYYY às HH24:MI` na view do CMS. |
| Responsividade | Cards em colunas `col-lg-6`/`col-md-3` (modelo1, 2, 5) e `col-md-3` (modelo3); o `modelo4` usa `col-10 col-md-6`. |
| Galeria | Depende de `lightGallery` (scripts incluídos no partial), com lightbox e legenda desabilitada (`getCaptionFromTitleOrAlt: false`). |
| Vídeo | iframe responsivo 16:9; URLs de Instagram/YouTube Shorts podem não se comportar bem no ratio fixo. |
| Sem imagem | Fallback `storage/img/exemplos/sem-imagem.png` nos cards do portal e `storage/img/exemplos/sem-imagem-min.png` na imagem de destaque do CMS. |
