‹ 首页

data-build-dashboard

@evolution-foundation · 收录于 5 天前 · 上游提交 2 个月前

Constrói um dashboard HTML interativo com gráficos, filtros e tabelas no tema Evolution (fundo escuro, acento

适合你,如果需要快速生成带图表和筛选器的数据看板。

/ 通过 npx 安装 校验哈希
npx oh-my-skill add evolution-foundation/evo-nexus/data-build-dashboard
/ 通过 bash 安装
curl -fsSL https://oh-my-skill.com/install.sh | bash -s -- evolution-foundation/evo-nexus/data-build-dashboard
/ 已经装过?验证本机副本,不用重装
npx oh-my-skill verify evolution-foundation/evo-nexus/data-build-dashboard
安装目标可用 --agent / --scope 或 --to 明确指定;省略时只会在唯一已存在的 agent 目录上自动选择,零命中或多命中会停止并提示。content_hash 缺失或不一致均拒装。
509GitHub stars
~4.8K上下文体积 · 单文件
索引托管

怎么用

商店整理自技能原文 · 版本 7f5dd76 · 表述以原文为准
它做什么

生成一个暗色主题的交互式HTML仪表板,包含KPI卡片、图表、过滤器和排序表格,可直接在浏览器中打开。

什么时候触发

当你需要将数据转为可视化报告,或要求创建含多个图表和过滤器的执行摘要时触发。

装好后可以这样说
Claude会生成对应的HTML文件。
Claude会调用int-stripe获取数据。
Claude会解析CSV并嵌入到HTML中。
技能原文 SKILL.md作者撰写 · Apache-2.0 · 7f5dd76

data-build-dashboard — Construir Dashboards Interativos

Gera um arquivo HTML auto-contido com gráficos, filtros, tabelas e estilo profissional no tema Evolution. Abre diretamente no browser — sem servidor ou dependências externas.

Uso
/data-build-dashboard <descrição do dashboard> [fonte de dados]
Fluxo de Trabalho
1. Entender os Requisitos do Dashboard

Determinar:

  • Propósito: Visão executiva, monitoramento operacional, análise aprofundada, relatório de time
  • Audiência: Quem vai usar este dashboard?
  • Métricas-chave: Quais números importam mais?
  • Dimensões: Por quais campos o usuário deve conseguir filtrar ou segmentar?
  • Fonte de dados: Query ao vivo, dados colados, arquivo CSV, ou dados de exemplo
2. Coletar os Dados

Se fonte de dados estiver disponível via skill:

  1. Usar int-stripe, int-omie, int-licensing ou int-evo-crm conforme necessário
  2. Executar as queries PostgreSQL necessárias
  3. Incorporar os resultados como JSON dentro do arquivo HTML

Se dados forem colados ou enviados como arquivo:

  1. Fazer parse e limpeza dos dados
  2. Incorporar como JSON no dashboard

Se trabalhando a partir de uma descrição sem dados:

  1. Criar um dataset de exemplo realista que corresponda ao schema descrito
  2. Indicar no dashboard que usa dados de exemplo
  3. Fornecer instruções para substituir pelos dados reais
3. Desenhar o Layout do Dashboard

Seguir o padrão de layout padrão:

┌──────────────────────────────────────────────────┐
│  Título do Dashboard               [Filtros ▼]   │
├────────────┬────────────┬────────────┬───────────┤
│  KPI Card  │  KPI Card  │  KPI Card  │ KPI Card  │
├────────────┴────────────┼────────────┴───────────┤
│                         │                        │
│    Gráfico Principal    │   Gráfico Secundário   │
│    (maior área)         │                        │
│                         │                        │
├─────────────────────────┴────────────────────────┤
│                                                  │
│    Tabela de Detalhes (ordenável, rolável)        │
│                                                  │
└──────────────────────────────────────────────────┘

Adaptar o layout ao conteúdo:

  • 2-4 cards de KPI no topo para números principais
  • 1-3 gráficos na seção intermediária para tendências e breakdowns
  • Tabela de detalhes opcional na parte inferior para drill-down
  • Filtros no cabeçalho ou barra lateral conforme a complexidade
4. Construir o Dashboard HTML

Gerar um único arquivo HTML auto-contido seguindo o template base abaixo. O arquivo inclui:

Estrutura (HTML):

  • Layout HTML5 semântico
  • Grid responsivo usando CSS Grid ou Flexbox
  • Controles de filtro (dropdowns, seletores de data, toggles)
  • Cards de KPI com valores e labels
  • Containers de gráficos
  • Tabela de dados com cabeçalhos ordenáveis

Estilo (CSS) — Tema Evolution:

  • Fundo escuro com camadas (#0A0A0A, #111111, #1A1A1A)
  • Acento Evolution verde (#00FFA7) para destaques, bordas ativas e valores positivos
  • Fonte Inter (Google Fonts CDN)
  • Cards com bordas sutis e sombras escuras
  • Design responsivo para diferentes tamanhos de tela

Interatividade (JavaScript):

  • Chart.js para gráficos interativos (via CDN)
  • Filtros dropdown que atualizam todos os gráficos e tabelas simultaneamente
  • Colunas de tabela ordenáveis
  • Tooltips de hover nos gráficos
  • Formatação de números (vírgulas, moeda R$, percentuais)

Dados (JSON incorporado):

  • Todos os dados incorporados diretamente no HTML como variáveis JavaScript
  • Sem requisições de dados externos necessárias
  • Dashboard funciona completamente offline
5. Implementar Tipos de Gráfico

Usar Chart.js para todos os gráficos. Padrões comuns para dashboards:

  • Gráfico de linha: Tendências de séries temporais
  • Gráfico de barras: Comparações de categorias
  • Gráfico de rosca (doughnut): Composição (quando <6 categorias)
  • Barras empilhadas: Composição ao longo do tempo
  • Misto (barras + linha): Volume com sobreposição de taxa
6. Adicionar Interatividade

Usar os padrões de filtro e interatividade abaixo para filtros dropdown, filtros de intervalo de data, lógica de filtro combinada, tabelas ordenáveis e atualizações de gráficos.

7. Salvar e Abrir
  1. Salvar o dashboard como arquivo HTML com nome descritivo (ex: dashboard_financeiro_abril.html)
  2. Abrir no browser padrão do usuário
  3. Confirmar que renderiza corretamente
  4. Fornecer instruções para atualizar dados ou personalizar

Template Base — Tema Evolution Dark

Todo dashboard segue esta estrutura:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Título do Dashboard</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.1" integrity="sha384-jb8JQMbMoBUzgWatfe6COACi2ljcDdZQ2OxczGA3bGNeWe+6DChMTBJemed7ZnvJ" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0" integrity="sha384-cVMg8E3QFwTvGCDuK+ET4PD341jF3W8nO1auiXfuZNQkzbUUiBGLsIQUE+b1mxws" crossorigin="anonymous"></script>
    <style>
        /* Estilos do dashboard vão aqui */
    </style>
</head>
<body>
    <div class="dashboard-container">
        <header class="dashboard-header">
            <div class="header-left">
                <div class="logo-badge">EVO</div>
                <h1>Título do Dashboard</h1>
            </div>
            <div class="filters">
                <!-- Controles de filtro -->
            </div>
        </header>

        <section class="kpi-row">
            <!-- Cards de KPI -->
        </section>

        <section class="chart-row">
            <!-- Containers de gráficos -->
        </section>

        <section class="table-section">
            <!-- Tabela de dados -->
        </section>

        <footer class="dashboard-footer">
            <span>Dados até: <span id="data-date"></span> • BRT (UTC-3)</span>
        </footer>
    </div>

    <script>
        // Dados incorporados
        const DATA = [];

        // Lógica do dashboard
        class Dashboard {
            constructor(data) {
                this.rawData = data;
                this.filteredData = data;
                this.charts = {};
                this.init();
            }

            init() {
                this.setupFilters();
                this.renderKPIs();
                this.renderCharts();
                this.renderTable();
            }

            applyFilters() {
                this.filteredData = this.rawData.filter(row => {
                    return true; // placeholder
                });
                this.renderKPIs();
                this.updateCharts();
                this.renderTable();
            }
        }

        const dashboard = new Dashboard(DATA);
    </script>
</body>
</html>
Sistema de Cores Evolution Dark
:root {
    /* Camadas de fundo */
    --bg-base: #0A0A0A;
    --bg-surface: #111111;
    --bg-card: #1A1A1A;
    --bg-elevated: #222222;

    /* Texto */
    --text-primary: #F0F0F0;
    --text-secondary: #888888;
    --text-muted: #555555;

    /* Acento Evolution */
    --accent: #00FFA7;
    --accent-dim: rgba(0, 255, 167, 0.15);
    --accent-border: rgba(0, 255, 167, 0.3);

    /* Status */
    --positive: #00FFA7;
    --negative: #FF4D4D;
    --warning: #FFB547;
    --neutral: #888888;

    /* Bordas */
    --border: rgba(255, 255, 255, 0.08);
    --border-accent: rgba(0, 255, 167, 0.3);

    /* Tipografia */
    --font: 'Inter', -apple-system, sans-serif;

    /* Espaçamento */
    --gap: 16px;
    --radius: 10px;
    --radius-sm: 6px;
}
Layout CSS
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: var(--font);
    background: var(--bg-base);
    color: var(--text-primary);
    line-height: 1.5;
}

.dashboard-container {
    max-width: 1400px;
    margin: 0 auto;
    padding: var(--gap);
}

.dashboard-header {
    background: var(--bg-surface);
    border: 1px solid var(--border);
    border-bottom: 1px solid var(--accent-border);
    padding: 18px 24px;
    border-radius: var(--radius);
    margin-bottom: var(--gap);
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.header-left {
    display: flex;
    align-items: center;
    gap: 12px;
}

.logo-badge {
    background: var(--accent);
    color: #000;
    font-weight: 700;
    font-size: 11px;
    letter-spacing: 1px;
    padding: 4px 8px;
    border-radius: 4px;
}

.dashboard-header h1 {
    font-size: 18px;
    font-weight: 600;
    color: var(--text-primary);
}

.dashboard-footer {
    text-align: center;
    font-size: 12px;
    color: var(--text-muted);
    padding: 16px 0 8px;
}
Padrão de Card KPI
<div class="kpi-card">
    <div class="kpi-label">Receita Total</div>
    <div class="kpi-value" id="kpi-revenue">R$ 0</div>
    <div class="kpi-change positive" id="kpi-revenue-change">+0%</div>
</div>
.kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--gap);
    margin-bottom: var(--gap);
}

.kpi-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px 24px;
    transition: border-color 0.2s;
}

.kpi-card:hover {
    border-color: var(--accent-border);
}

.kpi-label {
    font-size: 11px;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.8px;
    margin-bottom: 8px;
}

.kpi-value {
    font-size: 26px;
    font-weight: 700;
    color: var(--text-primary);
    margin-bottom: 6px;
}

.kpi-change {
    font-size: 12px;
    font-weight: 500;
}

.kpi-change.positive { color: var(--positive); }
.kpi-change.negative { color: var(--negative); }
.kpi-change.neutral  { color: var(--neutral); }
function renderKPI(elementId, value, previousValue, format = 'number') {
    const el = document.getElementById(elementId);
    const changeEl = document.getElementById(elementId + '-change');

    el.textContent = formatValue(value, format);

    if (previousValue && previousValue !== 0) {
        const pctChange = ((value - previousValue) / previousValue) * 100;
        const sign = pctChange >= 0 ? '+' : '';
        changeEl.textContent = `${sign}${pctChange.toFixed(1)}% vs período anterior`;
        changeEl.className = `kpi-change ${pctChange >= 0 ? 'positive' : 'negative'}`;
    }
}

function formatValue(value, format) {
    switch (format) {
        case 'currency':
            if (value >= 1e6) return `R$ ${(value / 1e6).toFixed(1)}M`;
            if (value >= 1e3) return `R$ ${(value / 1e3).toFixed(1)}K`;
            return `R$ ${value.toFixed(0)}`;
        case 'currency_usd':
            if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
            if (value >= 1e3) return `$${(value / 1e3).toFixed(1)}K`;
            return `$${value.toFixed(0)}`;
        case 'percent':
            return `${value.toFixed(1)}%`;
        case 'number':
            if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
            if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
            return value.toLocaleString('pt-BR');
        default:
            return value.toString();
    }
}
Integração Chart.js — Tema Evolution
Paleta de Cores para Gráficos
const COLORS = [
    '#00FFA7',  // Acento principal Evolution
    '#4C9FE8',  // Azul
    '#FFB547',  // Âmbar
    '#FF6B6B',  // Vermelho
    '#A78BFA',  // Roxo
    '#34D399',  // Verde esmeralda
    '#FB923C',  // Laranja
];

// Configuração global Chart.js para tema escuro
Chart.defaults.color = '#888888';
Chart.defaults.borderColor = 'rgba(255,255,255,0.06)';
Chart.defaults.font.family = 'Inter, sans-serif';
Gráfico de Linha
function createLineChart(canvasId, labels, datasets) {
    const ctx = document.getElementById(canvasId).getContext('2d');
    return new Chart(ctx, {
        type: 'line',
        data: {
            labels: labels,
            datasets: datasets.map((ds, i) => ({
                label: ds.label,
                data: ds.data,
                borderColor: COLORS[i % COLORS.length],
                backgroundColor: COLORS[i % COLORS.length] + '18',
                borderWidth: 2,
                fill: ds.fill || false,
                tension: 0.4,
                pointRadius: 3,
                pointHoverRadius: 6,
                pointBackgroundColor: COLORS[i % COLORS.length],
            }))
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            interaction: { mode: 'index', intersect: false },
            plugins: {
                legend: {
                    position: 'top',
                    labels: {
                        usePointStyle: true,
                        padding: 20,
                        color: '#888888',
                    }
                },
                tooltip: {
                    backgroundColor: '#1A1A1A',
                    borderColor: 'rgba(0,255,167,0.3)',
                    borderWidth: 1,
                    titleColor: '#F0F0F0',
                    bodyColor: '#888888',
                }
            },
            scales: {
                x: {
                    grid: { color: 'rgba(255,255,255,0.04)' },
                    ticks: { color: '#666666' }
                },
                y: {
                    beginAtZero: true,
                    grid: { color: 'rgba(255,255,255,0.04)' },
                    ticks: {
                        color: '#666666',
                        callback: function(value) {
                            return formatValue(value, 'number');
                        }
                    }
                }
            }
        }
    });
}
Gráfico de Barras
function createBarChart(canvasId, labels, data, options = {}) {
    const ctx = document.getElementById(canvasId).getContext('2d');
    const isHorizontal = options.horizontal || labels.length > 8;

    return new Chart(ctx, {
        type: 'bar',
        data: {
            labels: labels,
            datasets: [{
                label: options.label || 'Valor',
                data: data,
                backgroundColor: options.singleColor
                    ? COLORS[0] + 'CC'
                    : COLORS.map(c => c + 'CC'),
                borderColor: options.singleColor
                    ? COLORS[0]
                    : COLORS,
                borderWidth: 1,
                borderRadius: 4,
                hoverBackgroundColor: COLORS[0],
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            indexAxis: isHorizontal ? 'y' : 'x',
            plugins: {
                legend: { display: false },
                tooltip: {
                    backgroundColor: '#1A1A1A',
                    borderColor: 'rgba(0,255,167,0.3)',
                    borderWidth: 1,
                    titleColor: '#F0F0F0',
                    bodyColor: '#888888',
                    callbacks: {
                        label: function(context) {
                            return formatValue(context.parsed[isHorizontal ? 'x' : 'y'], options.format || 'number');
                        }
                    }
                }
            },
            scales: {
                x: {
                    beginAtZero: true,
                    grid: { color: 'rgba(255,255,255,0.04)', display: isHorizontal },
                    ticks: { color: '#666666' }
                },
                y: {
                    beginAtZero: !isHorizontal,
                    grid: { color: 'rgba(255,255,255,0.04)', display: !isHorizontal },
                    ticks: { color: '#666666' }
                }
            }
        }
    });
}
Gráfico de Rosca
function createDoughnutChart(canvasId, labels, data) {
    const ctx = document.getElementById(canvasId).getContext('2d');
    return new Chart(ctx, {
        type: 'doughnut',
        data: {
            labels: labels,
            datasets: [{
                data: data,
                backgroundColor: COLORS.map(c => c + 'CC'),
                borderColor: '#111111',
                borderWidth: 3,
                hoverBorderColor: '#00FFA7',
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            cutout: '65%',
            plugins: {
                legend: {
                    position: 'right',
                    labels: { usePointStyle: true, padding: 15, color: '#888888' }
                },
                tooltip: {
                    backgroundColor: '#1A1A1A',
                    borderColor: 'rgba(0,255,167,0.3)',
                    borderWidth: 1,
                    titleColor: '#F0F0F0',
                    bodyColor: '#888888',
                    callbacks: {
                        label: function(context) {
                            const total = context.dataset.data.reduce((a, b) => a + b, 0);
                            const pct = ((context.parsed / total) * 100).toFixed(1);
                            return `${context.label}: ${formatValue(context.parsed, 'number')} (${pct}%)`;
                        }
                    }
                }
            }
        }
    });
}
Atualizar Gráficos ao Filtrar
function updateChart(chart, newLabels, newData) {
    chart.data.labels = newLabels;

    if (Array.isArray(newData[0])) {
        newData.forEach((data, i) => {
            chart.data.datasets[i].data = data;
        });
    } else {
        chart.data.datasets[0].data = newData;
    }

    chart.update('none'); // 'none' desativa animação para atualização instantânea
}
Filtros e Interatividade
Filtro Dropdown
<div class="filter-group">
    <label for="filter-periodo">Período</label>
    <select id="filter-periodo" onchange="dashboard.applyFilters()">
        <option value="all">Todos</option>
    </select>
</div>
.filters { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.filter-group { display: flex; align-items: center; gap: 6px; }
.filter-group label { font-size: 11px; color: var(--text-secondary); }
.filter-group select,
.filter-group input[type="date"] {
    padding: 6px 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-elevated);
    color: var(--text-primary);
    font-size: 12px;
    font-family: var(--font);
    outline: none;
    cursor: pointer;
}
.filter-group select:focus {
    border-color: var(--accent-border);
}
.filter-group select option { background: var(--bg-card); }
Filtro de Intervalo de Data
function filterByDateRange(data, dateField, startDate, endDate) {
    return data.filter(row => {
        const rowDate = new Date(row[dateField]);
        if (startDate && rowDate < new Date(startDate)) return false;
        if (endDate && rowDate > new Date(endDate)) return false;
        return true;
    });
}
Lógica de Filtro Combinado
applyFilters() {
    const plano = getFilterValue('filter-plano');
    const status = getFilterValue('filter-status');
    const startDate = document.getElementById('filter-date-start').value;
    const endDate = document.getElementById('filter-date-end').value;

    this.filteredData = this.rawData.filter(row => {
        if (plano && row.plano !== plano) return false;
        if (status && row.status !== status) return false;
        if (startDate && row.data < startDate) return false;
        if (endDate && row.data > endDate) return false;
        return true;
    });

    this.renderKPIs();
    this.updateCharts();
    this.renderTable();
}
Tabela Ordenável
function renderTable(containerId, data, columns) {
    const container = document.getElementById(containerId);
    let sortCol = null;
    let sortDir = 'desc';

    function render(sortedData) {
        let html = '<table class="data-table">';
        html += '<thead><tr>';
        columns.forEach(col => {
            const arrow = sortCol === col.field ? (sortDir === 'asc' ? ' ▲' : ' ▼') : '';
            html += `<th onclick="sortTable('${col.field}')" style="cursor:pointer">${col.label}${arrow}</th>`;
        });
        html += '</tr></thead><tbody>';
        sortedData.forEach(row => {
            html += '<tr>';
            columns.forEach(col => {
                const value = col.format ? formatValue(row[col.field], col.format) : row[col.field];
                html += `<td>${value}</td>`;
            });
            html += '</tr>';
        });
        html += '</tbody></table>';
        container.innerHTML = html;
    }

    window.sortTable = function(field) {
        if (sortCol === field) {
            sortDir = sortDir === 'asc' ? 'desc' : 'asc';
        } else {
            sortCol = field;
            sortDir = 'desc';
        }
        const sorted = [...data].sort((a, b) => {
            const aVal = a[field], bVal = b[field];
            const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
            return sortDir === 'asc' ? cmp : -cmp;
        });
        render(sorted);
    };

    render(data);
}
.table-section {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px 24px;
    overflow-x: auto;
    margin-bottom: var(--gap);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.data-table thead th {
    text-align: left;
    padding: 10px 12px;
    border-bottom: 1px solid var(--border);
    color: var(--text-secondary);
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    white-space: nowrap;
    user-select: none;
}

.data-table thead th:hover { color: var(--accent); }
.data-table tbody td { padding: 10px 12px; border-bottom: 1px solid rgba(255,255,255,0.04); }
.data-table tbody tr:hover { background: rgba(0, 255, 167, 0.03); }
.data-table tbody tr:last-child td { border-bottom: none; }
Containers de Gráfico
.chart-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
    gap: var(--gap);
    margin-bottom: var(--gap);
}

.chart-container {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px 24px;
}

.chart-container h3 {
    font-size: 13px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 16px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.chart-container canvas { max-height: 280px; }
Design Responsivo
@media (max-width: 768px) {
    .dashboard-header { flex-direction: column; align-items: flex-start; }
    .kpi-row { grid-template-columns: repeat(2, 1fr); }
    .chart-row { grid-template-columns: 1fr; }
    .filters { flex-direction: column; align-items: flex-start; }
}

@media print {
    body { background: #fff; color: #000; }
    .filters, .logo-badge { display: none; }
    .chart-container, .kpi-card { border: 1px solid #ddd; box-shadow: none; break-inside: avoid; }
}
Performance para Datasets Grandes

| Tamanho dos Dados | Abordagem | |---|---| | <1.000 linhas | Incorporar diretamente. Interatividade completa. | | 1.000–10.000 linhas | Incorporar no HTML. Pode precisar pré-agregar para gráficos. | | 10.000–100.000 linhas | Pré-agregar server-side. Incorporar apenas dados agregados. | | >100.000 linhas | Não adequado para dashboard client-side. Use BI tool ou pagine. |

// Padrão de pré-agregação
const CHART_DATA = {
    receita_mensal: [
        { mes: '2025-01', receita: 150000, assinaturas: 120 },
        // ... 12 linhas em vez de 50.000
    ],
    top_planos: [
        { plano: 'Pro', receita: 45000 },
    ],
    kpis: {
        mrr_total: 198000,
        total_instancias: 15600,
        ticket_medio: 127,
    }
};
Exemplos
/data-build-dashboard Dashboard de MRR com tendência mensal, breakdown por plano e mapa de instâncias ativas.
/data-build-dashboard Aqui estão os dados de instâncias do Licensing [cola CSV]. Montar dashboard com distribuição por país, versão e status.
/data-build-dashboard Criar dashboard executivo SaaS com MRR, churn, novos clientes e NPS. Usar dados de exemplo.
Dicas
  • Dashboards são arquivos HTML totalmente auto-contidos — compartilhe com qualquer pessoa enviando o arquivo
  • Para dashboards em tempo real, considere conectar a um BI tool. Esses dashboards são snapshots pontuais
  • Peça "modo apresentação" para fontes maiores e mais contraste
  • O tema Evolution dark é o padrão; solicite "modo claro" apenas se necessário
按 Apache-2.0 许可原样转载,未经改动 · 在 GitHub 查看 →

评论

登录即可评论;带「已验证安装」的,是发布者名下有本店的安装或持有记录。