# Transmissões — Views e UX

> Versão analisada: `v6`. Módulo **novo na v6**.

## 1. Onde o cidadão encontra a transmissão

**Não existe página dedicada.** O player é inserido como *widget* na coluna
lateral (ou no topo da barra de banners) de **todas** as páginas do portal,
conforme o tema ativo (`Configuration` key `layout`, resolvido por
`getTemplateActived()` em `v6/app/Http/Controllers/Admin/Controller.php:39-49`).

### 1.1 Mapa tema → arquivo

| Tema | Arquivo do widget | Como entra na página | Observação |
|---|---|---|---|
| `modelo1` | `v6/resources/views/portal/layouts/modelo1/layout/sidebar/transmissao.blade.php` | `@include('portal.layouts.modelo1.layout.sidebar.transmissao')` em `v6/resources/views/portal/layouts/modelo1/layout/sidebar/layout.blade.php:4` | Card com borda, `card-body text-center`. |
| `modelo2` | `v6/resources/views/portal/layouts/modelo2/layout/sidebar/transmissao.blade.php` | `@include('portal.layouts.modelo2.layout.sidebar.transmissao')` em `v6/resources/views/portal/layouts/modelo2/layout/sidebar/layout.blade.php:4` | Idêntico ao modelo1. |
| `modelo3` | `v6/resources/views/portal/layouts/modelo3/layout/sidebar/banners.blade.php` | O próprio `banners.blade.php` | Card transparente (`background: transparent; box-shadow: none;`), acima dos banners. |
| `modelo4` | `v6/resources/views/portal/layouts/modelo4/layout/sidebar/banners.blade.php` | O próprio `banners.blade.php` | Mesma lógica. |
| `modelo5` | `v6/resources/views/portal/layouts/modelo5/home/news/sidebar/banners.blade.php` | O próprio `banners.blade.php` | Mesma lógica. |
| `modelo6` | `v6/resources/views/portal/layouts/modelo6/layout/sidebar/banners.blade.php` | O próprio `banners.blade.php` | Mesma lógica. |
| `default` | — | — | O tema `default` **não possui** widget de transmissão. |

> **Código morto:** `v6/resources/views/portal/layouts/modelo1/layout/sidebar/banners.blade.php:13-15`
> calcula `$transmissao` mas **nunca o renderiza** — é resíduo de uma versão
> anterior. O modelo1 usa o arquivo `transmissao.blade.php` separado.

### 1.2 Estrutura do widget (modelo1, `transmissao.blade.php`)

```blade
@if (isset($modulos['transmissoes']) && $modulos['transmissoes'])
    @inject('content', 'App\Prefecture\Content')

    @php
        $transmissao = $content->whereHas('categoryContents', function ($query) {
                $query->where('slug', 'transmissoes');
            })->where('status', true)->first();
    @endphp

    @if (!empty($transmissao))
        <div class="card mx-auto mb-3" style="width: 100%">
            <div class="card-body text-center">
```

Trechos idênticos (a menos da classe do card) existem nos 4 arquivos
`banners.blade.php`.

### 1.3 Anatomia do player renderizado

Quando a URL é reconhecida, o widget emite HTML puro via `echo`:

```html
<div class="piscar bg-danger rounded">
  <small class=" text-light"><i class="fas fa-video mr-1"></i> Transmissão</small>
</div>
<div class="mt-3 embed-responsive embed-responsive-16by9">
  <iframe class="embed-responsive-item" style="width: 100%;" src="<link>" allowfullscreen></iframe>
</div>
```

| Elemento | Comportamento |
|---|---|
| Faixa "Transmissão" | Classe `piscar` (animação de piscância) com fundo vermelho (`bg-danger`) — sinaliza "ao vivo". |
| Player | `iframe` do YouTube em `embed-responsive-16by9` (Bootstrap 4), `allowfullscreen`. |
| Sem controles próprios | O playback é o do YouTube (com os controles nativos do embed). |

### 1.4 Comportamento quando a URL não é reconhecível

```html
<div class="alert alert-danger" role="alert">Transmissão não disponível</div>
```

Isso ocorre quando o parser não consegue extrair um host válido (índice 2 da
URL explodida por `/`) nem um ID de vídeo. Exemplo típico: o operador informou
apenas o ID do YouTube (ver `08-GAPS-E-DIVIDAS.md`).

## 2. O parser de URL (idêntico nos 6 arquivos)

```php
$linkValido = false;

$info = parse_str(parse_url($transmissao->linksUnique->link, PHP_URL_QUERY), $array);
$url_ = explode('/', $transmissao->linksUnique->link);

if (
    isset($url_[2]) &&
    (mb_strpos($url_[2], 'youtube') !== false || mb_strpos($url_[2], 'youtu.be') !== false)
) {
    if (!empty($array)) {
        if (empty($array['v'])) {
            $video = isset($url_[count($url_) - 1]) ? explode('?', $url_[count($url_) - 1])[0] : '';
        } else {
            $video = $array['v'];
        }
    } else {
        $video = isset($url_[count($url_) - 1]) ? $url_[count($url_) - 1] : '';
    }

    if (!empty($video)) {
        $link = "https://www.youtube.com/embed/$video";
        $linkValido = true;
    }
} elseif (isset($url_[2])) {
    $link = $transmissao->linksUnique->link;
    $linkValido = true;
}
```

| Entrada no campo `link` | Resultado |
|---|---|
| `https://www.youtube.com/watch?v=YbJOTdZBX1g` | `https://www.youtube.com/embed/YbJOTdZBX1g` (via `$array['v']`). |
| `https://youtu.be/YbJOTdZBX1g` | `https://www.youtube.com/embed/YbJOTdZBX1g` (último segmento). |
| `https://www.youtube.com/embed/YbJOTdZBX1g` | `https://www.youtube.com/embed/YbJOTdZBX1g` (último segmento). |
| `https://outrodominio.com.br/video` | URL original exibida em `iframe` (host não-YouTube). |
| `YbJOTdZBX1g` (ID cru) | Gravado como `/YbJOTdZBX1g` pelo `sanitizeLink()` → **"Transmissão não disponível"**. |
| `https://www.youtube.com/watch?v=` (sem ID) | `$video` vazio → **"Transmissão não disponível"**. |

## 3. Telas do CMS

| Arquivo | Função |
|---|---|
| `v6/resources/views/prefecture/contents/transmissoes/index.blade.php` | Listagem (DataTable `#tabela_transmissoes`), cabeçalho "Lista de Transmissões", botão "Cadastrar Transmissão", botão de exclusão em lote. |
| `v6/resources/views/prefecture/contents/transmissoes/create.blade.php` | Formulário de cadastro (`Form::open(['route' => ['conteudo.store']])`, `files => true`). |
| `v6/resources/views/prefecture/contents/transmissoes/edit.blade.php` | Formulário de edição (`Form::model($content, ['route' => ['conteudo.update', $content->id], 'method' => 'PUT'])`). |
| `v6/resources/views/prefecture/contents/transmissoes/form.blade.php` | Campos comuns ao cadastro/edição. |

Layout do CMS: `@extends('admin.layouts.app')` (AdminLTE 1.22).

### 3.1 Campos do formulário

| Campo | Tipo | Limite | Observação |
|---|---|---|---|
| `category_contents_id` | hidden | — | Preenchido com `{{$categoryContent->id}}` (36). |
| `status` | checkbox | — | Rótulo "Ativar/Desativar"; marcado por padrão em novos registros (`isset($content)&& $content->status == false ? false : true`). |
| `title` | text | `maxlength="120"` | Placeholder "Título". |
| `link` | text | `maxlength="200"` | Rótulo **"Video (Youtube)"**, com a observação "Utilize o link ou ID de vídeo do YouTube" e placeholder `Exemplo: https://www.youtube.com/watch?v=YbJOTdZBX1g ou YbJOTdZBX1g`. |

### 3.2 Listagem (DataTable)

Colunas do HTML:

| Coluna | Origem |
|---|---|
| (seleção em lote) | `deleteByLoteMasterCheckbox`, visível apenas com `transmissoes.delete`. |
| ID | coluna `value` da view SQL (injetada por `runDatatables`). |
| Título | `titulo`. |
| Status | `status_name_transmissao`. |
| Ações | botão de toggle + botão de editar. |

## 4. JavaScript e Ajax

| Arquivo | Responsabilidade |
|---|---|
| `v6/public/assets/js/custom/contents/transmissoes/datatables.js` | Define as colunas e chama `runDatatables('transmissoes', collums, true, 'ASC', null, true)`. |
| `v6/public/assets/js/custom/utilities/table.js` | `runDatatables()` (linha 3), `createActiveButton()` (linha 373), `createBTN()`, `createDeleteAllButton()`. |
| `v6/public/assets/js/custom/utilities/deleteAll.js` | Exclusão em lote. |

Botão de status:

```js
createActiveButton(data.status, data.id, 'transmissão', data.active, data.inactivate, `/cms/transmissoes/${data.id}/status`)
```

O clique dispara `POST /cms/transmissoes/{id}/status` com o corpo
`{ contents_id: id }` (`table.js:514`).

Botão de edição:

```js
createBTN(data.id, `editar/transmissoes/${data.id}`, data.edit, data.delete)
```

### 4.1 Exportações

`runDatatables(..., deleteAll = true)` habilita os botões padrão de exportação
(Excel, CSV, PDF) montados em `v6/public/assets/js/custom/utilities/table.js:29+`,
com o título `Prefeitura de <cidade> - <data>`. A exportação usa os mesmos
dados server-side da view SQL.

## 5. Menu do CMS

`v6/app/Providers/MenuServiceProvider.php:64-66`

```php
                            case 'transmissoes':
                                $event->menu->add(['text' => 'Transmissões', 'url' => '/cms/conteudos/transmissoes', 'icon' => 'television']);
```

## 6. Fluxo completo operador → cidadão

1. Operador abre **Transmissões** no menu do CMS → `menuClicks('transmissoes')`.
2. Clica em **Cadastrar Transmissão**, preenche `Título` e `Video (Youtube)`,
   marca **Ativar/Desativar** e salva.
3. `ContentController@store` grava `contents` (categoria 36) e `links`
   (URL normalizada), desativando os demais ativos da categoria.
4. O `modulos.status` precisa estar `true` para o widget aparecer; caso
   contrário o operador precisa ligar o módulo em `/cms/modulos`.
5. Ao abrir qualquer página do portal, o widget consulta o primeiro conteúdo
   ativo da categoria e renderiza o `iframe` na lateral.

## 7. Acessibilidade e responsividade (observações)

- O `iframe` não possui `title` nem `aria-label` — leitores de tela anunciam
  apenas "iframe" (`embedVideo()` e o parser do widget não geram atributos).
- Não há alternativa textual para o vídeo nem transcrição.
- O rótulo "Transmissão" depende da animação `piscar` para ser percebido como
  "ao vivo"; não há texto "AO VIVO".
