# Carta de Serviços — Views e UX

## 1. Views do CMS — `v6/resources/views/prefecture/service-letter/`

Todas estendem `admin.layouts.app` (AdminLTE 1.22 / Bootstrap 3).

| Arquivo | Função |
|---|---|
| `services/index.blade.php` | Listagem de serviços. Cabeçalho com 4 botões: "Gerar PDF" (form POST para `carta-de-servicos.pdfs.generate`, com `confirm()`), "Configurações" (link), "Cadastrar Serviço" (link) e o componente `prefecture.components.deleteByLoteSubmitButton` (exclusão em lote). Tabela `id="tabela_carta_de_servicos"` com colunas ID, Nome, Secretaria, Assuntos, Menu, Status, Destaque e Ações. |
| `services/create.blade.php` | Wrapper de `Form::open(['route' => 'carta-de-servicos.servicos.store', 'files' => true])` + include de `services/form.blade.php` + botão "Cadastrar". |
| `services/edit.blade.php` | Wrapper de `Form::open([... 'method' => 'PUT', 'files' => true])` + include de `services/form.blade.php` + botão "Atualizar" + campo oculto `service_letter_id`. Depois do `Form::close()`, um `nav-tabs-custom` com **5 abas**: Responsáveis, Canais, Etapas, Documentos, Anexos. |
| `services/form.blade.php` | Formulário completo do serviço (≈390 linhas). |
| `services/partials/responsaveis.blade.php` | Tabela de responsáveis (`tbody id="lista_responsaveis"`) + modal `#modal_responsavel` com upload de foto. |
| `services/partials/channels.blade.php` | Tabela de canais (`tbody id="lista_canals"`) + modal `#modal_canal`. |
| `services/partials/steps.blade.php` | Tabela de etapas (`tbody id="lista_etapas"`) + modal `#modal_etapa`. |
| `services/partials/documents.blade.php` | Tabela de documentos (`tbody id="lista_documentos"`) + modal `#modal_documento`. |
| `services/partials/attachments.blade.php` | Tabela de anexos (`tbody id="lista_anexos"`) + modal `#modal_anexo` com upload. |
| `subjects/index.blade.php` | Listagem de assuntos (DataTable `tabela_carta_de_servicos_assuntos`) + botão "Cadastrar Assunto" + exclusão em lote. |
| `subjects/create.blade.php` / `subjects/edit.blade.php` | Wrappers de formulário (POST / PUT). |
| `subjects/form.blade.php` | Formulário do assunto: nome, ordem, descrição, tipo de visual (ícone/imagem), seletor de ícone com preview ao vivo, upload de imagem, flag "Assunto ativo". |
| `config/index.blade.php` | Formulário de configurações (PUT `carta-de-servicos.config.update`). |
| `pdfs/index.blade.php` | Listagem paginada de PDFs (10 por página) com ações visualizar / download / excluir. |

### 1.1 Estrutura do formulário do serviço

`v6/resources/views/prefecture/service-letter/services/form.blade.php`:

| Bloco | Campos |
|---|---|
| Dados básicos | `nome` (obrigatório), `ordem`, `descricao_curta` (500 chars), `secretaria_id` (select), `menu_acesso_rapido` (select estático: Cidadão / Empresa / Servidor), `tipo_exibicao` (select estático: Página Interna / Link Externo), `link_externo` (container oculto por JS), `subjects[]` (select múltiplo) |
| Formas de acesso | `acesso_online`, `acesso_telefone`, `acesso_presencial` (checkboxes) |
| Container `#campos_pagina` (visível só quando `tipo_exibicao = pagina`) | `sobre`, `documentacoes`, `requisitos`, `etapas` (Summernote), `gratuito` + `valor_taxa`, `custos` (Summernote), `previsao_atendimento`, `horario_atendimento`, `previsao_atendimento_detalhes`, `horario_atendimento_detalhes`, `prioridades_atendimento`, `justificativa`, `endereco_atendimento`, `endereco_cep`, `endereco_latitude`, `endereco_longitude`, `responsavel_nome`, `responsavel_cargo`, `responsavel_email`, `responsavel_telefone`, `responsavel_foto` (upload), `informacoes_adicionais` (Summernote) |

> **Duplicação de conteúdo verificada:** existem **duas** representações de "etapas do serviço" — o campo HTML livre `service_letters.etapas` (Summernote) e a tabela estruturada `service_letter_steps`. O portal **exibe as duas** (`view.blade.php` mostra o bloco "Etapas do Serviço" com `{!! $service->etapas !!}` **e** a lista estruturada de `$service->steps`). O operador pode preencher uma e deixar a outra vazia, produzindo páginas incoerentes.
>
> Também há duplicação entre `service_letters.responsavel_*` (campos no formulário principal) e `service_letter_responsaveis` (aba dedicada) — o portal exibe **apenas** a aba de responsáveis estruturados; os campos `responsavel_nome/cargo/email/telefone` do formulário não são exibidos em lugar nenhum do portal. Ver `08-GAPS-E-DIVIDAS.md`.

### 1.2 UX dos modais AJAX

Todos os modais seguem o mesmo contrato:

1. Botão "Adicionar X" → `resetFormX()` + `$('#modal_x').modal('show')`.
2. Submit do `<form id="form_x">` → `e.preventDefault()` + `FormData` + `formData.set('ativo', 'true'|'false')`.
3. `submitForm(url, formData, tipo, action)` faz `$.ajax` com `X-CSRF-TOKEN` do `<meta name="csrf-token">`, `processData: false`, `contentType: false`.
4. Sucesso → `toastr.success(response.message)` + fecha modal + `reloadTable(tipo, response.data, action)`.
5. `reloadTable` **substitui** a linha existente (`tr[data-id="..."]`) quando `action === 'update'`, ou **acrescenta** quando é `add`.
6. Erro 422 → `showErrors()` renderiza `<ul><li>…</li></ul>` no `#x_errors`; erro genérico → `toastr.error`.

> **Bug de HTML verificado:** o `tbody` dos canais tem `id="lista_canals"` (com "s" no fim, errado), enquanto o JS calcula `'#lista_' + tipo + 's'` → `#lista_canais`. A inserção/atualização de canal **não aparece na tabela** após o AJAX. Os demais recursos batem (`lista_etapas`, `lista_documentos`, `lista_anexos`). Ver `08-GAPS-E-DIVIDAS.md`.
>
> **Verificação do `colspan`:** `checkEmptyTable(tipo)` usa `6` para canais, etapas e anexos, e `5` para documentos — o que bate com o número real de colunas das tabelas (canais 6, etapas 6, documentos 5, anexos 6). Não há divergência.

## 2. JavaScript do CMS

| Arquivo | Endpoints chamados | Função |
|---|---|---|
| `v6/public/assets/js/custom/contents/service-letter/datatable.js` | `GET /cms/datatable/carta_de_servicos` | DataTable de serviços; colunas nome, secretaria, assuntos, menu, status, destaque + botões `createBTN` (editar/excluir). `runDatatables('carta_de_servicos', collums, true, 'ASC', null, true)` — o último `true` habilita o botão de exclusão em lote. |
| `v6/public/assets/js/custom/contents/service-letter/subjects-datatable.js` | `GET /cms/datatable/carta_de_servicos_assuntos` | DataTable de assuntos (visual, nome, ordem, status + ações). |
| `v6/public/assets/js/custom/contents/service-letter/modulos.js` | `POST/PUT/DELETE /cms/carta-de-servicos/{canais,etapas,documentos,anexos}` | Todo o CRUD AJAX de canais, etapas, documentos e anexos. Aplica máscara de telefone no canal. |
| `v6/public/assets/js/custom/contents/service-letter/responsaveis.js` | `POST/PUT/DELETE /cms/carta-de-servicos/responsaveis` | CRUD AJAX de responsáveis com upload de foto. |

Utilitários compartilhados:

| Arquivo | Função |
|---|---|
| `v6/public/assets/js/custom/utilities/table.js` | `runDatatables(url, columns, ordenavel, type_order, callback, deleteAll, customButtons, order_column)` — monta a DataTable com `ajax.url = '/cms/datatable/' + url`, botões de exportação Excel/PDF/CSV/TXT e `createBTN()` / `createSingleButton()`. |
| `v6/public/assets/js/custom/utilities/deleteAll.js` | `submitDeleteAll(routeDelete)` — POST com `toDelete` (array de ids) + Swal de confirmação. |

## 3. Views do Portal — `v6/resources/views/portal/layouts/modelo{1..6}/carta-servicos/`

Existem **6 modelos × 5 arquivos = 30 blades**. Os modelos são cópias quase idênticas: o `diff` entre `modelo1` e `modelo2` de `view.blade.php` aponta apenas o `@extends`, o `@include` do breadcrumb/sidebar, uma classe CSS de borda (`border-2` vs `border-1`) e um ícone (`fa-users` vs `fa-user-tie`).

| Arquivo | Função |
|---|---|
| `index.blade.php` | Página inicial (fallback quando não há assuntos ativos): lista os assuntos como `list-group`. **Raramente alcançável** — `Portal\ServiceLetterController@index` redireciona para o primeiro assunto ativo. |
| `nav.blade.php` | Menu lateral de assuntos (cards com `route('carta-servicos.show', $sub->slug)`), com scroll quando há mais de 14 assuntos. No rodapé, busca inline por `ServiceLetterPdf::where('status','gerado')->orderBy('gerado_em','desc')->first()` e exibe o botão "Baixar PDF Completo" se existir. |
| `show.blade.php` | Página do assunto: inclui `nav` + `list`, com **spinner** durante o carregamento AJAX. |
| `list.blade.php` | Fragmento HTML com o campo de busca (`id="search"`, `name="text"`), a lista de serviços e a paginação (`{{ $services->links() }}`). É este fragmento que é re-renderizado por AJAX. |
| `view.blade.php` | Ficha completa do serviço (≈310 linhas): badges de formas de acesso, cards de Sobre, Requisitos, Documentos (HTML e estruturados), Etapas (HTML e estruturadas), Custos, Prazo, Prioridades, Horário, Local, Canais, Responsáveis, Informações Adicionais e o botão "Acessar Serviço Online" (quando `tipo_exibicao = link_externo`). |

### 3.1 Comportamento AJAX do portal

`show.blade.php` embute um `<script>` com três funções:

| Função | Endpoint | Efeito |
|---|---|---|
| `getServices(page)` | `?page=N&texto=…&secretaria=…` na própria URL | Substitui `#include-list` pelo HTML retornado; atualiza `location.hash` |
| `getServicesByTexto()` | `?texto=…&secretaria=…` | idem |
| `getServicesBySecretaria()` | `?secretaria=…&texto=…` | idem |

O controller detecta `$request->ajax()` e devolve `Response::json(view(...)->render())` — isto é, **JSON contendo HTML já renderizado**, não JSON de dados. O JS injeta a string direto em `$('#include-list').html(data)`.

> **Contrato frágil verificado:** como o payload é JSON com HTML dentro, qualquer aspas/quebra de linha no HTML é escapada pelo `json_encode`. O `Response::json` do Laravel 5.5 usa `JSON_UNESCAPED_UNICODE`? [NÃO VERIFICADO] em runtime — o efeito prático é que acentos podem aparecer escapados (`\u00e7`) no HTML injetado, dependendo das flags de `json_encode` do `ResponseFactory`. Testar em ambiente real é obrigatório antes de replicar o padrão.

> **Assinatura divergente verificada:** o `<input>` de busca em `list.blade.php` tem `name="text"`, mas o controller lê `$request->filled('texto')` e `$request->texto`. O JS contorna isso montando a query manualmente com `texto=` — mas a query string só é montada nos botões e no `hashchange`, **não** no primeiro carregamento. Um usuário que digite e pressione Enter no campo (submit do form, se houver) **não** dispara a busca. Ver `05-REGRAS-E-PORTABILIDADE.md`.

## 4. Exportações

### 4.1 Exportação da listagem do CMS (DataTables)

Herdada do `runDatatables` (`v6/public/assets/js/custom/utilities/table.js`): botões **Excel** (xlsx), **PDF** (landscape A4), **CSV** e **TXT**, todos operando sobre as colunas visíveis exceto `.column-actions`, com cabeçalho "Prefeitura de {cidade} - {data}".

### 4.2 Exportação da Carta de Serviços em PDF (módulo)

- **CMS:** `POST /cms/carta-de-servicos/pdfs/gerar` → `ServiceLetterPdfController@generate`. O controller monta a lista de serviços publicados e chama `new ServiceLetterPdfService()`.
- **Portal:** `GET /carta-de-servicos/pdf` → `Portal\ServiceLetterController@pdf` — baixa o PDF mais recente com `status = 'gerado'`, montando o caminho `storage_path('app/public/' . str_replace('storage/', '', $pdf->arquivo))` e devolvendo `response()->file($path)`.

Detalhamento e estado atual do gerador em `10-PDF-E-ANEXOS.md`.

## 5. O que o usuário vê

### 5.1 Cidadão (portal)

1. Acessa `/carta-de-servicos` → é redirecionado ao primeiro assunto ativo.
2. Escolhe o assunto no menu lateral (ou usa o acesso rápido por link).
3. Vê a lista paginada (12 por página) com nome, secretaria, assuntos, descrição curta (150 chars) e badges de Online/Presencial/Telefone/Gratuito.
4. Filtra por texto (com normalização de acentos via `clean_slug` + `translate(... ilike ...)` no Postgres) ou por secretaria.
5. Abre a ficha do serviço e lê toda a documentação; o contador `visualizacoes` é incrementado.
6. Pode baixar o PDF completo da Carta (se houver PDF gerado).

### 5.2 Operador (CMS)

1. `/cms/carta-de-servicos/servicos` → DataTable com 4 botões de ação no topo.
2. "Cadastrar Serviço" → formulário em duas áreas (básicas + "página"); salva e redireciona à listagem.
3. "Editar Serviço" → mesmo formulário + 5 abas para canais/etapas/documentos/anexos/responsáveis.
4. "Gerar PDF" → gera o documento consolidado (sujeito à falha da classe ausente).
5. "Configurações" → identidade visual, textos fixos e tipografia do PDF.

## 6. Componentes Blade reutilizados

| Componente | Uso |
|---|---|
| `components.inputs.input`, `.textarea`, `.select`, `.select-static`, `.checkbox`, `.radio`, `.file`, `.icon-picker` | Formulários do CMS (AdminLTE/Bootstrap 3) |
| `components.layout.errors` | Bloco de erros de validação |
| `components.layout.mandatory_help` | Legenda de campos obrigatórios |
| `prefecture.components.deleteByLoteSubmitButton` | Botão de exclusão em lote (serviços e assuntos) |
| `prefecture.components.deleteByLoteMasterCheckbox` | Checkbox "selecionar todos" da lista |
| `portal.components.view_pdf.files-list` | (usado por outros módulos; não neste) |

## 7. Assets

| Asset | Situação |
|---|---|
| `v6/public/assets/js/custom/contents/service-letter/datatable.js` | existe |
| `v6/public/assets/js/custom/contents/service-letter/subjects-datatable.js` | existe |
| `v6/public/assets/js/custom/contents/service-letter/modulos.js` | existe |
| `v6/public/assets/js/custom/contents/service-letter/responsaveis.js` | existe |
| `public/assets/css/custom/contents/carta-de-servicos/style.css` | **NÃO EXISTE** — referenciado em `services/form.blade.php` (`@section('adminlte_css')`) e `services/edit.blade.php`; resulta em 404 silencioso no console do navegador |
| `assets/vendor/portal/css/spinner.css` | referenciado por `portal/.../carta-servicos/show.blade.php`; **existe** em `v6/public/assets/vendor/portal/css/spinner.css` |

## 8. Layouts do portal usados

| Modelo | Views da Carta de Serviços | `home/index` (exigido por `getTemplateActived`) |
|---|---|---|
| `modelo1` | sim | sim |
| `modelo2` | sim | sim |
| `modelo3` | sim | sim |
| `modelo4` | sim | sim |
| `modelo5` | sim | sim |
| `modelo6` | sim | sim |
| `default` | **não** | **não** → `getTemplateActived()` aborta com 404 |
| `newsbit` | **não** | **não** → idem |

> **Risco operacional verificado:** se a prefeitura trocar o layout ativo (chave `layout` em `configurations`) para `default` ou `newsbit`, **todas** as páginas da Carta de Serviços do portal passam a dar 404 — e o próprio `getTemplateActived()` aborta antes, porque nem `home/index` existe nesses layouts.
