# 📊 Sistema de Painel Dinâmico - Documentação

## 🎯 Objetivo

Centralizar toda a lógica comum dos painéis do sistema em um único arquivo JavaScript (`painel.js`), eliminando duplicação de código e facilitando manutenção.

---

## 📁 Arquivos do Sistema

```
sag-adm/
├── js/
│   ├── painel.js                    # Classe centralizada (CORE)
│   ├── painel-exemplo-uso.js        # Exemplos de implementação
│   ├── mapa.js                      # Funções de mapas
│   └── matrix.js                    # Funções de matrizes
├── css/
│   └── painel.css                   # Estilos dos painéis
├── include/
│   ├── salvarlayoutpainel.php       # API para salvar layouts
│   ├── excluirlayoutpainel.php      # API para excluir layouts
│   └── filtros_db.php               # Filtros comuns
└── [modulo]/
    └── painel.php                   # Painel específico do módulo
```

---

## 🚀 Como Usar

### Passo 1: Incluir as bibliotecas necessárias

No seu arquivo `painel.php`, adicione:

```html
<!-- GridStack -->
<link href="https://cdn.jsdelivr.net/npm/gridstack@9.2.2/dist/gridstack.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/gridstack@9.2.2/dist/gridstack-all.js"></script>

<!-- Bibliotecas adicionais (conforme necessário) -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<!-- Estilos e scripts do sistema -->
<link rel="stylesheet" href="../css/painel.css" />
<script src="../js/mapa.js"></script>
<script src="../js/matrix.js"></script>

<!-- CORE DO PAINEL -->
<script src="../js/painel.js"></script>
```

### Passo 2: Preparar os dados no PHP

```php
<?php
// Configurações básicas
$usuario = $_SESSION['UsuarioID'];
$rotina = 'meu_modulo_painel'; // Nome único da rotina

// Layout padrão
$layout = [
    ["tipo" => "w_widget1", "x" => 0, "y" => 0, "w" => 4, "h" => 4],
    ["tipo" => "w_widget2", "x" => 4, "y" => 0, "w" => 4, "h" => 4]
];

// Recuperar layout salvo do banco...
// (código de recuperação aqui)

// Filtros
$filtros = [
    'data_inicio' => $_POST['data_inicio'] ?? date('Y-m-01'),
    'data_fim' => $_POST['data_fim'] ?? date('Y-m-t'),
    // ... outros filtros
];

// WHERE para as consultas dos widgets
$where = "empresa = $1";
$params = [$codempresa];
?>
```

### Passo 3: Passar dados para o JavaScript

```html
<script>
// Variáveis globais
window.filtrosPainel = {
    data_inicio: '<?=htmlspecialchars($filtros['data_inicio'])?>',
    data_fim: '<?=htmlspecialchars($filtros['data_fim'])?>',
    where_clause: '<?=htmlspecialchars($where)?>',
    params_json: '<?=htmlspecialchars(json_encode($params))?>'
};

window.layoutPainel = <?php echo json_encode($layout); ?>;
</script>
```

### Passo 4: Inicializar o painel

```javascript
document.addEventListener('DOMContentLoaded', function() {
    
    // Criar instância do painel
    window.painelApp = new PainelDinamico({
        // Identificação única
        rotina: 'meu_modulo_painel',
        
        // Configurações do GridStack
        cellHeight: 50,
        margin: 3,
        minRow: 1,
        delayCarregamento: 300,
        
        // Mapeamento de widgets
        widgets: {
            'w_widget1': 'Título do Widget 1',
            'w_widget2': 'Título do Widget 2',
            'w_widget3': 'Título do Widget 3'
        },
        
        // Filtros que serão enviados aos widgets
        filtros: window.filtrosPainel,
        
        // Debug
        debug: true
    });
    
    // Inicializar e carregar layout
    if (painelApp.inicializar()) {
        painelApp.carregarLayout(window.layoutPainel);
    }
});
```

### Passo 5: Estrutura HTML necessária

```html
<div class="inner-wrapper">
    <section role="main" class="content-body">
        <div class="row">
            <div class="col">
                <header class="page-header">
                    <div class="card-actions">
                        <div class="btn-group">
                            <!-- Seletor de layout -->
                            <select id="select-layout" class="form-control form-control-sm mr-2">
                                <!-- Options aqui -->
                            </select>
                            
                            <!-- Botões padrão -->
                            <button class="btn btn-sm btn-success mr-2" id="btn-salvar-novo-layout">
                                <i class="fas fa-save"></i> Salvar Novo Layout
                            </button>
                            <button class="btn btn-sm btn-warning mr-2" id="btn-atualizar-layout">
                                <i class="fas fa-sync"></i> Atualizar Layout
                            </button>
                            <button class="btn btn-sm btn-danger mr-2" id="btn-excluir-layout">
                                <i class="fas fa-trash"></i> Excluir Layout
                            </button>
                            
                            <!-- Dropdown de widgets -->
                            <button class="btn btn-sm btn-primary dropdown-toggle mr-2" data-toggle="dropdown">
                                <i class="material-symbols-outlined">add</i> Adicionar Widget
                            </button>
                            <div class="dropdown-menu">
                                <a class="dropdown-item" href="#" data-widget="w_widget1">Widget 1</a>
                                <a class="dropdown-item" href="#" data-widget="w_widget2">Widget 2</a>
                            </div>
                            
                            <button class="btn btn-sm btn-secondary mr-2" id="btn-filtros">
                                <i class="fas fa-filter"></i> Filtros
                            </button>
                            <button class="btn btn-sm btn-dark mr-2" id="btn-exportar-pdf">
                                <i class="fas fa-file-pdf"></i> PDF
                            </button>
                        </div>
                    </div>
                </header>
                
                <!-- Painel de filtros -->
                <div id="filtros-painel" style="display:none;">
                    <form id="form-filtros" method="post">
                        <!-- Campos de filtro aqui -->
                        <button type="submit" class="btn btn-primary">Aplicar Filtros</button>
                        <button type="button" class="btn btn-link" id="btn-fechar-filtros">Fechar</button>
                    </form>
                </div>
                
                <!-- Grid -->
                <div class="card-body">
                    <div class="grid-stack"></div>
                </div>
            </div>
        </div>
    </section>
</div>
```

---

## ⚙️ Opções de Configuração

### Configurações Básicas

```javascript
{
    rotina: 'nome_unico',           // Identificador da rotina no banco
    cellHeight: 50,                 // Altura base das células do grid
    margin: 3,                      // Espaço entre widgets
    minRow: 1,                      // Número mínimo de linhas
    delayCarregamento: 300,         // Delay entre carregar widgets (ms)
    debug: true                     // Mostrar logs no console
}
```

### Configuração Avançada do GridStack

```javascript
{
    gridConfig: {
        cellHeight: 60,
        float: true,
        column: 12,
        margin: 5,
        resizable: { handles: 'e, se, s, sw, w' }
    }
}
```

### Mapeamento de Widgets

```javascript
{
    widgets: {
        'w_identificador': 'Título Exibido no Widget',
        'w_outro_widget': 'Outro Título'
    }
}
```

### Filtros

```javascript
{
    filtros: {
        data_inicio: '2025-01-01',
        data_fim: '2025-12-31',
        idveiculo: '123',
        // ... qualquer parâmetro que os widgets precisem
    }
}
```

### Callbacks Personalizados

```javascript
{
    // Executado após carregar cada widget
    onWidgetCarregado: function(tipo, contentDiv) {
        console.log('Widget carregado:', tipo);
    },
    
    // Customiza mensagem de erro
    onErroCarregamento: function(tipo, error) {
        return '<div>Erro personalizado</div>';
    },
    
    // Customiza popup de mapas
    mapaPopupFn: function(m) {
        return '<b>Dado:</b> ' + m.campo;
    }
}
```

---

## 🔧 Métodos Públicos

### `painelApp.inicializar()`
Inicializa o GridStack e configura eventos.

```javascript
if (painelApp.inicializar()) {
    console.log('Sucesso!');
}
```

### `painelApp.carregarLayout(layout)`
Carrega um array de widgets no grid.

```javascript
painelApp.carregarLayout([
    { tipo: 'w_widget1', x: 0, y: 0, w: 4, h: 4 }
]);
```

### `painelApp.adicionarWidget(tipo)`
Adiciona um widget específico ao grid.

```javascript
painelApp.adicionarWidget('w_meu_widget');
```

### `painelApp.removerWidget(btn)`
Remove um widget (geralmente chamado pelo botão X).

```javascript
<button onclick="painelApp.removerWidget(this)">×</button>
```

### `painelApp.recarregarWidget(tipo)`
Recarrega o conteúdo de um widget específico.

```javascript
painelApp.recarregarWidget('w_widget1');
```

### `painelApp.recarregarTodosWidgets()`
Recarrega todos os widgets do painel.

```javascript
painelApp.recarregarTodosWidgets();
```

### `painelApp.atualizarFiltros(novosFiltros)`
Atualiza os filtros e recarrega todos os widgets.

```javascript
painelApp.atualizarFiltros({
    data_inicio: '2025-06-01',
    data_fim: '2025-06-30'
});
```

### `painelApp.salvarNovoLayout()`
Solicita nome e salva o layout atual como novo.

### `painelApp.atualizarLayout()`
Atualiza o layout selecionado com a configuração atual.

### `painelApp.excluirLayout()`
Exclui o layout selecionado.

### `painelApp.exportarPDF()`
Exporta o painel para PDF.

---

## 📝 Estrutura dos Widgets

Cada widget deve ser um arquivo PHP separado (ex: `w_widget1.php`):

```php
<?php
// Recebe os filtros via GET
$data_inicio = $_GET['data_inicio'] ?? '';
$data_fim = $_GET['data_fim'] ?? '';
$where_clause = $_GET['where_clause'] ?? '';
$params_json = $_GET['params_json'] ?? '[]';

// Processa os dados
$params = json_decode($params_json, true);

// Conecta ao banco
// Consulta
$query = "SELECT COUNT(*) as total FROM tabela WHERE $where_clause";
$result = pg_query_params($bdcon, $query, $params);
$data = pg_fetch_assoc($result);

// Retorna o HTML
?>
<div class="widget-kpi">
    <div class="kpi-value"><?= $data['total'] ?></div>
    <div class="kpi-label">Total de Registros</div>
</div>

<style>
.widget-kpi {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
}
.kpi-value {
    font-size: 48px;
    font-weight: bold;
    color: #2196f3;
}
.kpi-label {
    font-size: 14px;
    color: #666;
}
</style>
```

---

## 🗺️ Widgets com Mapas

Para widgets que usam mapas, use a classe especial:

```html
<div class="map-meuwidget-widget" style="height: 400px;"></div>

<script>
// Os dados do mapa devem estar em window.dadosMapa
window.dadosMapa = <?= json_encode($marcadores) ?>;
</script>
```

O `painel.js` detectará automaticamente e inicializará o mapa usando `mapa.js`.

---

## 📊 Widgets com Gráficos (Chart.js)

```html
<canvas id="meuGrafico" width="400" height="200"></canvas>

<script>
var ctx = document.getElementById('meuGrafico').getContext('2d');
var chart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: <?= json_encode($labels) ?>,
        datasets: [{
            label: 'Dados',
            data: <?= json_encode($valores) ?>,
            backgroundColor: 'rgba(33, 150, 243, 0.7)'
        }]
    }
});
</script>
```

---

## 🔍 Debug e Troubleshooting

### Habilitar modo debug

```javascript
window.painelApp = new PainelDinamico({
    debug: true  // Mostra logs detalhados no console
});
```

### Console de erros

Abra o console do navegador (F12) para ver:
- Logs de inicialização
- URLs carregadas
- Erros de requisição
- Status de cada widget

### Problemas comuns

1. **GridStack não carrega**: Verifique se o CDN está acessível
2. **Widgets não aparecem**: Verifique se `layoutPainel` foi definido
3. **Mapas não funcionam**: Verifique se `mapa.js` foi carregado
4. **Filtros não funcionam**: Verifique se `window.filtrosPainel` está definido

---

## 🎨 Customização de Estilos

Os widgets herdam estilos de `painel.css`. Você pode sobrescrever:

```css
.widget {
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.widget-header {
    background: #2196f3;
    color: white;
    padding: 10px;
    cursor: move;
}

.widget-content {
    padding: 15px;
}
```

---

## 📦 Migração de Painéis Existentes

Para migrar um painel existente:

1. **Inclua o `painel.js`**
2. **Extraia a configuração de widgets** para o objeto `widgets`
3. **Substitua o código de inicialização** por `new PainelDinamico({...})`
4. **Remova código duplicado** (eventos, carregamento, etc)
5. **Teste** cada widget individualmente

---

## 📞 Suporte

Para dúvidas ou problemas:
- Consulte os exemplos em `painel-exemplo-uso.js`
- Use o template em `_template_painel.php`
- Verifique painéis existentes (TMS001C, JOR005) como referência

---

## 📜 Licença

© 2025 SAG Tecnologia - Todos os direitos reservados
