# ENQUETES — Views e UX

> **Versão analisada:** `v6`
> Views do CMS em `v6/resources/views/prefecture/surveys/`
> Views do portal em `v6/resources/views/portal/layouts/surveys/`
> Sidebar do portal em `v6/resources/views/portal/layouts/modelo*/layout/sidebar/enquetes.blade.php`

---

## 1. CMS — telas do operador

| Arquivo | Função |
|---|---|
| `v6/resources/views/prefecture/surveys/index.blade.php` | Listagem em DataTable (`#tabela_enquete`). Estende `admin.layouts.app`. |
| `v6/resources/views/prefecture/surveys/create.blade.php` | Formulário de criação. `Form::open(['route' => ['enquete.store'], 'files' => true, 'id' => 'form'])`. |
| `v6/resources/views/prefecture/surveys/edit.blade.php` | Formulário de edição. `Form::model($survey, ['route' => ['enquete.update', $survey->id], 'method' => 'PUT', 'id' => 'survey-edit-form'])`. |
| `v6/resources/views/prefecture/surveys/form.blade.php` | Formulário compartilhado entre create e edit. |

### 1.1 Listagem (`index.blade.php`)

- Cabeçalho "Lista de Enquetes".
- Botão **"Cadastrar Enquete"** — visível apenas com `has_access(['enquete.create'])` (linha 11).
- Botão **"Excluir Registro(s)"** — `prefecture.components.deleteByLoteSubmitButton`
  apontando para `route('ajax.survey.deleteByLote')`, visível apenas com
  `has_access(['enquete.delete'])` (linha 16).
- Tabela `#tabela_enquete` com colunas: *checkbox mestre* (se permissão de
  exclusão), *ID*, *Pergunta*, *Qtd. Resposta*, *Data de publicação*,
  *Data de encerramento*, *Ações*.
- Scripts: `assets/js/custom/utilities/deleteAll.js` e
  `assets/js/custom/contents/enquetes/datatable.js`.

### 1.2 Formulário (`form.blade.php`)

| Campo | Componente | Observação |
|---|---|---|
| Link Permanente | texto + link "Acessar" | Só aparece quando `$survey` está definido (edição); mostra `url("/enquetes/{$survey->slug}")`. |
| Data de publicação | `components.inputs.date` com máscara `br-data-tempo` | formato `d/m/Y H:i`; valor padrão = agora. |
| Data de expiração | `components.inputs.date` com máscara `br-data-tempo` | idem. |
| Pergunta | `components.inputs.textarea` | obrigatório. |
| Publicar | `components.inputs.checkbox` | marca "Publicar"; o `obs` diz "Para deixar o evento como rascunho, desmarque essa opção." (texto herdado do módulo *Agenda de Eventos*). |
| Opção(ões) de resposta | construtor dinâmico em JS | cada linha: `answers[<id>][answer]` + `answers[<id>][id]` oculto; linhas novas: `answers[][answer]`. |

- O botão "Adicionar mais respostas" (`#adiciona-resposta`) cria uma nova linha
  via JS (sem nova ida ao servidor).
- O botão de lixeira (`#btn-delete`) chama a função JS `deleteAnswer(id)`, que
  **apenas remove o elemento do DOM** — não chama o servidor. A exclusão real
  acontece no `update`, que recria todas as opções.
- Se `$disabled` (enquete com votos), o botão "Adicionar mais respostas" e o
  botão de lixeira são desabilitados; o botão de submit do `edit.blade.php`
  também é substituído pela mensagem "Essa enquete não pode ser editada".

### 1.3 Auditoria exibida

- `edit.blade.php` registra em `activity()` os acessos a create/edit/listagem,
  com `performedOn($this->surveyLog)`, onde `$surveyLog` é um **`CalendarEvent`**
  (não um `Survey`) — herança do módulo *Agenda de Eventos*. O log de auditoria
  das enquetes, portanto, fica gravado sobre o modelo errado.

## 2. Portal — telas do cidadão

| Arquivo | View | Quando é usada |
|---|---|---|
| `v6/resources/views/portal/layouts/surveys/index.blade.php` | `portal.layouts.surveys.index` | `GET /enquetes` |
| `v6/resources/views/portal/layouts/surveys/show.blade.php` | `portal.layouts.surveys.show` | `GET /enquetes/{slug}` (votação) |
| `v6/resources/views/portal/layouts/surveys/closed.blade.php` | `portal.layouts.surveys.closed` | `GET /enquetes/encerrada/{slug}` (resultado) |

As três estendem `portal.layouts.$modelo.layout.layout`, onde `$modelo` vem de
`Configuration` chave `layout` (`modelo1`..`modelo6`).

### 2.1 Listagem (`surveys/index.blade.php`)

- Cartão "Enquetes" com campo de **filtro por texto** (`#txtBusca`): o JS
  percorre `#ulItens li` e oculta os itens cujo texto não contém o termo
  digitado (filtro **client-side**, sem paginação nem servidor).
- Para cada enquete:
  - se `final_date <= current_date`: "Encerrada em ..." + botão **"Ver Resultado"**
    (`route('enquetes.closed.show', $survey->slug)`);
  - caso contrário: "Encerra em ..." + botão **"Votar"**
    (`route('enquetes.show', $survey->slug)`).

### 2.2 Votação (`surveys/show.blade.php`)

- Título com a pergunta, botão de compartilhamento (`addthis_inline_share_toolbox`).
- "Publicado em {data} às {hora}" — usa `publication_date`.
- Se `$votado` → exibe "Você já votou!" em verde.
- Caso contrário, exibe o formulário com um `Form::radio('survey_answer_id', ...)`
  por opção e o botão "Votar" (`onclick="contact()"`).
- Ao final, `$('#enquetes_sidebar').remove();` — **remove a enquete da sidebar**
  da própria página (o CSS `#enquetes_sidebar{display:none}` já a escondia).
- Bloco `<style>` local com `#enquetes_sidebar{display:none}`.

### 2.3 Resultado (`surveys/closed.blade.php`)

- "Enquete encerrada em {data} às {hora}" — **usa `publication_date`**
  (deveria ser `final_date`); é um erro de exibição.
- Para cada opção: barra de progresso com `width: {{$result->porcetagem}}%` e o
  texto "{opção} - {percentual} %".
- O atributo `aria-valuenow="40"` está **fixo** em 40 em todas as barras
  (valor Accessibility inconsistente com o percentual real).

## 3. Layouts do portal utilizados

| Template | Layout | Sidebar de enquetes | Observação |
|---|---|---|---|
| `modelo1` | `portal.layouts.modelo1.layout.layout` | `modelo1/layout/sidebar/enquetes.blade.php` (incluído em `modelo1/layout/sidebar/layout.blade.php:13`) | idem |
| `modelo2` | idem | `modelo2/layout/sidebar/enquetes.blade.php` (`sidebar/layout.blade.php:15`) | idem |
| `modelo3` | idem | `modelo3/layout/sidebar/enquetes.blade.php` (`sidebar/layout.blade.php:5`) | idem |
| `modelo4` | idem | `modelo4/layout/sidebar/enquetes.blade.php` (`sidebar/layout.blade.php:5`) | idem |
| `modelo5` | idem | `modelo5/layout/sidebar/enquetes.blade.php` (`sidebar/layout.blade.php:3`) | idem |
| `modelo6` | idem | `modelo6/layout/sidebar/enquetes.blade.php` (`sidebar/layout.blade.php:13`) | idem |
| `default` | — | **inexistente** | Não possui `home/index.blade.php` nem `layout/layout.blade.php`; se for o layout ativo, `getTemplateActived()` aborta com 404 (`v6/app/Http/Controllers/Admin/Controller.php:43-45`). |

## 4. Sidebar de enquetes (portal)

Arquivos idênticos em estrutura para `modelo1`..`modelo6`:

```
v6/resources/views/portal/layouts/modelo1/layout/sidebar/enquetes.blade.php
v6/resources/views/portal/layouts/modelo2/layout/sidebar/enquetes.blade.php
v6/resources/views/portal/layouts/modelo3/layout/sidebar/enquetes.blade.php
v6/resources/views/portal/layouts/modelo4/layout/sidebar/enquetes.blade.php
v6/resources/views/portal/layouts/modelo5/layout/sidebar/enquetes.blade.php
v6/resources/views/portal/layouts/modelo6/layout/sidebar/enquetes.blade.php
```

Comportamento:

1. Só renderiza se `isset($modulos['enquetes']) && $modulos['enquetes']`
   (módulo ligado em `modulos.status`).
2. Só renderiza se `!isset($votado)` — a variável `$votado` é injetada pelo
   controller da tela principal; quando a tela já é a de votação, o bloco
   inteiro é omitido.
3. Busca, **dentro do Blade**, a enquete ativa mais recente
   (`status = 1 AND final_date > now`, `orderBy created_at DESC`, `first()`)
   e o primeiro `survey_results` dela, comparando o IP — duas queries por
   renderização de sidebar, feitas via `@inject('surveyModel', 'App\Prefecture\Survey')`.
4. Mostra "Encerra em ...", a pergunta, e o formulário de voto (mesmo
   componente e mesmo JS da tela de votação).

**Colisão de identificadores:** o formulário da sidebar usa `id="form-survey"` e
a tela `surveys/show.blade.php` também usa `id="form-survey"`. Na página de
votação ambos coexistem no DOM (a sidebar apenas é ocultada por CSS), o que
produz id duplicado e faz `$('#form-survey')[0]` depender da ordem do DOM.

**Colisão de função global:** a sidebar define `function contact()` e a tela
`surveys/show.blade.php` define `function contact()` outra vez, no mesmo
documento. A última definição prevalece. O mesmo nome é usado pela página de
contato do portal.

## 5. JS / Ajax

### 5.1 Portal — envio do voto

Definido em 7 arquivos (6 sidebars + `surveys/show.blade.php`):

```js
function contact() {
    const form = $('#form-survey')[0];
    const formData = new FormData(form);

    $.ajax({
        type: 'POST',
        url: "{{route('send.survey.ajax')}}",
        processData: false,
        cache: false,
        contentType: false,
        data: formData,
        success: function (data) {
            if (data[0] == 400){
                $('#msgError').html(data[1]);
            }else{
                /* substitui o formulário por "Voto salvo com sucesso!" */
            }
        },
        error: function (e) { $('#msgError').html(e); }
    });
}
```

- **Endpoint:** `POST /send-survey-ajax` (nome `send.survey.ajax`).
- **Sem polling, sem retry e sem proteção contra duplo clique**: o botão permanece
  clicável durante a requisição.
- O `Form::open(['route' => 'contato.enviar'])` define o `action` HTML como
  `/cms/contato/enviar` — errado. Só funciona porque o JS sobrescreve a URL.
- O `FormData` inclui o token CSRF (injetado por `Form::open`), o
  `survey_id` (hidden) e o `survey_answer_id` (radio).

### 5.2 Portal — filtro da listagem

`v6/resources/views/portal/layouts/surveys/index.blade.php:83-95`: filtro de
texto client-side sobre `#ulItens li`.

### 5.3 CMS — DataTable

`v6/public/assets/js/custom/contents/enquetes/datatable.js`:

```js
let collums = [
    { data: "pergunta", ... },
    { data: "qtd_respostas", ... },
    { data: "data_publicacao", ... },
    { data: "data_encerramento", ... },
    { data: null, orderable: false, render: ... createBTN(...) }
]

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

- `runDatatables` (`v6/public/assets/js/custom/utilities/table.js:3`) monta o
  DataTable **client-side** (a opção `serverSide` não é habilitada), consumindo
  via AJAX o endpoint `/cms/datatable/enquete` (`table.js:181-185`), que
  devolve a view SQL **inteira**. O helper também prepende a coluna `value`
  (o `row_number()` da view) e a coluna de seleção para exclusão em lote
  (`deleteAll = true`).
- O botão de exclusão individual usa `createBTN(data.id, 'enquete/{id}/edit',
  data.edit, data.delete, 'enquete/{id}')`
  (`v6/public/assets/js/custom/utilities/table.js:227`), que monta os botões
  de editar/excluir conforme as flags `edit`/`delete` injetadas por
  `DatatableController::struct_views()`
  (`v6/app/Http/Controllers/Prefecture/DatatableController.php:504-520`).
- O clique no botão de exclusão é tratado por `addDelete()`
  (`v6/public/assets/js/custom/utilities/table.js:466-489`), que dispara
  `myFetch(url, 'DELETE')` com o token CSRF no header
  `X-CSRF-TOKEN`
  (`v6/public/assets/js/custom/utilities/functions.js:64-88`).
  A URL vem do atributo `target` do botão — no módulo Enquetes, o valor
  `enquete/{id}` é **relativo** e resolve para `/cms/enquete/{id}` porque a
  listagem está em `/cms/enquete`. O utilitário tem um fallback hardcoded
  `/cms/conteudo/{id}` (`table.js:471`) que só não é usado neste módulo.
- Exportação: o helper `runDatatables` habilita **Excel** e **PDF** (botões
  `extend: 'excel'` / `extend: 'pdf'`), usando as colunas visíveis exceto
  `.column-actions`. O título do arquivo é
  "Prefeitura de {cidade} - {data}" e a exportação é feita **no cliente**,
  a partir dos dados do DataTable (nunca em PDF nativo do servidor).

### 5.4 CMS — exclusão em lote

`v6/public/assets/js/custom/utilities/deleteAll.js`:

- `submitDeleteAll(routeDelete)` coleta `input[name="toDeleteAll[]"]:checked`,
  pede confirmação com SweetAlert e faz `POST` com
  `{_token, toDelete: [...]}`.
- Em caso de sucesso, recarrega o DataTable (`datatable.ajax.reload()`).

### 5.5 CMS — construtor de opções

`v6/public/assets/js/custom/contents/enquetes/script.js`:

- Máscaras `00/00/0000 00:00` em `#publication_date` e `#final_date`.
- `#adiciona-resposta` clona uma linha `<input name="answers[][answer]">`.
- `.js-remove-itens` remove a linha, mas **mantém ao menos uma** linha.

### 5.6 CMS — remoção visual de uma opção

`v6/resources/views/prefecture/surveys/edit.blade.php:46-55`: `deleteAnswer(id)`
faz `$("#answer"+id).fadeOut('slow')` e remove o elemento após 500 ms, com
toastr de sucesso. **Nenhuma requisição ao servidor** — a remoção é efetivada
quando o formulário é salvo.

## 6. Exportações

| Exportação | Onde | Como é gerada |
|---|---|---|
| Excel | DataTable do CMS | Botão "Excel" do Yajra DataTables, export client-side, colunas visíveis sem `.column-actions`. |
| PDF | DataTable do CMS | Botão "PDF" do Yajra DataTables, `orientation: 'landscape'`, `pageSize: 'A4'`. |

O portal **não** oferece exportação de resultados. Não há CSV, nem API de
resultados, nem relatório em PDF para o cidadão.

## 7. Resumo do que cada perfil vê

| Perfil | Telas | Ações |
|---|---|---|
| Operador do CMS | `/cms/enquete`, `/cms/enquete/create`, `/cms/enquete/{id}/edit` | Cadastrar, editar (até o 1º voto), publicar/rascunhar, excluir individual e em lote, exportar Excel/PDF, ver contagem de respostas. |
| Cidadão | `/enquetes`, `/enquetes/{slug}`, `/enquetes/encerrada/{slug}`, sidebar do portal | Votar anonimamente, ver resultado percentual após o encerramento, filtrar a lista por texto. |
|.visitante não logado no CMS | `/cms/*` | Redirecionado ao dashboard (middleware `check.logged`) nas rotas do resource; as rotas de Ajax do módulo **não** têm essa proteção. |
