# Views e experiência — Licitações

## 1. Inventário de blades

| Arquivo | Superfície | Papel |
|---|---|---|
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/index.blade.php` | CMS | lista paginada, filtros, ações por linha |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/archived.blade.php` | CMS | lixeira: restaurar e excluir em definitivo |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/create.blade.php` | CMS | casca do formulário de criação + modal de órgão |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/edit.blade.php` | CMS | casca do formulário de edição + anexos + modais |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/form.blade.php` | CMS | todos os campos da licitação e seleção de órgãos |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments.blade.php` | CMS | lista de anexos, upload/edição/reordenação |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments-modals.blade.php` | CMS | modais de upload e de título do anexo |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/orgao-modal.blade.php` | CMS | modal + JS de criação inline de órgão |
| `V2/Modules/Publicacoes/Licitacoes/Views/cms/orgaos/index.blade.php` | CMS | CRUD de órgãos em uma página |
| `V2/Modules/Publicacoes/Licitacoes/Views/portal/index.blade.php` | Portal | tabela de licitações ativas com filtro local |
| `V2/Modules/Publicacoes/Licitacoes/Views/portal/show.blade.php` | Portal | detalhe por slug |

Views nomeadas por namespace `v2-licitacoes::`, registrado pelo Core.

## 2. Layouts e herança

- **CMS**: `@extends('v2-cms::layouts.app', ['title' => ..., 'pageIcon' => ...])`.
  Usa as seções `page-breadcrumbs`, `page-actions`, `content`, as pilhas
  `modals` e `scripts`, e os partials `v2-cms::partials.breadcrumb`,
  `status-badge`, `filter-card` e `table`.
- **Portal**: `@extends('v2-portal::app-layout', ['pageTitle' => ...])`. Usa as
  seções `main-header-area` e `main-section-area`, e a pilha `scripts`.
- Não há layout próprio do módulo. O tema, cabeçalho, rodapé e navegação vêm
  do Core.

### 2.1 Índice do CMS — construção das colunas

`V2/Modules/Publicacoes/Licitacoes/Views/cms/index.blade.php:1-97` monta closures de renderização e um array
`$columns` consumido pelo partial `v2-cms::partials.table`. As colunas são
`is_active` (status), `number`, `title`, `orgaos`, `value`, `anexos` e
`actions`.

```php
$renderValue = function ($row) {
    return $row->show_value
        ? 'R$ ' . number_format((float) $row->value, 2, ',', '.')
        : '<span class="text-muted small">—</span>';
};
```

(`V2/Modules/Publicacoes/Licitacoes/Views/cms/index.blade.php:27-32`)

O HTML é concatenado em strings e devolvido ao partial, que o imprime sem
escapar. Todo valor dinâmico passa por `e()` dentro das closures; a exceção é
o próprio HTML estrutural.

### 2.2 Ações por linha

- **Ativar/desativar**: `<form method="POST">` para
  `v2.licitacoes.toggle-status`, com `csrf_field()`.
- **Editar**: link para `v2.licitacoes.edit`.
- **Ver no portal**: exibido apenas quando `is_active` e `slug` não vazio;
  abre em nova aba com `rel="noopener noreferrer"`.
- **Arquivar**: botão que aciona o componente declarativo
  `data-v2-confirm` (JS do layout do CMS), passando ação, método, título,
  corpo, rótulo e ícone.

Não há ação em massa, exportação, duplicação nem visualização em modal.

### 2.3 Formulário

`V2/Modules/Publicacoes/Licitacoes/Views/cms/form.blade.php` é incluído por `create` e por `edit`. Campos:

| Grupo | Campos | Controle |
|---|---|---|
| identificação | `number`, `title`, `slug` | inputs texto |
| classificação | `status_id` | select com todos os status |
| órgãos | `orgao_ids[]` | checkboxes em grade + botão “Novo Órgão” |
| conteúdo | `object` | componente `content-rich-editor` |
| processo | `process_number`, `local`, `management_unit` | inputs texto |
| valor | `value` | input number `step=0.01` |
| datas | `publication_edital_date`, `competition_date`, `homologation_date` | inputs `type=date` |
| flags | `show_value`, `is_active` | switches com hidden `0` |
| observação | `reason` | textarea, **somente em edição** |

Código de erro: cada campo principal exibe `invalid-feedback` quando há erro;
`object` usa `text-danger` abaixo do componente. O `reason` não tem limite de
tamanho no formulário apesar de o banco ser `text`.

O componente de conteúdo é chamado assim:

```blade
@include('v2-service-contentricheditor::components.forms.content-rich-editor', [
    'name' => 'object',
    'label' => 'Descrição do objeto da licitação',
    'value' => old('object', $licitacao->object ?? '{"type":"doc","content":[]}'),
    'required' => true,
])
```

(`V2/Modules/Publicacoes/Licitacoes/Views/cms/form.blade.php:107-113`)

Ou seja: o editor espera **AST JSON**, e o valor inicial de uma licitação nova
é um documento vazio. O campo do banco é `text`, não `jsonb`, e o mutator
`setObjectAttribute` aplica `strip_tags(html_entity_decode(...))` ao valor
recebido — o JSON sobrevive porque não contém tags, mas a modelagem é
ambígua (ver `08-GAPS-E-DIVIDAS.md`).

### 2.4 Anexos na edição

`V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments.blade.php` só renderiza quando
`$licitacao->id` existe. Cada item mostra ícone, título de exibição, tamanho e
MIME, e oferece:

- input numérico de posição (`attachment-position`);
- setas para cima/baixo;
- botão de editar título;
- formulário DELETE de remoção.

A lista usa `$licitacao->anexos()->orderBy('order')->get()` — uma query
própria, além da relação já carregada pelo controller. O `max` do input de
posição é `$licitacao->anexos()->count()` (outra query por item, no laço).

O JS do final da view:

1. mantém a ordem do DOM como fonte de verdade;
2. renumera os inputs após mover;
3. envia `POST` JSON para `v2.licitacoes.attachments.reorder` com
   `{order: [ids]}` e `X-CSRF-TOKEN`;
4. em erro de rede, apenas `console.error`.

Não há estado “salvando”, feedback visual, rollback em falha nem bloqueio de
cliques concorrentes.

### 2.5 Modais

- **Upload** (`#attachmentModal`): form multipart para
  `v2.licitacoes.attachments.upload`, `accept` com as extensões permitidas,
  texto “Máx. 50MB”. O `div#attachmentError` existe mas **nunca é preenchido**
  pelo JS; erros de validação do Laravel voltam como redirect com flash,
  exibido pelo layout, não no modal.
- **Editar título** (`#editAttachmentModal`): form com method spoofing `PUT`;
  a função global `editAttachment(id, title)` ajusta a `action` substituindo
  `__ID__`. É chamada por `onclick` inline no botão do anexo
  (`V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments.blade.php:43`).
- **Órgão** (`#newOrgaoModal`): form sem `action`, interceptado por
  `fetch`; ao sucesso, cria um checkbox já marcado e fecha o modal.
- **Editar órgão** (`#editOrgaoModal`, em `V2/Modules/Publicacoes/Licitacoes/Views/cms/orgaos/index.blade.php`): função
  global `editOrgao(id, name)`.

**Duplicação**: o modal de órgão e seu JS existem em duas cópias —
`V2/Modules/Publicacoes/Licitacoes/Views/cms/create.blade.php:47-129` e
`V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/orgao-modal.blade.php:1-89`. A edição inclui a segunda;
a criação inclui a primeira. As cópias divergem em detalhes (a de `create`
não tem guarda `if (!form) return;`).

## 3. Portal — listagem

`V2/Modules/Publicacoes/Licitacoes/Views/portal/index.blade.php`:

- breadcrumb com microdados Schema.org (`BreadcrumbList`);
- título e descrição fixos;
- card de filtros com `#portalSearch` (texto) e `#portalFilterOrgao` (select
  com todos os órgãos ativos);
- tabela com colunas Nº, Título, Órgãos, Valor, Status, Anexos e ação
  “Detalhes”.

Cada linha recebe `data-orgao` com os ids dos órgãos concatenados:

```blade
<tr class="portal-row"
    data-orgao="{{implode(',', $l->orgaos->pluck('id')->toArray())}}">
```

(`V2/Modules/Publicacoes/Licitacoes/Views/portal/index.blade.php:76-77`)

O filtro é **inteiramente client-side**:

```js
rows.forEach(function(row) {
    var text = row.textContent.toLowerCase();
    var show = true;
    if (term && text.indexOf(term) === -1) show = false;
    if (org && row.dataset.orgao.indexOf(org) === -1) show = false;
    row.style.display = show ? '' : 'none';
});
```

(`V2/Modules/Publicacoes/Licitacoes/Views/portal/index.blade.php:118-128`)

Consequências:

- o servidor envia todas as linhas ativas, com `status`, `orgaos` e `anexos`
  carregados; não há paginação;
- não há contador de resultados, “nenhum resultado” dinâmico, nem preservação
  do filtro em URL;
- o filtro por órgão usa substring do id concatenado, o que funciona porque os
  ids são UUIDs separados por vírgula, mas é frágil;
- o valor e o status aparecem mesmo para linhas inativas? Não: a consulta filtra
  `active()`.

A acessibilidade tem pontos positivos (breadcrumb com `aria-current`, rótulos
nos filtros, `aria-label` nos ícones) e negativos (nenhum `aria-live` para o
resultado do filtro, tabela sem `<caption>`, links “Detalhes” sem contexto
suficiente para leitores de tela).

## 4. Portal — detalhe

`V2/Modules/Publicacoes/Licitacoes/Views/portal/show.blade.php`:

- breadcrumb de três níveis;
- `<h1>` com o número e subtítulo com o título (quando diferente do número);
- card principal com badges de status e ativo/inativo;
- **objeto** impresso cru:

```blade
<div class="licitacao-objeto">
    {!! $objectHtml !!}
</div>
```

(`V2/Modules/Publicacoes/Licitacoes/Views/portal/show.blade.php:48-52`)

- tabela de processo/local/unidade gestora e de valor/datas;
- badges de órgãos;
- sidebar com anexos (nome, tamanho, ícone, `download` com
  `original_name`) e observação (`reason`);
- nenhum botão de compartilhar, imprimir, exportar PDF ou denunciar erro.

**Ponto crítico**: `$objectHtml` vem de
`ContentRichEditorRendererService::toHtml($l->object)`. O renderer faz
`json_decode` quando recebe string e devolve `[]` se o decode falhar
(`V2/Services/ContentRichEditor/Services/ContentRichEditorRendererService.php:36-44`).
Objetos legados em texto puro — e todos os testes que criam `object => 'X'` —
renderizam **vazio**, sem cair no `catch`, porque não há exceção. O fallback
`'<p>' . e($l->object) . '</p>'` só cobre exceções.

## 5. JS e endpoints chamados

| Origem | Endpoint | Método | Formato | Tratamento de erro |
|---|---|---|---|---|
| filtro do portal | nenhum | — | — | — |
| busca do CMS | `v2.licitacoes.index` | GET | query string | submit do form |
| filtros do CMS | mesma rota | GET | query string | `data-filter-auto` dispara submit |
| criar órgão | `v2.licitacoes.orgaos.create-inline` | POST | `FormData` | mostra texto fixo no modal |
| reordenar anexos | `v2.licitacoes.attachments.reorder` | POST | JSON | `console.error` |
| editar anexo | rota PUT gerada por `__ID__` | POST + `_method=PUT` | form | redirect/flash |
| confirmar arquivamento | rota de arquivo | POST | form | componente `data-v2-confirm` |

Não há polling, WebSocket, `fetch` de listagem, upload assíncrono nem
progresso de upload.

## 6. Exportações

Nenhuma. O módulo não gera CSV, Excel, PDF, XML ou JSON de dados abertos. O
`LicitacaoDataSource` apenas alimenta widgets de Estatísticas; não é uma
exportação.

## 7. O que o operador vê vs. o que o cidadão vê

| Informação | CMS | Portal |
|---|---|---|
| `number`, `title`, `object` | sim | sim |
| `process_number`, `local`, `management_unit` | sim | sim |
| `value` | sempre (com máscara) | só se `show_value` |
| três datas | sim | sim |
| `status` | select e badge na lista | badge colorida |
| `is_active` | switch e badge | badge “Ativo/Inativo” (sempre ativo na prática) |
| `reason` | textarea | card “Observação” |
| `orgaos` | checkboxes | badges |
| `anexos` | upload/edição/ordem | lista para download |
| `jurisdicionados`, `arquivos`, `metadata`, `v1_id` | não | não |

`jurisdicionados` e `arquivos` são dados de importação sem UI. `metadata` é
opaco. `v1_id` não é exibido nem filtrável.

## 8. UX e acessibilidade — avaliação

**Pontos fortes**

- formulário em grade responsiva, com labels explícitos;
- confirmação antes de arquivar/excluir e `aria-label` nos botões icônicos;
- breadcrumbs com dados estruturados;
- `download` com o nome original do arquivo;
- mensagens de validação em português.

**Problemas**

1. `onclick`/`onsubmit` inline (`V2/Modules/Publicacoes/Licitacoes/Views/cms/includes/attachments.blade.php:43,49`;
   `V2/Modules/Publicacoes/Licitacoes/Views/cms/orgaos/index.blade.php:74,78`) — incompatível com CSP sem `unsafe-inline`
   (ADR-022) e com o padrão V7 sem jQuery/JS inline.
2. `confirm()` nativo em vez de `<dialog>` acessível.
3. Filtro do portal sem `aria-live`/contagem; usuários de leitor de tela não
   sabem que linhas sumiram.
4. Modal de upload não exibe erro de validação no contexto.
5. A posição numérica do anexo não desabilita enquanto salva; cliques rápidos
   podem gerar requisições fora de ordem.
6. Não há indicação de “rascunho” vs “publicado”; `is_active` e `status_id`
   podem se contradizer visualmente.
7. O detalhe mostra badge “Ativo” ou “Inativo”, mas a rota só serve ativos:
   o badge “Inativo” é código morto no portal.
8. `str_limit()` (helper do Laravel 5.5) é usado nas listas; no V7 deve ser
   substituído por `Str::limit`.
