# Estagiários — Views, UX e Front-end

> Versão analisada: `v5`. O módulo não existe na `v6`.

---

## 1. Telas do CMS

| Arquivo | Função |
|---|---|
| `v5/resources/views/prefecture/estagiarios/index.blade.php` | Listagem; estende `admin.layouts.app`; contém apenas o `<table id="tabela_estagiario">` (tbody preenchido por JS) e o include do JS do DataTable |
| `v5/resources/views/prefecture/estagiarios/create.blade.php` | Formulário de cadastro; `Form::open(['route' => 'estagiario.store', 'method' => 'POST', 'enctype' => 'multipart/form-data'])` |
| `v5/resources/views/prefecture/estagiarios/edit.blade.php` | Formulário de edição; `Form::model($estagiario, ['route' => ['estagiario.update', $estagiario->id], 'method' => 'PUT', ...])` |
| `v5/resources/views/prefecture/estagiarios/form.blade.php` | Partial compartilhada entre create/edit: campos, construtor de arquivos e listagem de anexos existentes |

### 1.1 Estrutura do `index.blade.php` (CMS)

```blade
<table id="tabela_estagiario" class="table table-striped table-hover w-100">
    <thead>
        <tr>
            <th scope="col" width="5%">ID</th>
            <th scope="col">Nome</th>
            <th scope="col">Período Início</th>
            <th scope="col">Período Fim</th>
            <th scope="col">Lotação</th>
            <th scope="col">Situação</th>
            <th scope="col" class="text-center" width="15%">Ações</th>
        </tr>
    </thead>
    <tbody>
        {{-- Preenchido via Datatable JS --}}
    </tbody>
</table>
```

O botão "Cadastrar" é condicionado à permissão:

```blade
@if (has_access(['estagiario.create']))
    <a href="{{ route('estagiario.create') }}" class="btn btn-success font-weight-bold btn-sm" role="button">
        <i class="fa fa-plus" aria-hidden="true"></i> Cadastrar
    </a>
@endif
```

### 1.2 Campos do formulário (`form.blade.php`)

| Campo | Controle | Origem do valor | Observações |
|---|---|---|---|
| `nome` | `components/inputs/input.blade.php` (`size 6`, `maxlen 255`) | `old('nome')` ou `$estagiario->nome` | Obrigatório |
| `lotacao` | `<select>` | `$secretarias` (id + `titulo as name`) | Primeira opção vazia "Selecione..." — viola a regra `required` |
| `situacao` | `<select>` | lista fixa `['Ativo', 'Encerrado', 'Rescindido']` | Regra do Request não restringe (`required|string`) |
| `periodo_inicio` | `components/inputs/date.blade.php` (`mask br-data`, `date_format d/m/Y`) | `date('d/m/Y', strtotime(...))` | Texto livre, sem `type=date` |
| `periodo_fim` | `components/inputs/date.blade.php` | idem | Idem |
| `arquivo[]` | `<input type="file" accept=".pdf">` dinâmico | JS | Linhas clonadas pelo botão `#btn-adicionar` |
| `arquivo_desc[]` | `<input type="text">` dinâmico | JS | Descrição exibida na listagem de anexos |

A fila de anexos é montada inteiramente em JavaScript dentro da própria view:

```js
$('#btn-adicionar').click(function () {
    var novoInput = $('<div class="data-itens" style="margin-top: 10px;">' +
        '<div class="row">' +
        '<div class="col-md-4 col-sm-12 mb-2">' +
        '<input type="file" name="arquivo[]" class="form-control" accept=".pdf">' +
        '</div>' +
        '<div class="col-md-4 col-sm-12 mb-2">' +
        '<input type="text" name="arquivo_desc[]" class="form-control" placeholder="Informe a descrição">' +
        '</div>' +
        '<button type="button" class="js-remove-itens btn btn-sm btn-danger mb-2" title="Remover Linha" style="margin-left: 15px; height: 35px;">' +
        '<i class="fa fa-trash"></i>' +
        '</button>' +
        '</div>' +
        '</div>');

    novoInput.hide();
    $('.itens-wrapper .data-itens:last').after(novoInput);
    novoInput.fadeIn('fast');
    $('.description').css('display', 'block');
    return false;
});
```

Arquivos já enviados são exibidos com três ações (download, visualizar e
deletar). O botão de deletar dispara um `DELETE` via AJAX:

```js
$.ajax({
    url: url,
    type: 'DELETE',
    data: { _token: $('meta[name="csrf-token"]').attr('content') },
    success: function () {
        row.fadeOut(300, function () { $(this).remove(); });
    },
    error: function () {
        alert('Erro ao remover o arquivo. Tente novamente.');
    }
});
```

## 2. Telas do portal

O controller escolhe o template pelo valor de `configurations.layout`
(`getTemplateActived()`, `v5/app/Http/Controllers/Admin/Controller.php:39-50`).
No banco de desenvolvimento o valor é `modelo1`.

| Arquivo (por template) | Função |
|---|---|
| `v5/resources/views/portal/layouts/modelo1/estagiarios/index.blade.php` (idem `modelo2`..`modelo6`) | Página de listagem + filtros + tabela |
| `v5/resources/views/portal/layouts/modelo1/estagiarios/filtro.blade.php` (idem `modelo2`..`modelo6`) | Formulário de filtros e botões de exportação |
| `v5/resources/views/portal/layouts/modelo1/estagiarios/show.blade.php` (idem `modelo2`..`modelo6`) | Ficha individual do estagiário |
| `v5/resources/views/portal/components/estagiarios/table.blade.php` | Tabela paginada compartilhada por todos os templates |

As seis cópias são idênticas exceto pelo `@extends` / `@include` do próprio
layout (`portal.layouts.modeloN.layout.layout`) e por uma classe CSS
(`text-right` em `modelo1`..`modelo4`, `text-end` em `modelo5`/`modelo6`).

> **Armadilha:** não existe a pasta `estagiarios` no template `default`
> (nem em `newsbit`). Se `configurations.layout` for alterado para `default`,
> `view('portal.layouts.default.estagiarios.index')` estoura
> `InvalidArgumentException` (view inexistente). O próprio
> `getTemplateActived()` só valida a existência de
> `portal.layouts.{layout}.home.index`.

### 2.1 Listagem (portal)

`index.blade.php` do portal:

```blade
@include('portal.layouts.modelo1.estagiarios.filtro')
<br>
@include('portal.components.estagiarios.table')

@if ($result->isEmpty())
    <div class="text-center mt-3">
        <p>Nenhuma informação encontrada!</p>
    </div>
@endif
```

A tabela compartilhada (`portal/components/estagiarios/table.blade.php`) tem as
colunas Nome, Período do Contrato (início "a" fim), Lotação, Situação e
Detalhes; o link de detalhes usa o `slug`:

```blade
<a class="btn btn-link" href="{{ route('estagiarios.show', $estagiario->slug') }}"
    data-toggle="tooltip" data-title="Visualizar" title="Visualizar">
    <i class="fas fa-search txt-color-1"></i>
</a>
```

A paginação mantém os filtros:

```blade
{{ $result->appends(request()->query())->links() }}
```

### 2.2 Filtros (portal)

`filtro.blade.php` envia um `GET` para `estagiarios.index`:

| Campo | Nome | Comportamento |
|---|---|---|
| Buscar por Nome | `busca` | Texto livre; filtra com `whereRaw` + `translate(...)` |
| Secretaria / Órgão | `lotacao` | `<select>` com os títulos vindos da view `secretarias` |
| Situação | `situacao` | `<select>` com `Ativo`, `Encerrado`, `Rescindido` |

A exportação é disparada pelo próprio formulário:

```js
function exportarEstagiarios(type) {
    var valores = $('#form-filter').serialize();
    valores = valores + '&type=' + type;
    window.location.href = '{{ route('estagiarios.index') }}?' + valores;
}
```

O botão "Limpar campos" apenas zera os campos do formulário (não submete):

```js
function limparFiltros() {
    $('input[name="busca"]').val('');
    $('select[name="lotacao"]').val('');
    $('select[name="situacao"]').val('');
}
```

### 2.3 Ficha individual (portal)

`show.blade.php` exibe nome, período (início/fim em `d/m/Y`), lotação, situação e
a lista de arquivos para download:

```blade
<a href="{{ url('storage/' . $item['path']) }}" target="_blank"
    class="text-dark" data-toggle="tooltip"
    data-placement="right" data-title="Clique para baixar">
```

O `path` vem do JSON da coluna `arquivo`; o link é público (sem autenticação).

## 3. JavaScript do CMS

`v5/public/assets/js/custom/config/estagiarios/datatable.js`

```js
let collums = [
    {
        data: 'nome',
        render: function (data) {
            return `<div class='vertical-middle' style='padding:8px'>${data ?? '-'}</div>`;
        }
    },
    {
        data: 'periodo_inicio',
        render: function (data) {
            if (!data) return '-';
            const d = new Date(data);
            return `<div class='vertical-middle' style='padding:8px'>${d.toLocaleDateString('pt-BR', {timeZone: 'UTC'})}</div>`;
        }
    },
    {
        data: 'periodo_fim',
        render: function (data) {
            if (!data) return '-';
            const d = new Date(data);
            return `<div class='vertical-middle' style='padding:8px'>${d.toLocaleDateString('pt-BR', {timeZone: 'UTC'})}</div>`;
        }
    },
    {
        data: 'lotacao',
        render: function (data) {
            return `<div class='vertical-middle' style='padding:8px'>${data ?? '-'}</div>`;
        }
    },
    {
        data: 'situacao',
        render: function (data) {
            return `<div class='vertical-middle' style='padding:8px'>${data ?? '-'}</div>`;
        }
    },
    {
        data: null,
        orderable: false,
        className: 'text-center',
        render: function (data) {
            return `<div class='flex justify-center'>`
                + createBTN(data.id, `estagiarios/editar/${data.id}`, data.edit, data.delete, `estagiarios/excluir/${data.id}`)
                + `</div>`;
        }
    }
];

runDatatables('estagiario', collums);
```

Pontos técnicos:

- `runDatatables` (`v5/public/assets/js/custom/utilities/table.js:3`) injeta uma
  coluna extra no início com `data: 'value'` (a primeira coluna da view
  `estagiarios_view`, ou seja, o `row_number()`, não o `id`).
- O AJAX é montado internamente: `url: '/cms/datatable/${url}'`, `type: 'GET'`,
  `contentType: 'application/json'` → `/cms/datatable/estagiario`.
- As URLs de edição/exclusão são **relativas** (`estagiarios/editar/{id}`,
  `estagiarios/excluir/{id}`), resolvidas contra a URL atual `/cms/estagiarios`.
  Funciona, mas quebra se a listagem for aberta em outra URL base.
- O botão de exclusão usa o atributo `target`, que `addDelete()`
  (`v5/public/assets/js/custom/utilities/table.js:357-380`) transforma na URL do
  `DELETE` via `myFetch`.
- `runDatatables` também habilita os botões **Excel, PDF, CSV e TXT** nativos do
  DataTable (client-side), com título "Prefeitura de {cidade} - {data}".

## 4. Exportações

| Formato | Como é gerado | Observação |
|---|---|---|
| XLSX | `Excel::download(new EstagiariosExport($estagiarios), 'estagiarios.xlsx')` | `EstagiariosExport` implementa `FromView`; a planilha é gerada a partir do HTML da view |
| CSV | `Excel::download(new EstagiariosExport($estagiarios), 'estagiarios.csv')` | O arquivo tem **extensão `.csv`**, mas o conteúdo é o HTML da view (não CSV real) |
| PDF | `PDF::loadView('exports.estagiarios', compact('estagiarios'))->download('estagiarios.pdf')` | `barryvdh/laravel-dompdf`; a view detecta `$pdf` e acrescenta cabeçalho com nome da prefeitura e data da pesquisa |
| TXT | `exportTxt()` no controller | Texto alinhado com `|`; `Content-Type: plain/txt` (MIME não padrão) |

A view de exportação (`v5/resources/views/exports/estagiarios.blade.php`) é
compartilhada entre Excel, CSV e PDF:

```blade
@if(isset($pdf))
    <h4 style="margin-bottom: 5px">Estagiários - {{config('app.name')}}</h4>
    <small style="font-size: 8px">Data da pesquisa: {{date('d/m/Y H:i')}}</small>
@endif
```

No caminho do Excel/CSV a variável `$pdf` não existe, então o cabeçalho **não**
aparece — a planilha sai sem título nem data da pesquisa.

> Observação: o cabeçalho é impresso **apenas** no PDF; nos demais formatos o
> arquivo começa direto na tabela.

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

**Portal (cidadão)**

1. `/estagiarios` → título "Estagiários", card de filtros, tabela com
   "Nenhum dado encontrado." quando vazia, paginação e contador
   "Exibindo X a Y de Z registros".
2. `/estagiario/{slug}` → ficha com os dados e a lista "Arquivos para Download".

**CMS (operador)**

1. `/cms/estagiarios` → tabela AdminLTE, coluna "ID" (numeração da view),
   botões de ação por linha (editar/excluir) e os botões de exportação do
   DataTable.
2. `/cms/estagiarios/cadastrar` e `/cms/estagiarios/editar/{id}` → formulário
   com mensagens de erro em faixa vermelha (`$errors->all()`), indicador de
   campos obrigatórios e botão "Salvar" que chama `desabilitaBotao()`.

### 5.1 Acessibilidade e responsividade

- As tabelas do portal usam `table-responsive-lg` com `overflow: auto`.
- Os filtros usam `col-md-4` (três colunas) e o botão de consulta fica
  alinhado à direita (`justify-content-end`).
- Não há rótulos `aria-*` nem papel `role` além do `scope="col"` dos cabeçalhos.

## 6. Menu e navegação

- **CMS:** item "Estagiários" no menu do AdminLTE, ícone `id-card-o`, apontando
  para `/cms/estagiarios` (`v5/app/Providers/MenuServiceProvider.php:370-372`).
- **Portal:** não existe link estático em nenhuma view do portal
  (`grep` por `estagiarios` nas views do portal retorna apenas o componente de
  tabela) nem registro em `contents`/`links` no banco. A página é acessível
  **apenas por URL direta**.
