@bluemarble/bi 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DashboardDevtools-ByrFb6HT.js → DashboardDevtools-5wh3brAT.js} +344 -208
- package/dist/DashboardDevtools-5wh3brAT.js.map +1 -0
- package/dist/{ModelDictionary-AjNbffwG.js → ModelDictionary-DN77UDVM.js} +10 -4
- package/dist/ModelDictionary-DN77UDVM.js.map +1 -0
- package/dist/client/index.d.ts +60 -4
- package/dist/client/index.js +26 -9
- package/dist/client/index.js.map +1 -1
- package/dist/{layout-iPByu6V0.js → layout-DvHS8XFz.js} +111 -14
- package/dist/layout-DvHS8XFz.js.map +1 -0
- package/dist/server/index.d.ts +7 -2
- package/dist/server/index.js +7 -5
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CB4sK4jP.d.ts → types-BO_AQNxv.d.ts} +87 -5
- package/dist/types.d.ts +2 -2
- package/package.json +2 -2
- package/dist/DashboardDevtools-ByrFb6HT.js.map +0 -1
- package/dist/ModelDictionary-AjNbffwG.js.map +0 -1
- package/dist/layout-iPByu6V0.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as isAggregate, f as resolveChartTypes, m as canonicalJson, y as AGGREGATION_LABEL } from "./layout-DvHS8XFz.js";
|
|
2
2
|
import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material";
|
|
3
3
|
import { Suspense, createContext, lazy, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -222,7 +222,7 @@ function createDevtoolsStore() {
|
|
|
222
222
|
//#endregion
|
|
223
223
|
//#region src/client/DashboardProvider.tsx
|
|
224
224
|
const DashboardContext = createContext(null);
|
|
225
|
-
const DashboardDevtools = lazy(() => import("./DashboardDevtools-
|
|
225
|
+
const DashboardDevtools = lazy(() => import("./DashboardDevtools-5wh3brAT.js"));
|
|
226
226
|
const DEVTOOLS_DEFAULT = process.env.NODE_ENV === "development";
|
|
227
227
|
const draftStorageKey = (model) => `bm-dashboard-devtools:draft:${model}`;
|
|
228
228
|
function loadDraft(model) {
|
|
@@ -237,7 +237,9 @@ function loadDraft(model) {
|
|
|
237
237
|
* Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o
|
|
238
238
|
* mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.
|
|
239
239
|
*/
|
|
240
|
-
function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEVTOOLS_DEFAULT, children }) {
|
|
240
|
+
function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEVTOOLS_DEFAULT, charts, children }) {
|
|
241
|
+
const chartTypes = useMemo(() => resolveChartTypes(charts), [charts]);
|
|
242
|
+
const chartComponents = useMemo(() => Object.fromEntries(Object.entries(charts ?? {}).map(([name, spec]) => [name, spec.component])), [charts]);
|
|
241
243
|
const [filters, setFilters] = useState(defaultFilters);
|
|
242
244
|
const defaultsRef = useRef(defaultFilters);
|
|
243
245
|
const setFilter = useCallback((dimension, values) => setFilters((current) => ({
|
|
@@ -319,6 +321,8 @@ function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEV
|
|
|
319
321
|
fetcher: effectiveFetcher,
|
|
320
322
|
meta,
|
|
321
323
|
refreshKey,
|
|
324
|
+
chartTypes,
|
|
325
|
+
chartComponents,
|
|
322
326
|
devtools: devtoolsValue
|
|
323
327
|
}), [
|
|
324
328
|
model,
|
|
@@ -327,6 +331,8 @@ function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEV
|
|
|
327
331
|
effectiveFetcher,
|
|
328
332
|
meta,
|
|
329
333
|
refreshKey,
|
|
334
|
+
chartTypes,
|
|
335
|
+
chartComponents,
|
|
330
336
|
devtoolsValue
|
|
331
337
|
]);
|
|
332
338
|
return /* @__PURE__ */ jsxs(DashboardContext.Provider, {
|
|
@@ -489,4 +495,4 @@ function ModelDictionary() {
|
|
|
489
495
|
//#endregion
|
|
490
496
|
export { useDashboard as a, layoutItemKey as c, DashboardProvider as i, parseLayoutItemKey as l, INSPECT_ATTRIBUTE as n, errorMessage as o, useModelQuery as r, LAYOUT_ITEM_ATTRIBUTE as s, ModelDictionary as t };
|
|
491
497
|
|
|
492
|
-
//# sourceMappingURL=ModelDictionary-
|
|
498
|
+
//# sourceMappingURL=ModelDictionary-DN77UDVM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelDictionary-DN77UDVM.js","names":["loadDraft"],"sources":["../src/devtools/layoutStore.ts","../src/devtools/store.ts","../src/client/DashboardProvider.tsx","../src/client/useModelQuery.ts","../src/client/ModelDictionary.tsx"],"sourcesContent":["import { canonicalJson } from '../codegen'\nimport type { ChartsLayout } from '../layout'\n\n/**\n * Layouts montados na página (`<DashboardCharts>` / `<DashboardFilters>`) e o rascunho de cada um,\n * editado na aba Layout do devtools. Store externo como o de consultas: editar um gráfico\n * re-renderiza só os componentes daquele layout e o painel.\n */\n\n/** Parte do layout que um componente renderiza. */\nexport type LayoutPart = 'charts' | 'filters' | 'canvas'\n\nexport type LayoutEntry = {\n /** O layout que veio do arquivo (o que a página passou ao `<DashboardCharts>`). */\n registered: ChartsLayout\n /** Rascunho editado no devtools; `null` = página usando o registrado. */\n draft: ChartsLayout | null\n /**\n * Partes montadas na página e os formatters que cada uma recebeu (`formatDimension`,\n * `formatLabel`). Parte ausente = a página não renderiza (ex.: sem `<DashboardFilters>`).\n */\n parts: Partial<Record<LayoutPart, string[]>>\n}\n\n/**\n * Marca o elemento de cada item do layout com `<layoutId>/chart:<id>` ou\n * `<layoutId>/filter:<dimensão>` (destaque e inspetor do devtools).\n */\nexport const LAYOUT_ITEM_ATTRIBUTE = 'data-bm-dashboard-layout-item'\n\nexport const layoutItemKey = (layoutId: string, item: LayoutItem) =>\n `${layoutId}/${item.kind}:${item.key}`\n\nexport type LayoutItem = { kind: 'chart' | 'filter'; key: string }\n\nexport function parseLayoutItemKey(value: string): { layoutId: string; item: LayoutItem } | null {\n const match = /^([^/]+)\\/(chart|filter):(.+)$/.exec(value)\n return match\n ? {\n layoutId: match[1] as string,\n item: { kind: match[2] as LayoutItem['kind'], key: match[3] as string }\n }\n : null\n}\n\nconst draftKey = (id: string) => `bm-dashboard-devtools:layout:${id}`\n\nconst sameLayout = (a: ChartsLayout | null, b: ChartsLayout | null) =>\n canonicalJson(a) === canonicalJson(b)\n\nfunction loadDraft(id: string): ChartsLayout | null {\n try {\n const saved = sessionStorage.getItem(draftKey(id))\n return saved ? (JSON.parse(saved) as ChartsLayout) : null\n } catch {\n return null\n }\n}\n\nfunction storeDraft(id: string, draft: ChartsLayout | null) {\n try {\n if (draft) sessionStorage.setItem(draftKey(id), JSON.stringify(draft))\n else sessionStorage.removeItem(draftKey(id))\n } catch {}\n}\n\nexport function createLayoutStore() {\n const entries = new Map<string, LayoutEntry>()\n // Montagens por layout e parte (`<id>\\u0000<parte>`)\n const mounts = new Map<string, number>()\n let snapshot: LayoutEntry[] = []\n const listeners = new Set<() => void>()\n\n const emit = () => {\n snapshot = [...entries.values()]\n for (const listener of listeners) listener()\n }\n\n const setDraft = (id: string, next: ChartsLayout | null) => {\n const entry = entries.get(id)\n if (!entry) return\n // Rascunho igual ao arquivo não é rascunho (ex.: desfez tudo à mão)\n const draft = next && !sameLayout(next, entry.registered) ? next : null\n storeDraft(id, draft)\n entries.set(id, { ...entry, draft })\n emit()\n }\n\n return {\n subscribe(listener: () => void) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n getSnapshot: () => snapshot,\n get: (id: string) => entries.get(id) ?? null,\n /** Rascunho do layout, mesmo antes de registrar (evita renderizar o registrado e trocar). */\n draftOf(layout: ChartsLayout) {\n const entry = entries.get(layout.id)\n if (entry) return entry.draft\n const draft = loadDraft(layout.id)\n return draft && !sameLayout(draft, layout) ? draft : null\n },\n\n /** Chamado pelo `<DashboardCharts>`/`<DashboardFilters>` ao montar e quando o arquivo muda (HMR). */\n register(layout: ChartsLayout, part: LayoutPart, formatters: string[]) {\n const current = entries.get(layout.id)\n let draft = current ? current.draft : loadDraft(layout.id)\n // Depois de \"Salvar no arquivo\" o Next recarrega o JSON: o rascunho virou o registrado\n if (draft && sameLayout(draft, layout)) {\n draft = null\n storeDraft(layout.id, null)\n }\n const parts = { ...current?.parts, [part]: formatters }\n entries.set(layout.id, { registered: layout, draft, parts })\n const mountKey = `${layout.id}\\u0000${part}`\n mounts.set(mountKey, (mounts.get(mountKey) ?? 0) + 1)\n emit()\n return () => {\n const count = (mounts.get(mountKey) ?? 1) - 1\n mounts.set(mountKey, count)\n const entry = entries.get(layout.id)\n if (count > 0 || !entry) return\n const { [part]: _, ...rest } = entry.parts\n // O rascunho continua no sessionStorage e volta na próxima montagem\n if (Object.keys(rest).length) entries.set(layout.id, { ...entry, parts: rest })\n else entries.delete(layout.id)\n emit()\n }\n },\n setDraft\n }\n}\n\nexport type LayoutStore = ReturnType<typeof createLayoutStore>\n","import type { DashboardQuery, DashboardQueryDebug, DashboardRow } from '../types'\n\n/**\n * Registro das consultas feitas pelos componentes (`useModelQuery`), lido pelo painel do devtools.\n * É um store externo (`useSyncExternalStore`) para que registrar uma consulta não re-renderize a\n * página inteira — só o painel.\n */\n\nexport type RunStatus = 'loading' | 'success' | 'error' | 'cancelled'\n\nexport type QueryRun = {\n runId: number\n instanceId: string\n label: string\n query: DashboardQuery\n status: RunStatus\n startedAt: number\n /** Ida e volta medida no browser. */\n durationMs?: number\n rows?: DashboardRow[]\n error?: string\n debug?: DashboardQueryDebug\n}\n\nexport type TrackedInstance = {\n instanceId: string\n label: string\n runs: number\n last: QueryRun\n /** Refeita muitas vezes em pouco tempo: sinal de re-render em loop (ex.: array novo a cada render). */\n storm: boolean\n}\n\nexport type DevtoolsSnapshot = {\n instances: TrackedInstance[]\n /** Execuções mais recentes primeiro. */\n history: QueryRun[]\n}\n\nconst MAX_HISTORY = 100\n/** `STORM_RUNS` execuções da mesma instância dentro de `STORM_WINDOW_MS` = consulta em loop. */\nconst STORM_RUNS = 6\nconst STORM_WINDOW_MS = 5000\n\nexport function errorMessage(error: unknown): string {\n const err = error as {\n response?: { data?: { error?: string; message?: string } }\n message?: string\n }\n return err?.response?.data?.error ?? err?.response?.data?.message ?? err?.message ?? String(error)\n}\n\nexport function createDevtoolsStore() {\n const instances = new Map<string, TrackedInstance>()\n let history: QueryRun[] = []\n // Execuções em andamento (o histórico é limitado e pode já ter descartado uma consulta lenta)\n const pending = new Map<number, QueryRun>()\n const recentStarts = new Map<string, number[]>()\n // Re-execuções pedidas pelo próprio devtools (refresh, aplicar rascunho) não contam como loop\n let expectedUntil = 0\n let snapshot: DevtoolsSnapshot = { instances: [], history: [] }\n let nextRunId = 1\n const listeners = new Set<() => void>()\n\n const emit = () => {\n snapshot = { instances: [...instances.values()], history }\n for (const listener of listeners) listener()\n }\n\n const update = (runId: number, patch: Partial<QueryRun>) => {\n const current = pending.get(runId)\n if (!current) return\n pending.delete(runId)\n const run = { ...current, ...patch, durationMs: Date.now() - current.startedAt }\n history = history.map((item) => (item.runId === runId ? run : item))\n const instance = instances.get(run.instanceId)\n if (instance?.last.runId === runId) instances.set(run.instanceId, { ...instance, last: run })\n emit()\n }\n\n return {\n subscribe(listener: () => void) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n getSnapshot: () => snapshot,\n\n start(instanceId: string, label: string, query: DashboardQuery) {\n const run: QueryRun = {\n runId: nextRunId++,\n instanceId,\n label,\n query,\n status: 'loading',\n startedAt: Date.now()\n }\n pending.set(run.runId, run)\n history = [run, ...history].slice(0, MAX_HISTORY)\n const previous = instances.get(instanceId)\n const expected = run.startedAt <= expectedUntil\n const starts = [\n ...(recentStarts.get(instanceId) ?? []),\n ...(expected ? [] : [run.startedAt])\n ].filter((time) => run.startedAt - time <= STORM_WINDOW_MS)\n recentStarts.set(instanceId, starts)\n const storm = previous?.storm || starts.length >= STORM_RUNS\n if (storm && !previous?.storm)\n console.warn(\n `[@bluemarble/bi] \"${label}\" executou ${starts.length}x em ${STORM_WINDOW_MS / 1000}s. ` +\n 'Confira se o componente não recria dimensions/measures/filtros a cada render.'\n )\n instances.set(instanceId, {\n instanceId,\n label,\n runs: (previous?.runs ?? 0) + 1,\n last: run,\n storm\n })\n emit()\n return run.runId\n },\n succeed: (runId: number, rows: DashboardRow[], debug?: DashboardQueryDebug) =>\n update(runId, { status: 'success', rows, debug }),\n fail: (runId: number, error: unknown) =>\n update(runId, { status: 'error', error: errorMessage(error) }),\n /** Consulta descartada antes de responder (filtro mudou, componente desmontou). */\n cancel: (runId: number) => update(runId, { status: 'cancelled' }),\n\n remove(instanceId: string) {\n recentStarts.delete(instanceId)\n if (instances.delete(instanceId)) emit()\n },\n /** Avisa que todas as consultas vão refazer agora, de propósito. */\n expectRefetch() {\n expectedUntil = Date.now() + 500\n },\n /** Zera os alertas de loop (depois de corrigir o componente, sem recarregar a página). */\n resetStorms() {\n recentStarts.clear()\n for (const [id, instance] of instances) instances.set(id, { ...instance, storm: false })\n emit()\n },\n clearHistory() {\n history = []\n emit()\n }\n }\n}\n\nexport type DevtoolsStore = ReturnType<typeof createDevtoolsStore>\n","import {\n type ComponentType,\n createContext,\n lazy,\n type ReactNode,\n Suspense,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react'\nimport { createLayoutStore, type LayoutStore } from '../devtools/layoutStore'\nimport { createDevtoolsStore, type DevtoolsStore } from '../devtools/store'\nimport { type ChartTypeSpec, type ChartTypes, resolveChartTypes } from '../layout'\nimport type { ModelDef, ModelMeta } from '../model'\nimport type { DashboardFilters, DashboardQuery, DashboardResult, FilterValue } from '../types'\n\nexport type DashboardFetcher = (query: DashboardQuery) => Promise<DashboardResult>\n\n/**\n * Tipo de gráfico do app: os campos do JSON (`ChartTypeSpec`) e o componente que os renderiza. O\n * componente recebe os campos como props (com os `default` aplicados e formatters já resolvidos) e\n * `queryLabel` — ver `ChartComponentProps`.\n */\n// biome-ignore lint/suspicious/noExplicitAny: cada tipo tem as próprias props, vindas do JSON\nexport type ChartComponentSpec = ChartTypeSpec & { component: ComponentType<any> }\n\n/** Props de um componente registrado em `charts`: os campos do tipo + `queryLabel`. */\nexport type ChartComponentProps<Fields extends object = Record<string, unknown>> = Fields & {\n /** Nome da consulta no devtools (`<título> · <tipo>`): passe ao `useModelQuery({ label })`. */\n queryLabel: string\n}\n\n/** Só existe com o devtools ligado. */\nexport type DashboardDevtoolsContext = {\n store: DevtoolsStore\n /** Layouts da página (`<DashboardCharts>`/`<DashboardFilters>`) e os rascunhos da aba Layout. */\n layouts: LayoutStore\n /** Consulta o modelo registrado, ignorando o rascunho (comparações, snapshots). */\n rawFetcher: DashboardFetcher\n /** Rascunho do modelo aplicado: todas as consultas da página passam a usá-lo. */\n draft: ModelDef | null\n setDraft: (draft: ModelDef | null) => void\n /** Refaz todas as consultas da página. */\n refresh: () => void\n defaultFilters: DashboardFilters\n setFilters: (filters: DashboardFilters) => void\n}\n\ntype DashboardContextValue = {\n model: string\n filters: DashboardFilters\n setFilter: (dimension: string, values: FilterValue[]) => void\n fetcher: DashboardFetcher\n /** Dicionário do modelo (rótulos, descrições, formatos); `null` até carregar. */\n meta: ModelMeta | null\n /** Muda quando o devtools pede para refazer as consultas. */\n refreshKey: number\n /** Tipos de gráfico do layout: os da lib e os do app (`charts`). */\n chartTypes: ChartTypes\n /** Componentes dos tipos do app, por nome. */\n chartComponents: Record<string, ChartComponentSpec['component']>\n devtools: DashboardDevtoolsContext | null\n}\n\nconst DashboardContext = createContext<DashboardContextValue | null>(null)\n\n// Lazy: o painel não entra no bundle da página, só é baixado quando o devtools está ligado\nconst DashboardDevtools = lazy(() => import('../devtools/DashboardDevtools'))\n\nconst DEVTOOLS_DEFAULT = process.env.NODE_ENV === 'development'\n\nconst draftStorageKey = (model: string) => `bm-dashboard-devtools:draft:${model}`\n\nfunction loadDraft(model: string): ModelDef | null {\n try {\n const saved = sessionStorage.getItem(draftStorageKey(model))\n return saved ? (JSON.parse(saved) as ModelDef) : null\n } catch {\n return null\n }\n}\n\ntype DashboardProviderProps = {\n /** Nome do modelo no servidor (chave do registro de modelos do app). */\n model: string\n /** Como chamar o endpoint do app (ex.: `(q) => api.post('/dashboard/query', q).then((r) => r.data)`). */\n fetcher: DashboardFetcher\n defaultFilters?: DashboardFilters\n /**\n * Painel de desenvolvimento (debug das consultas, edição do modelo). Padrão: ligado só com\n * `NODE_ENV=development`. O endpoint também precisa de `runQuery(..., { devtools: true })`.\n */\n devtools?: boolean\n /**\n * Tipos de gráfico do app, usáveis no layout JSON (`\"type\": \"<nome>\"`) e no editor do devtools.\n * Declare fora do componente (referência estável). O servidor precisa dos mesmos campos em\n * `devtools.chartTypes` para salvar o layout.\n */\n charts?: Record<string, ChartComponentSpec>\n children: ReactNode\n}\n\n/**\n * Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o\n * mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.\n */\nexport function DashboardProvider({\n model,\n fetcher,\n defaultFilters = {},\n devtools = DEVTOOLS_DEFAULT,\n charts,\n children\n}: DashboardProviderProps) {\n const chartTypes = useMemo(() => resolveChartTypes(charts), [charts])\n const chartComponents = useMemo(\n () =>\n Object.fromEntries(\n Object.entries(charts ?? {}).map(([name, spec]) => [name, spec.component])\n ),\n [charts]\n )\n const [filters, setFilters] = useState<DashboardFilters>(defaultFilters)\n const defaultsRef = useRef(defaultFilters)\n\n const setFilter = useCallback(\n (dimension: string, values: FilterValue[]) =>\n setFilters((current) => ({ ...current, [dimension]: values })),\n []\n )\n\n // --- devtools ---\n const store = useMemo(() => (devtools ? createDevtoolsStore() : null), [devtools])\n const layouts = useMemo(() => (devtools ? createLayoutStore() : null), [devtools])\n // O rascunho aplicado sobrevive ao reload da aba (útil enquanto se edita o modelo)\n const [draft, setDraftState] = useState<ModelDef | null>(() =>\n devtools && typeof window !== 'undefined' ? loadDraft(model) : null\n )\n const [refreshKey, setRefreshKey] = useState(0)\n const [mounted, setMounted] = useState(false)\n useEffect(() => setMounted(true), [])\n\n const setDraft = useCallback(\n (next: ModelDef | null) => {\n store?.expectRefetch()\n setDraftState(next)\n try {\n if (next) sessionStorage.setItem(draftStorageKey(model), JSON.stringify(next))\n else sessionStorage.removeItem(draftStorageKey(model))\n } catch {}\n },\n [model, store]\n )\n\n const refresh = useCallback(() => {\n store?.expectRefetch()\n setRefreshKey((key) => key + 1)\n }, [store])\n\n // Com o devtools ligado, toda consulta pede debug e, se houver, roda contra o rascunho\n const effectiveFetcher = useMemo<DashboardFetcher>(() => {\n if (!devtools) return fetcher\n return (query) =>\n fetcher({\n ...query,\n devtools: { debug: true, ...(draft && { model: draft }), ...query.devtools }\n })\n }, [fetcher, devtools, draft])\n\n const rawFetcher = useMemo<DashboardFetcher>(\n () => (query) => fetcher({ ...query, devtools: { debug: true, ...query.devtools } }),\n [fetcher]\n )\n\n // Dicionário: segue o rascunho, para os rótulos editados aparecerem na hora\n const [meta, setMeta] = useState<ModelMeta | null>(null)\n useEffect(() => {\n let cancelado = false\n effectiveFetcher({ model, meta: true })\n .then((result) => !cancelado && setMeta(result.meta ?? null))\n .catch(() => !cancelado && setMeta(null))\n return () => {\n cancelado = true\n }\n }, [effectiveFetcher, model])\n\n const devtoolsValue = useMemo(\n () =>\n store &&\n layouts && {\n store,\n layouts,\n rawFetcher,\n draft,\n setDraft,\n refresh,\n defaultFilters: defaultsRef.current,\n setFilters\n },\n [store, layouts, rawFetcher, draft, setDraft, refresh]\n )\n\n const value = useMemo(\n () => ({\n model,\n filters,\n setFilter,\n fetcher: effectiveFetcher,\n meta,\n refreshKey,\n chartTypes,\n chartComponents,\n devtools: devtoolsValue\n }),\n [\n model,\n filters,\n setFilter,\n effectiveFetcher,\n meta,\n refreshKey,\n chartTypes,\n chartComponents,\n devtoolsValue\n ]\n )\n\n return (\n <DashboardContext.Provider value={value}>\n {children}\n {/* Só depois de montar: o painel lê localStorage e não deve participar da hidratação */}\n {devtoolsValue && mounted && (\n <Suspense fallback={null}>\n <DashboardDevtools />\n </Suspense>\n )}\n </DashboardContext.Provider>\n )\n}\n\nexport function useDashboard() {\n const context = useContext(DashboardContext)\n if (!context) throw new Error('useDashboard precisa estar dentro de <DashboardProvider>')\n return context\n}\n","import { useEffect, useId, useMemo, useState } from 'react'\nimport type { DashboardFilters, DashboardRow } from '../types'\nimport { useDashboard } from './DashboardProvider'\n\nexport const INSPECT_ATTRIBUTE = 'data-bm-dashboard-query'\n\ntype UseModelQueryParams = {\n dimensions?: string[]\n measures?: string[]\n /**\n * Filtros do dashboard que esta consulta NÃO deve respeitar. Um filtro ignora a si mesmo para\n * listar as opções (senão, ao escolher um navio, a lista mostraria só ele).\n */\n ignoreFilters?: string[]\n /** Nome da consulta no devtools (padrão: dimensões × medidas). */\n label?: string\n /** Não consulta (devolve `rows: []`); ex.: a linha de total de uma tabela que não mostra total. */\n skip?: boolean\n}\n\nexport function useModelQuery({\n dimensions,\n measures,\n ignoreFilters,\n label,\n skip = false\n}: UseModelQueryParams) {\n const { model, filters, fetcher, refreshKey, devtools } = useDashboard()\n const instanceId = useId()\n\n const applied: DashboardFilters = {}\n for (const [dimension, values] of Object.entries(filters)) {\n if (values.length && !ignoreFilters?.includes(dimension)) applied[dimension] = values\n }\n\n // Chave por valor: só refaz a consulta quando o que vai pro servidor muda (não a cada render,\n // nem quando muda um filtro que esta consulta ignora)\n const key = JSON.stringify([model, dimensions, measures, applied])\n const query = useMemo(() => ({ model, dimensions, measures, filters: applied }), [key])\n\n const [rows, setRows] = useState<DashboardRow[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<unknown>()\n\n const store = devtools?.store\n const debugLabel =\n label ?? [dimensions?.join(', '), measures?.join(', ')].filter(Boolean).join(' × ')\n\n useEffect(() => {\n if (skip) {\n setRows([])\n setIsLoading(false)\n return\n }\n let cancelado = false\n setIsLoading(true)\n const runId = store?.start(instanceId, debugLabel, query)\n fetcher(query)\n .then((result) => {\n if (cancelado) return\n setRows(result.rows)\n setError(undefined)\n if (runId) store?.succeed(runId, result.rows, result.devtools?.debug)\n })\n .catch((err) => {\n if (cancelado) return\n setError(err)\n if (runId) store?.fail(runId, err)\n })\n .finally(() => !cancelado && setIsLoading(false))\n return () => {\n cancelado = true\n if (runId) store?.cancel(runId)\n }\n }, [fetcher, query, refreshKey, skip])\n\n useEffect(() => (store ? () => store.remove(instanceId) : undefined), [store, instanceId, skip])\n\n // Espalhar no elemento raiz do componente: é como o inspetor do devtools acha o componente na tela\n const inspectProps = store && !skip ? { [INSPECT_ATTRIBUTE]: instanceId } : {}\n\n return { rows, isLoading, error, inspectProps }\n}\n","import {\n Box,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Typography\n} from '@mui/material'\nimport { AGGREGATION_LABEL, isAggregate, type ModelMeta, type ValueFormat } from '../model'\nimport { useDashboard } from './DashboardProvider'\n\nconst FORMAT_LABEL: Record<ValueFormat, string> = {\n number: 'Número',\n integer: 'Inteiro',\n currency: 'Moeda (R$)',\n percent: 'Percentual'\n}\n\n/** Como a medida é calculada, em termos de negócio (o SQL não sai do servidor). */\nfunction formula(meta: ModelMeta, name: string) {\n const measure = meta.measures[name]\n const label = (ref: string) => meta.measures[ref]?.label ?? ref\n if (measure.type === 'formula' && measure.refs?.length)\n return `Combina ${measure.refs.map(label).join(', ')}`\n if (isAggregate(measure)) return AGGREGATION_LABEL[measure.type]\n if (measure.type !== 'ratio' || !measure.numerator || !measure.denominator) return '—'\n return `${label(measure.numerator)} ÷ ${label(measure.denominator)}`\n}\n\n/**\n * Dicionário do modelo do `DashboardProvider`: o que cada filtro/eixo e cada número significa. É\n * montado a partir de `label`/`description` das dimensões e medidas — documentação que não\n * desatualiza, porque é a mesma definição que calcula os números.\n */\nexport function ModelDictionary() {\n const { meta } = useDashboard()\n if (!meta) return null\n\n const header = (labels: string[]) => (\n <TableHead>\n <TableRow>\n {labels.map((label) => (\n <TableCell key={label} sx={{ fontWeight: 700 }}>\n {label}\n </TableCell>\n ))}\n </TableRow>\n </TableHead>\n )\n\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <Box>\n <Typography sx={{ fontWeight: 700, mb: 1 }}>Indicadores</Typography>\n <TableContainer>\n <Table size=\"small\">\n {header(['Indicador', 'Descrição', 'Cálculo', 'Formato'])}\n <TableBody>\n {Object.entries(meta.measures).map(([name, measure]) => (\n <TableRow key={name}>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{measure.label ?? name}</TableCell>\n <TableCell>{measure.description ?? '—'}</TableCell>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{formula(meta, name)}</TableCell>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>\n {measure.format ? FORMAT_LABEL[measure.format] : '—'}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </Box>\n <Box>\n <Typography sx={{ fontWeight: 700, mb: 1 }}>Filtros e agrupamentos</Typography>\n <TableContainer>\n <Table size=\"small\">\n {header(['Campo', 'Descrição'])}\n <TableBody>\n {Object.entries(meta.dimensions).map(([name, dimension]) => (\n <TableRow key={name}>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{dimension.label ?? name}</TableCell>\n <TableCell>{dimension.description ?? '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </Box>\n </Box>\n )\n}\n"],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ComponentType, ReactNode } from "react";
|
|
1
|
+
import { B as ModelDef, S as ChartsLayout, V as ModelMeta, a as DashboardResult, b as ChartTypeSpec, i as DashboardQueryDebug, l as FilterValue, n as DashboardFilters$1, o as DashboardRow, r as DashboardQuery, v as ChartDef, x as ChartTypes, z as MeasureMeta } from "../types-BO_AQNxv.js";
|
|
2
|
+
import { ComponentType, MouseEvent, ReactNode } from "react";
|
|
3
3
|
//#region src/client/BarChart.d.ts
|
|
4
4
|
type BarChartProps = {
|
|
5
5
|
/** Dimensão das categorias. */
|
|
@@ -82,6 +82,39 @@ declare function ComboChart({ dimension, bar, line, detail, barLabel, lineLabel,
|
|
|
82
82
|
declare const SERIES_COLORS: readonly ['#F58220', '#2a78d6', '#1baf7a', '#eda100', '#e87ba4', '#008300', '#6250d6', '#e34948'];
|
|
83
83
|
/** "Outros" (fatias agrupadas) fica neutro, fora da paleta. */
|
|
84
84
|
declare const OTHER_COLOR = "#9e9e9e";
|
|
85
|
+
declare const seriesColor: (index: number) => string;
|
|
86
|
+
/** Valor numérico de uma coluna da linha (`null` quando vazio). */
|
|
87
|
+
declare const numberOf: (row: DashboardRow, key?: string) => number | null;
|
|
88
|
+
/**
|
|
89
|
+
* Filtro cruzado a partir do gráfico: clicar num valor filtra o dashboard por ele (Ctrl/Cmd+clique
|
|
90
|
+
* acumula; clicar de novo no único selecionado limpa). O gráfico deve ignorar o próprio filtro na
|
|
91
|
+
* consulta (`ignoreFilters`) e só destacar a seleção.
|
|
92
|
+
*/
|
|
93
|
+
declare function useClickFilter(dimension: string | undefined, enabled: boolean): {
|
|
94
|
+
selected: FilterValue[];
|
|
95
|
+
/** Há seleção e este valor não está nela. */
|
|
96
|
+
isDimmed: (value: FilterValue) => boolean;
|
|
97
|
+
toggle: (value: FilterValue, event?: Pick<MouseEvent, 'ctrlKey' | 'metaKey'>) => void;
|
|
98
|
+
clear: () => void;
|
|
99
|
+
};
|
|
100
|
+
declare function Legend({ items, loading }: {
|
|
101
|
+
items: {
|
|
102
|
+
label: string;
|
|
103
|
+
color: string;
|
|
104
|
+
}[];
|
|
105
|
+
loading?: boolean;
|
|
106
|
+
}): import("react").JSX.Element;
|
|
107
|
+
/** Erro / carregando / sem dados no lugar do gráfico; `null` quando há o que desenhar (função, não componente). */
|
|
108
|
+
declare function chartPlaceholder({ height, error, isLoading, empty }: {
|
|
109
|
+
height: number;
|
|
110
|
+
error: unknown;
|
|
111
|
+
isLoading: boolean;
|
|
112
|
+
empty: boolean;
|
|
113
|
+
}): ReactNode;
|
|
114
|
+
declare function TooltipBox({ title, children }: {
|
|
115
|
+
title: string;
|
|
116
|
+
children: ReactNode;
|
|
117
|
+
}): import("react").JSX.Element;
|
|
85
118
|
//#endregion
|
|
86
119
|
//#region src/client/DashboardCanvas.d.ts
|
|
87
120
|
type DashboardCanvasProps = {
|
|
@@ -230,6 +263,19 @@ type DevtoolsStore = ReturnType<typeof createDevtoolsStore>;
|
|
|
230
263
|
//#endregion
|
|
231
264
|
//#region src/client/DashboardProvider.d.ts
|
|
232
265
|
type DashboardFetcher = (query: DashboardQuery) => Promise<DashboardResult>;
|
|
266
|
+
/**
|
|
267
|
+
* Tipo de gráfico do app: os campos do JSON (`ChartTypeSpec`) e o componente que os renderiza. O
|
|
268
|
+
* componente recebe os campos como props (com os `default` aplicados e formatters já resolvidos) e
|
|
269
|
+
* `queryLabel` — ver `ChartComponentProps`.
|
|
270
|
+
*/
|
|
271
|
+
type ChartComponentSpec = ChartTypeSpec & {
|
|
272
|
+
component: ComponentType<any>;
|
|
273
|
+
};
|
|
274
|
+
/** Props de um componente registrado em `charts`: os campos do tipo + `queryLabel`. */
|
|
275
|
+
type ChartComponentProps<Fields extends object = Record<string, unknown>> = Fields & {
|
|
276
|
+
/** Nome da consulta no devtools (`<título> · <tipo>`): passe ao `useModelQuery({ label })`. */
|
|
277
|
+
queryLabel: string;
|
|
278
|
+
};
|
|
233
279
|
/** Só existe com o devtools ligado. */
|
|
234
280
|
type DashboardDevtoolsContext = {
|
|
235
281
|
store: DevtoolsStore;
|
|
@@ -254,6 +300,10 @@ type DashboardContextValue = {
|
|
|
254
300
|
meta: ModelMeta | null;
|
|
255
301
|
/** Muda quando o devtools pede para refazer as consultas. */
|
|
256
302
|
refreshKey: number;
|
|
303
|
+
/** Tipos de gráfico do layout: os da lib e os do app (`charts`). */
|
|
304
|
+
chartTypes: ChartTypes;
|
|
305
|
+
/** Componentes dos tipos do app, por nome. */
|
|
306
|
+
chartComponents: Record<string, ChartComponentSpec['component']>;
|
|
257
307
|
devtools: DashboardDevtoolsContext | null;
|
|
258
308
|
};
|
|
259
309
|
type DashboardProviderProps = {
|
|
@@ -267,13 +317,19 @@ type DashboardProviderProps = {
|
|
|
267
317
|
* `NODE_ENV=development`. O endpoint também precisa de `runQuery(..., { devtools: true })`.
|
|
268
318
|
*/
|
|
269
319
|
devtools?: boolean;
|
|
320
|
+
/**
|
|
321
|
+
* Tipos de gráfico do app, usáveis no layout JSON (`"type": "<nome>"`) e no editor do devtools.
|
|
322
|
+
* Declare fora do componente (referência estável). O servidor precisa dos mesmos campos em
|
|
323
|
+
* `devtools.chartTypes` para salvar o layout.
|
|
324
|
+
*/
|
|
325
|
+
charts?: Record<string, ChartComponentSpec>;
|
|
270
326
|
children: ReactNode;
|
|
271
327
|
};
|
|
272
328
|
/**
|
|
273
329
|
* Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o
|
|
274
330
|
* mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.
|
|
275
331
|
*/
|
|
276
|
-
declare function DashboardProvider({ model, fetcher, defaultFilters, devtools, children }: DashboardProviderProps): import("react").JSX.Element;
|
|
332
|
+
declare function DashboardProvider({ model, fetcher, defaultFilters, devtools, charts, children }: DashboardProviderProps): import("react").JSX.Element;
|
|
277
333
|
declare function useDashboard(): DashboardContextValue;
|
|
278
334
|
//#endregion
|
|
279
335
|
//#region src/client/DataTable.d.ts
|
|
@@ -406,5 +462,5 @@ declare function useModelQuery({ dimensions, measures, ignoreFilters, label, ski
|
|
|
406
462
|
};
|
|
407
463
|
};
|
|
408
464
|
//#endregion
|
|
409
|
-
export { BarChart, type ChartPanelProps, ComboChart, DashboardCanvas, DashboardCharts, type DashboardFetcher, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, ModelDictionary, OTHER_COLOR, SERIES_COLORS, formatCurrency, formatMeasure, formatNumber, measureFormatter, useDashboard, useModelQuery };
|
|
465
|
+
export { BarChart, type ChartComponentProps, type ChartComponentSpec, type ChartPanelProps, ComboChart, DashboardCanvas, DashboardCharts, type DashboardFetcher, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartPlaceholder, formatCurrency, formatMeasure, formatNumber, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
410
466
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/client/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as isBuiltinChart, s as customChartProps } from "../layout-DvHS8XFz.js";
|
|
2
|
+
import { a as useDashboard, c as layoutItemKey, i as DashboardProvider, n as INSPECT_ATTRIBUTE, r as useModelQuery, s as LAYOUT_ITEM_ATTRIBUTE, t as ModelDictionary } from "../ModelDictionary-DN77UDVM.js";
|
|
2
3
|
import { Box, Checkbox, CircularProgress, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Paper, Select, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, Typography } from "@mui/material";
|
|
3
4
|
import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, ComposedChart, LabelList, Line, Pie, PieChart, ResponsiveContainer, Tooltip as Tooltip$1, XAxis, YAxis } from "recharts";
|
|
4
5
|
import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
@@ -1183,6 +1184,12 @@ function DashboardCharts({ layout, formatters = {}, panel: Panel = DefaultPanel,
|
|
|
1183
1184
|
});
|
|
1184
1185
|
}
|
|
1185
1186
|
function Chart({ chart, formatters }) {
|
|
1187
|
+
const { chartTypes, chartComponents } = useDashboard();
|
|
1188
|
+
if (!isBuiltinChart(chart)) {
|
|
1189
|
+
const Component = chartComponents[chart.type];
|
|
1190
|
+
if (Component) return /* @__PURE__ */ jsx(Component, { ...customChartProps(chart, chartTypes, formatters) });
|
|
1191
|
+
return /* @__PURE__ */ jsx(UnknownChart, { type: chart.type });
|
|
1192
|
+
}
|
|
1186
1193
|
const queryLabel = `${chart.title || chart.id} · ${chart.type}`;
|
|
1187
1194
|
const formatter = (name) => name ? formatters[name] : void 0;
|
|
1188
1195
|
switch (chart.type) {
|
|
@@ -1230,15 +1237,25 @@ function Chart({ chart, formatters }) {
|
|
|
1230
1237
|
formatDimensions: resolved
|
|
1231
1238
|
});
|
|
1232
1239
|
}
|
|
1233
|
-
default: return /* @__PURE__ */
|
|
1234
|
-
sx: {
|
|
1235
|
-
color: "error.main",
|
|
1236
|
-
p: 2
|
|
1237
|
-
},
|
|
1238
|
-
children: ["Tipo de gráfico desconhecido: ", String(chart.type)]
|
|
1239
|
-
});
|
|
1240
|
+
default: return /* @__PURE__ */ jsx(UnknownChart, { type: chart.type });
|
|
1240
1241
|
}
|
|
1241
1242
|
}
|
|
1243
|
+
function UnknownChart({ type }) {
|
|
1244
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1245
|
+
sx: {
|
|
1246
|
+
color: "error.main",
|
|
1247
|
+
p: 2
|
|
1248
|
+
},
|
|
1249
|
+
children: [
|
|
1250
|
+
"Tipo de gráfico desconhecido: ",
|
|
1251
|
+
String(type),
|
|
1252
|
+
" (tipos do app vão no",
|
|
1253
|
+
" ",
|
|
1254
|
+
/* @__PURE__ */ jsx("code", { children: "<DashboardProvider charts>" }),
|
|
1255
|
+
")"
|
|
1256
|
+
]
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1242
1259
|
//#endregion
|
|
1243
1260
|
//#region src/client/FilterSelect.tsx
|
|
1244
1261
|
/**
|
|
@@ -1320,6 +1337,6 @@ function DashboardFilters({ layout, formatters = {}, gap = 2 }) {
|
|
|
1320
1337
|
});
|
|
1321
1338
|
}
|
|
1322
1339
|
//#endregion
|
|
1323
|
-
export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, ModelDictionary, OTHER_COLOR, SERIES_COLORS, formatCurrency, formatMeasure, formatNumber, measureFormatter, useDashboard, useModelQuery };
|
|
1340
|
+
export { BarChart, ComboChart, DashboardCanvas, DashboardCharts, DashboardFilters, DashboardProvider, DataTable, DonutChart, FilterSelect, INSPECT_ATTRIBUTE, KpiCard, Legend, ModelDictionary, OTHER_COLOR, SERIES_COLORS, TooltipBox, chartPlaceholder, formatCurrency, formatMeasure, formatNumber, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
1324
1341
|
|
|
1325
1342
|
//# sourceMappingURL=index.js.map
|