Início Boas práticas Tema Idiomas e RTL App e cabeçalho Layout Tipografia Abas Menu e âncora Dropdown e popover Command e toggles Botão Campos Seleção e valores Combobox e menções Datas e calendário Árvore e cascata Transfer Upload Filtros Tabela Planilha KPI e estatística Badge e tag Listas e detalhes Gráficos Imagem e carrossel Ícones Diagramas Alertas e estados Progresso e passos Diálogo e toast Drawer e notificação Utilitários Overview Work queue Report Report com htmx Form / request Tabela administrativa Página de detalhe Formulário em diálogo Gráfico com dados do servidor Buscar… llms.txt Tema Tema Restaurar Copiar prompt Copiar snippet Prompt: para as instruções de um agente. Snippet: direto no templates/base.html. v0.11.0 Abrir em tela cheia <ab-app brand-name="Acme Finanças" user-email="ana.souza@acme.com"> <a slot="nav" href="#" aria-current="page">Visão geral</a> <a slot="nav" href="#">Contas a receber</a> <a slot="nav" href="#">Conciliação</a> <ab-page-header heading="Bom dia, Ana" description="Situação do caixa hoje"> <ab-button slot="actions" icon="arrows-clockwise" id="refresh">Atualizar</ab-button> </ab-page-header> <ab-stack gap="5"> <ab-grid cols="4" min="12rem"> <ab-kpi id="k1" label="Saldo em conta" icon="bank" type="currency" loading></ab-kpi> <ab-kpi id="k2" label="A receber (7 dias)" icon="arrow-down-left" type="currency" loading></ab-kpi> <ab-kpi id="k3" label="A pagar (7 dias)" icon="arrow-up-right" type="currency" loading></ab-kpi> <ab-kpi id="k4" label="Inadimplência" type="percent" invert-delta loading></ab-kpi> </ab-grid> <ab-split side="end" side-width="22rem"> <ab-table id="due" heading="Vencendo esta semana" clickable></ab-table> <ab-card slot="side" heading="Atenção"> <ab-stack gap="3"> <ab-alert tone="warn">3 boletos sem conciliação há mais de 5 dias.</ab-alert> <ab-alert tone="info">Última sincronização com o ERP há 12 min.</ab-alert> </ab-stack> </ab-card> </ab-split> </ab-stack> </ab-app> <script type="module"> // Simula a função Python get_overview() (na Page real ela é gerada pelo @register_function). const get_overview = () => new Promise((r) => setTimeout(() => r({ saldo: 1284300.12, receber: 214500, pagar: 98210.5, inadimplencia: 0.031, delta_inad: -0.004, due: [ { cliente: "Padaria Pão Dourado", vencimento: "2026-11-07", valor: 1890.4, status: "A vencer" }, { cliente: "Clínica Bem Viver", vencimento: "2026-11-05", valor: 12500, status: "Atrasado" }, { cliente: "Oficina Rota 66", vencimento: "2026-11-09", valor: 420, status: "A vencer" }, ], }), 700)); const t = document.getElementById("due"); t.columns = [ { key: "cliente", label: "Cliente" }, { key: "vencimento", label: "Vencimento", type: "date" }, { key: "valor", label: "Valor", type: "currency" }, { key: "status", label: "Status", type: "badge", tones: { Atrasado: "danger", "A vencer": "neutral" } }, ]; // Uma chamada alimenta vários componentes: abLoad liga o loading de todos e o apply distribui os dados. const kpis = ["k1", "k2", "k3", "k4"].map((id) => document.getElementById(id)); const load = () => abLoad([...kpis, t], get_overview, { apply: (data, el) => { if (el === t) t.rows = data.due; else Object.assign(el, { k1: { value: data.saldo }, k2: { value: data.receber }, k3: { value: data.pagar }, k4: { value: data.inadimplencia, delta: data.delta_inad } }[el.id]); }, }); document.getElementById("refresh").addEventListener("click", load); load(); </script> # page_overview.py — __render__ only renders the shell; data comes after first paint. from abstra.pages import register_function from abstra.tables import select from lib_jinja import render_template @register_function def __render__(): return render_template("overview.html", title="Visão geral", active="overview") @register_function def get_overview(): due = select("receivables", where={"status": "open"}, order_by="due_date", limit=10) return { "saldo": 1284300.12, "receber": sum(r["amount"] for r in due), "due": [ {"cliente": r["customer"], "vencimento": r["due_date"], "valor": r["amount"], "status": r["status_label"]} for r in due ], }