Cards para disjuntores e falta de luz no Home Assistant

Quando você começa a colocar disjuntores inteligentes na rotina da casa, a automação deixa de ser “legal” e passa a ser infraestrutura. E infraestrutura precisa de duas coisas:

  1. Controle rápido (ligar/desligar, entender status, ver potência)
  2. Confiança visual (saber em um olhar se está tudo normal ou se tem algo errado)

Meu problema era simples: eu tinha vários disjuntores e precisava de uma página única no Home Assistant onde eu conseguisse:

  • Ver todos os disjuntores na mesma tela
  • Saber imediatamente se cada um está OK / com problema
  • Ter o estado operacional (on/standby/off) e o controle de energia
  • Acompanhar tendências (potência ao longo das últimas horas)
  • E, principalmente, ter um card separado que mostrasse quando faltou luz, quantas vezes e por quanto tempo

No fim, eu cheguei em dois cards principais:

  1. um card de “controle do disjuntor” (status + power + métricas + gráfico)
  2. um card de “estatísticas de queda de energia” (Today/MTD/YTD/Lifetime com contagem e tempo)

O detalhe interessante é que esse resultado veio de uma mistura de ChatGPT Pro com muita lapidação manual no YAML—porque o “jeito certo” é aquele que se encaixa no seu uso real.

Card 1 — O “cockpit” do disjuntor: controle + leitura rápida

O primeiro card virou meu “painel de comando” por disjuntor.

O que ele mostra (sem poluição visual)

  • Nome do disjuntor (ex.: D2)
  • Status (um pill “on/off/unknown” com cor)
  • Botão power (chip de toggle)
  • Um retângulo de saúde (OK/PROBLEM) baseado em um binary_sensor
  • Total Power em destaque
  • Voltage / Current / Temperature como “resumo de instrumentação”
  • Um mini gráfico ao fundo com o histórico recente de potência

A base técnica aqui é a combinação de:

  • custom:button-card (para grid, custom_fields e layout livre)
  • mini-graph-card (para o histórico compacto dentro do card)
  • mushroom-chips-card (toggle bonito e consistente)

Por que isso funciona bem na prática?

Porque ele te dá três níveis de informação no mesmo componente:

  • Nível 1 (instante): “tá tudo OK?” (pill verde/vermelho)
  • Nível 2 (controle): “posso desligar/ligar agora?” (chip power)
  • Nível 3 (diagnóstico): potência e instrumentação + tendência (gráfico)

Você não precisa entrar em “mais detalhes” para tomar as ações comuns.

Código Card Largo

type: custom:button-card
show_icon: false
show_name: true
name: D2
show_state: false
styles:
  name:
    - font-weight: 200
    - font-size: 22px
    - justify-self: start
    - align-self: start
  card:
    - padding: 16px
    - border-radius: 18px
    - overflow: visible
  grid:
    - grid-template-areas: |
        "n status switch"
        "problem problem problem"
        "power power power"
        "volt cur temp"
    - grid-template-columns: 1fr 1fr 1fr
    - row-gap: 12px
    - column-gap: 12px
    - align-items: center
    - overflow: visible
  custom_fields:
    graph:
      - position: absolute
      - width: 100%
      - left: 0
      - bottom: 0
    problem:
      - justify-self: stretch
      - align-self: center
      - overflow: visible
    status:
      - justify-self: stretch
      - align-self: center
      - overflow: visible
    switch:
      - justify-self: end
      - align-self: center
    power:
      - justify-self: stretch
      - align-self: center
      - overflow: visible
custom_fields:
  graph:
    card:
      type: custom:mini-graph-card
      entities:
        - entity: sensor.d2_test_power
          color: "#a6cf9d"
      line_width: 0
      height: 100
      hours_to_show: 12
      show:
        icon: false
        name: false
        state: false
        legend: false
        labels: false
      card_mod:
        style: |
          ha-card { border-style: none; background: none; }
  status:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: switch.d2_test
      tap_action:
        action: toggle
      hold_action:
        action: more-info
      name: |
        [[[
          const st = states['switch.d2_test']?.state;
          const val = (!st || st === 'unavailable' || st === 'unknown')
            ? 'unknown'
            : (st === 'on' ? 'on' : 'off');

          const bg =
            val === 'off' ? '#f57c00' :
            val === 'on' ? '#2e7d32' :
            'rgba(255,255,255,0.10)';

          const fg =
            val === 'off' ? 'black' :
            val === 'on' ? 'white' :
            'var(--primary-text-color)';

          return `
            <div style="text-align:center;">
              <div style="font-size:12px; opacity:0.85;">Status</div>
              <div style="
                font-size:15px;
                display:inline-block;
                margin-top:4px;
                padding:2px 8px;
                border-radius:8px;
                font-weight:600;
                background:${bg};
                color:${fg};
              ">
                ${val}
              </div>
            </div>
          `;
        ]]]
      styles:
        card:
          - border: 0px
          - border-radius: 10px
          - padding: 6px
          - background: none
          - overflow: visible
          - width: max-content
          - justify-self: start
          - align-self: start
        name:
          - text-align: center
          - font-weight: 600
          - overflow: visible
  problem:
    card:
      type: custom:button-card
      entity: binary_sensor.d2_test_problem
      show_icon: false
      show_name: false
      show_state: false
      styles:
        card:
          - border: 0
          - background: none
          - padding: 0
          - overflow: visible
        grid:
          - grid-template-areas: "\"bar\""
          - grid-template-columns: 1fr
          - justify-items: stretch
          - align-items: stretch
        custom_fields:
          bar:
            - width: 100%
            - justify-self: stretch
            - align-self: stretch
      custom_fields:
        bar: |
          [[[
            const stRaw = states['binary_sensor.d2_test_problem']?.state;
            const badState = (!stRaw || stRaw === 'unavailable' || stRaw === 'unknown');

            // OFF means OK
            const isOk = (!badState && stRaw === 'off');
            const label = badState ? String(stRaw || 'unknown').toUpperCase() : (isOk ? 'OK' : 'PROBLEM');

            const bg = badState
              ? 'rgba(0,0,0,0.06)'
              : isOk
                ? 'rgba(46, 125, 50, 0.18)'
                : 'rgba(211, 47, 47, 0.18)';

            const fg = badState
              ? 'rgba(0,0,0,0.45)'
              : isOk
                ? '#2e7d32'
                : '#d32f2f';

            return `
              <div style="
                width:100%;
                display:block;
                box-sizing:border-box;
                text-align:center;
                padding: 4px 10px;
                font-weight: 800;
                font-size: 13px;
                line-height: 1;
                background: ${bg};
                color: ${fg};
                letter-spacing: 0.3px;
                text-transform: uppercase;
              ">
                ${label}
              </div>
            `;
          ]]]
  switch:
    card:
      type: custom:mushroom-chips-card
      alignment: end
      chips:
        - type: template
          entity: switch.d2_test
          icon: mdi:power
          icon_color: >
            {% if is_state(config.entity, 'on') %} white {% else %}
            var(--secondary-text-color) {% endif %}
          tap_action:
            action: toggle
          hold_action:
            action: more-info
          card_mod:
            style: |
              :host {
                --chip-background: {{ 'var(--primary-color)' if is_state(config.entity,'on') else 'rgba(255,255,255,0.06)' }};
              }
              ha-card {
                --chip-height: 46px;
                --chip-font-size: 16px;
                --mdc-icon-size: 24px;
                border-radius: 28px !important;
              }
  power:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_power
      name: |
        [[[
          const st = states['sensor.d2_test_power']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const p = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (p === null || Number.isNaN(p)) ? 'N/A' : p.toFixed(1);

          return `
            <div style="text-align:center;">
              <div style="font-size:13px; color: var(--secondary-text-color); margin-bottom:6px;">
                Total Power
              </div>
              <div style="font-size:32px; font-weight:500; letter-spacing:-0.6px; line-height:1;">
                ${val} <span style="font-size:34px; font-weight:700; opacity:0.95;">W</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 4px 0 0 0
          - overflow: visible
  volt:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_voltage
      name: |
        [[[
          const st = states['sensor.d2_test_voltage']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const v = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (v === null || Number.isNaN(v)) ? '—' : String(Math.round(v));

          let col = 'var(--secondary-text-color)';
          if (!bad && v !== null && Number.isFinite(v)) {
            if (v >= 110 && v <= 130) col = '#2e7d32';
            else if ((v > 130 && v <= 135) || (v >= 100 && v < 110)) col = '#f57c00';
            else col = '#d32f2f';
          }

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Voltage
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1; color:${col};">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">V</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
  cur:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_current
      name: |
        [[[
          const st = states['sensor.d2_test_current']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const a = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (a === null || Number.isNaN(a)) ? '—' : a.toFixed(2);

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Current
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1;">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">A</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
  temp:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_temperature
      name: |
        [[[
          const st = states['sensor.d2_test_temperature']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const t = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (t === null || Number.isNaN(t)) ? '—' : String(Math.round(t));

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Temp.
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1;">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">°C</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
grid_options:
  columns: 6
  rows: auto

Código Card Fino

type: custom:button-card
show_icon: false
show_name: true
name: D2
show_state: false
styles:
  name:
    - font-weight: 200
    - font-size: 22px
  card:
    - padding: 16px
    - border-radius: 18px
    - overflow: visible
    - justify-self: stretch
    - align-self: center
    - overflow: visible
  grid:
    - grid-template-areas: |
        "n"
        "status"
        "switch"
        "problem"
        "power"
        "volt "
        "cur"
        "temp"
    - grid-template-columns: 1fr
    - row-gap: 12px
    - align-items: center
    - overflow: visible
  custom_fields:
    graph:
      - position: absolute
      - width: 100%
      - left: 0
      - bottom: 0
custom_fields:
  graph:
    card:
      type: custom:mini-graph-card
      entities:
        - entity: sensor.d2_test_power
          color: "#a6cf9d"
      line_width: 0
      height: 100
      hours_to_show: 12
      show:
        icon: false
        name: false
        state: false
        legend: false
        labels: false
      card_mod:
        style: |
          ha-card { border-style: none; background: none; }
  status:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: switch.d2_test
      tap_action:
        action: toggle
      hold_action:
        action: more-info
      name: |
        [[[
          const st = states['switch.d2_test']?.state;
          const val = (!st || st === 'unavailable' || st === 'unknown')
            ? 'unknown'
            : (st === 'on' ? 'on' : 'off');

          const bg =
            val === 'off' ? '#f57c00' :
            val === 'on' ? '#2e7d32' :
            'rgba(255,255,255,0.10)';

          const fg =
            val === 'off' ? 'black' :
            val === 'on' ? 'white' :
            'var(--primary-text-color)';

          return `
            <div style="text-align:center;">
              <div style="font-size:12px; opacity:0.85;">Status</div>
              <div style="
                font-size:15px;
                display:inline-block;
                margin-top:4px;
                padding:2px 8px;
                border-radius:8px;
                font-weight:600;
                background:${bg};
                color:${fg};
              ">
                ${val}
              </div>
            </div>
          `;
        ]]]
      styles:
        card:
          - border: 0px
          - justify-self: start
          - align-self: start
        name:
          - text-align: center
          - font-weight: 600
          - overflow: visible
  problem:
    card:
      type: custom:button-card
      entity: binary_sensor.d2_test_problem
      show_icon: false
      show_name: false
      show_state: false
      styles:
        card:
          - border: 0
          - background: none
          - padding: 0
          - overflow: visible
        grid:
          - grid-template-areas: "\"bar\""
          - grid-template-columns: 1fr
          - justify-items: stretch
          - align-items: stretch
        custom_fields:
          bar:
            - width: 100%
            - justify-self: stretch
            - align-self: stretch
      custom_fields:
        bar: |
          [[[
            const stRaw = states['binary_sensor.d2_test_problem']?.state;
            const badState = (!stRaw || stRaw === 'unavailable' || stRaw === 'unknown');

            // OFF means OK
            const isOk = (!badState && stRaw === 'off');
            const label = badState ? String(stRaw || 'unknown').toUpperCase() : (isOk ? 'OK' : 'PROBLEM');

            const bg = badState
              ? 'rgba(0,0,0,0.06)'
              : isOk
                ? 'rgba(46, 125, 50, 0.18)'
                : 'rgba(211, 47, 47, 0.18)';

            const fg = badState
              ? 'rgba(0,0,0,0.45)'
              : isOk
                ? '#2e7d32'
                : '#d32f2f';

            return `
              <div style="
                width:100%;
                display:block;
                box-sizing:border-box;
                text-align:center;
                padding: 4px 10px;
                font-weight: 800;
                font-size: 13px;
                line-height: 1;
                background: ${bg};
                color: ${fg};
                letter-spacing: 0.3px;
                text-transform: uppercase;
              ">
                ${label}
              </div>
            `;
          ]]]
  switch:
    card:
      type: custom:mushroom-chips-card
      alignment: center
      chips:
        - type: template
          entity: switch.d2_test
          icon: mdi:power
          icon_color: >
            {% if is_state(config.entity, 'on') %} white {% else %}
            var(--secondary-text-color) {% endif %}
          tap_action:
            action: toggle
          hold_action:
            action: more-info
          card_mod:
            style: |
              :host {
                --chip-background: {{ 'var(--primary-color)' if is_state(config.entity,'on') else 'rgba(255,255,255,0.06)' }};
              }
              ha-card {
                --chip-height: 46px;
                --chip-font-size: 16px;
                --mdc-icon-size: 24px;
                border-radius: 28px !important;
              }
  power:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_power
      name: |
        [[[
          const st = states['sensor.d2_test_power']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const p = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (p === null || Number.isNaN(p)) ? 'N/A' : p.toFixed(1);

          return `
            <div style="text-align:center;">
              <div style="font-size:13px; color: var(--secondary-text-color); margin-bottom:6px;">
                Total Power
              </div>
              <div style="font-size:32px; font-weight:500; letter-spacing:-0.6px; line-height:1;">
                ${val} <span style="font-size:34px; font-weight:700; opacity:0.95;">W</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 4px 0 0 0
          - overflow: visible
  volt:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_voltage
      name: |
        [[[
          const st = states['sensor.d2_test_voltage']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const v = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (v === null || Number.isNaN(v)) ? '—' : String(Math.round(v));

          let col = 'var(--secondary-text-color)';
          if (!bad && v !== null && Number.isFinite(v)) {
            if (v >= 110 && v <= 130) col = '#2e7d32';
            else if ((v > 130 && v <= 135) || (v >= 100 && v < 110)) col = '#f57c00';
            else col = '#d32f2f';
          }

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Voltage
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1; color:${col};">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">V</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
  cur:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_current
      name: |
        [[[
          const st = states['sensor.d2_test_current']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const a = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (a === null || Number.isNaN(a)) ? '—' : a.toFixed(2);

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Current
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1;">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">A</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
  temp:
    card:
      type: custom:button-card
      show_icon: false
      show_name: true
      show_state: false
      entity: sensor.d2_test_temperature
      name: |
        [[[
          const st = states['sensor.d2_test_temperature']?.state;
          const bad = (!st || st === 'unavailable' || st === 'unknown');
          const t = bad ? null : parseFloat(String(st).replace(',', '.'));
          const val = (t === null || Number.isNaN(t)) ? '—' : String(Math.round(t));

          return `
            <div style="text-align:center;">
              <div style="font-size:11px; color: var(--secondary-text-color); margin-bottom:3px;">
                Temp.
              </div>
              <div style="font-size:16px; font-weight:500; line-height:1;">
                ${val}
                <span style="font-size:11px; font-weight:300; color: var(--secondary-text-color);">°C</span>
              </div>
            </div>
          `;
        ]]]
      tap_action:
        action: more-info
      styles:
        card:
          - border: 0px
          - background: none
          - padding: 0px
          - overflow: visible
grid_options:
  columns: 4
  rows: auto

Card 2 — Outage stats: quantas vezes faltou luz e por quanto tempo

O segundo card nasceu de uma dor bem real:
às vezes a energia “some e volta” rápido, e você só percebe depois quando algum equipamento reinicia ou um log acusa falha.

Então eu quis um card que respondesse, de forma objetiva:

  • Faltou luz hoje? Quantas vezes? Quanto tempo?
  • E no mês, no ano, e no acumulado total?

A ideia foi: detectar “queda” com base na voltagem (A/B/C) e gerar sensores de:

  • count (quantas ocorrências)
  • time (duração total)

Depois, no card, exibir em colunas “Today / MTD / YTD / Lifetime” com Count e Time.

Esse card resolve o que eu chamo de “telemetria da realidade”: a casa está estável mesmo quando você não está olhando?

type: custom:button-card
entity: binary_sensor.d01_power_outage
show_icon: false
show_name: false
show_state: false
tap_action:
  action: none
hold_action:
  action: more-info
  entity: sensor.d01_voltage_a
styles:
  card:
    - border-radius: 16px
    - padding: 12px
  grid:
    - grid-template-areas: "\"left right\""
    - grid-template-columns: 1fr 1.35fr
    - column-gap: 12px
    - align-items: start
custom_fields:
  left: |
    [[[
      const st = entity?.state;
      const outage = (st === 'on');
      const ok = (st === 'off');

      const icon = outage ? 'mdi:flash-off' : 'mdi:flash';
      const iconColor = outage ? '#d32f2f' : ok ? '#2e7d32' : 'rgba(0,0,0,0.35)';
      const bg = outage ? 'rgba(211, 47, 47, 0.18)' : ok ? 'rgba(46, 125, 50, 0.18)' : 'rgba(0,0,0,0.08)';
      const fg = outage ? '#d32f2f' : ok ? '#2e7d32' : 'rgba(0,0,0,0.45)';
      const label = outage ? 'Outage' : ok ? 'Power OK' : 'Unknown';

      return `
        <div
          onclick="this.dispatchEvent(new CustomEvent('hass-more-info', { composed: true, bubbles: true, detail: { entityId: 'binary_sensor.d01_power_outage' } }))"
          style="padding-top:10px; display:flex; flex-direction:column; gap:8px; align-items:center; cursor:pointer;"
        >
          <ha-icon icon="${icon}" style="width:28px; height:28px; color:${iconColor};"></ha-icon>
          <div style="padding-top:10px; font-size:18px; font-weight:800; line-height:1.1;">D01 Power</div>
          <span style="
            display:inline-block; padding:4px 10px; border-radius:10px;
            font-weight:700; font-size:11px; letter-spacing:0.2px;
            background:${bg}; color:${fg};
          ">${label}</span>
          <div style="padding-top:10px; font-size:11px; color:var(--secondary-text-color); opacity:0.85;">
            Based on voltage (A/B/C)
          </div>
        </div>
      `;
    ]]]
  right: |
    [[[
      const num = (eid) => {
        const s = states[eid]?.state;
        if (!s || s === 'unavailable' || s === 'unknown') return null;
        const n = parseFloat(String(s).replace(',', '.'));
        return Number.isFinite(n) ? n : null;
      };

      const hoursToMin = (h) => (h === null ? null : Math.round(h * 60));
      const fmtMin = (m) => (m === null ? '—' : `${m} min`);
      const fmtCnt = (c) => (c === null ? '—' : `${Math.round(c)}x`);

      const rows = [
        { label: 'Today',    t: 'sensor.d01_outage_time_today',    c: 'sensor.d01_outage_count_today' },
        { label: 'MTD',      t: 'sensor.d01_outage_time_mtd',      c: 'sensor.d01_outage_count_mtd' },
        { label: 'YTD',      t: 'sensor.d01_outage_time_ytd',      c: 'sensor.d01_outage_count_ytd' },
        { label: 'Lifetime', t: 'sensor.d01_outage_time_lifetime', c: 'sensor.d01_outage_count_lifetime' },
      ];

      const moreInfo = (eid) => `
        this.dispatchEvent(new CustomEvent('hass-more-info', {
          composed: true, bubbles: true, detail: { entityId: '${eid}' }
        }));
        event.stopPropagation();
      `;

      const header = `
        <div style="display:flex; justify-content:space-between; gap:10px; margin-bottom:6px;">
          <div style="font-size:11px; opacity:0.7;">Period</div>
          <div style="display:flex; gap:10px;">
            <div style="font-size:11px; opacity:0.7; width:56px; text-align:right;">Count</div>
            <div style="font-size:11px; opacity:0.7; width:78px; text-align:right;">Time</div>
          </div>
        </div>
      `;

      const body = rows.map(r => {
        const c = num(r.c);
        const h = num(r.t);
        const m = hoursToMin(h);

        return `
          <div style="display:flex; justify-content:space-between; gap:10px; padding:6px 0;">
            <div style="font-size:12px; opacity:0.8;">${r.label}</div>
            <div style="display:flex; gap:10px; align-items:baseline;">

              <div
                onclick="${moreInfo(r.c)}"
                style="
                  width:56px; text-align:right; font-size:14px; font-weight:800;
                  cursor:pointer; border-radius:6px; padding:2px 4px;
                  transition: background 0.15s;
                "
                onmouseenter="this.style.background='rgba(var(--rgb-primary-color, 33,150,243),0.12)'"
                onmouseleave="this.style.background='transparent'"
              >${fmtCnt(c)}</div>

              <div
                onclick="${moreInfo(r.t)}"
                style="
                  width:78px; text-align:right; font-size:14px; font-weight:800;
                  cursor:pointer; border-radius:6px; padding:2px 4px;
                  transition: background 0.15s;
                "
                onmouseenter="this.style.background='rgba(var(--rgb-primary-color, 33,150,243),0.12)'"
                onmouseleave="this.style.background='transparent'"
              >${fmtMin(m)}</div>

            </div>
          </div>
        `;
      }).join('');

      return `
        <div style="display:flex; flex-direction:column;">
          <div style="font-size:12px; opacity:0.7; letter-spacing:0.3px; text-align:right;">
            Outage stats
          </div>
          <div style="margin-top:8px;">
            ${header}
            ${body}
          </div>
        </div>
      `;
    ]]]

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *