@bluemarble/bi 0.6.4 → 0.6.6
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-CSRt9lib.js → DashboardDevtools-I00Iz0Z8.js} +424 -240
- package/dist/DashboardDevtools-I00Iz0Z8.js.map +1 -0
- package/dist/{ModelDictionary-Dib4Vk3i.js → ModelDictionary-DUGFHBo-.js} +123 -6
- package/dist/ModelDictionary-DUGFHBo-.js.map +1 -0
- package/dist/cli.js +167 -11
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +65 -7
- package/dist/client/index.js +222 -161
- package/dist/client/index.js.map +1 -1
- package/dist/{codegen-DkfI6UJr.js → codegen-LoQzFAle.js} +3 -1
- package/dist/codegen-LoQzFAle.js.map +1 -0
- package/dist/{layout-CoGyGWYM.d.ts → formatRules-DTKbov47.d.ts} +272 -207
- package/dist/{layout-nViz3MdU.js → layout-CrZ00tB0.js} +187 -15
- package/dist/layout-CrZ00tB0.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/DashboardDevtools-CSRt9lib.js.map +0 -1
- package/dist/ModelDictionary-Dib4Vk3i.js.map +0 -1
- package/dist/codegen-DkfI6UJr.js.map +0 -1
- package/dist/layout-nViz3MdU.js.map +0 -1
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli/scaffold.ts","../src/cli/index.ts"],"sourcesContent":["import { literal, toModelCode } from '../codegen'\nimport type { ChartsLayout } from '../layout'\n\n/**\n * Arquivos de um dashboard novo: o modelo vazio (`models/<nome>.ts`), o registro dele no\n * `models/index.ts` e o layout vazio (`layouts/<id>.json`). Tudo como texto, sem tocar no disco —\n * quem grava é o CLI (`bm-bi new`).\n */\n\nexport class ScaffoldError extends Error {}\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/\nconst LAYOUT_ID = /^[\\w-]+$/\n\nexport function checkModelName(name: string) {\n if (!IDENTIFIER.test(name))\n throw new ScaffoldError(\n `Nome de modelo inválido: \"${name}\" (use um identificador JS, ex.: analiseCusto)`\n )\n}\n\nexport function checkLayoutId(id: string) {\n if (!LAYOUT_ID.test(id))\n throw new ScaffoldError(`Id de layout inválido: \"${id}\" (só letras, números, _ e -)`)\n}\n\n/** Modelo vazio, pronto para montar pelo diagrama da aba Modelo do devtools. */\nexport function modelFileCode(name: string) {\n checkModelName(name)\n return toModelCode({ name, tables: {}, dimensions: {}, measures: {} }).replace(\n 'export const',\n '/** Monte tabelas, relacionamentos, dimensões e medidas pela aba Modelo do devtools. */\\nexport const'\n )\n}\n\n/** Layout vazio, pronto para montar pela aba Layout do devtools. */\nexport function layoutFileCode(id: string) {\n checkLayoutId(id)\n const layout: ChartsLayout = { id, filters: [], charts: [] }\n return `${JSON.stringify(layout, null, 2)}\\n`\n}\n\n/** `models/index.ts` novo, com só este modelo. */\nexport function modelIndexCode(name: string) {\n checkModelName(name)\n return [\n `import { ${name} } from './${name}'`,\n '',\n '/** Modelos expostos pelo endpoint do dashboard (`runQuery(dashboardModels, ...)`). */',\n 'export const dashboardModels = {',\n ` ${name}`,\n '}',\n ''\n ].join('\\n')\n}\n\n/**\n * Acrescenta o modelo ao `models/index.ts` existente: o import depois do último import e o nome no\n * primeiro `export const x = { ... }`. Devolve `null` se o modelo já está registrado.\n */\nexport function registerModel(source: string, name: string): string | null {\n checkModelName(name)\n const importPath = new RegExp(`from\\\\s+['\"]\\\\./${name}(\\\\.ts)?['\"]`)\n if (importPath.test(source)) return null\n\n const object = /export\\s+const\\s+\\w+(?:\\s*:[^=]+)?\\s*=\\s*\\{/.exec(source)\n if (!object)\n throw new ScaffoldError(\n 'Não encontrei `export const <nome> = { ... }` no models/index.ts — registre o modelo à mão.'\n )\n const open = object.index + object[0].length - 1\n let depth = 0\n let close = -1\n for (let i = open; i < source.length; i++) {\n if (source[i] === '{') depth++\n else if (source[i] === '}' && --depth === 0) {\n close = i\n break\n }\n }\n if (close === -1) throw new ScaffoldError('Objeto de modelos do models/index.ts sem `}`.')\n\n const body = source.slice(open + 1, close)\n const items = body.trimEnd()\n let newBody: string\n if (!items.trim()) newBody = `\\n ${name}\\n`\n else if (!body.includes('\\n')) newBody = ` ${items.trim().replace(/,$/, '')}, ${name} `\n else newBody = `${items.endsWith(',') ? items : `${items},`}\\n ${name}\\n`\n let result = `${source.slice(0, open + 1)}${newBody}${source.slice(close)}`\n\n // Import logo depois do último import do arquivo (ou no topo, se não houver)\n const line = `import { ${name} } from './${name}'\\n`\n const imports = [...result.matchAll(/^import[\\s\\S]*?from\\s+['\"][^'\"]+['\"];?[^\\n]*\\n/gm)]\n const last = imports.at(-1)\n const at = last ? last.index + last[0].length : 0\n result = `${result.slice(0, at)}${line}${last ? '' : '\\n'}${result.slice(at)}`\n return result\n}\n\n// --- página ---\n\n/** `analiseCusto` → `analise-custo` (rota da página). */\nexport const kebabCase = (text: string) =>\n text\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[_\\s]+/g, '-')\n .toLowerCase()\n\n/** `analiseCusto` → `Analise Custo` (título padrão da página). */\nexport const titleCase = (text: string) =>\n kebabCase(text)\n .split('-')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n\n/** `analiseCusto` → `AnaliseCustoPage` (nome do componente). */\nexport const pageComponentName = (text: string) => `${titleCase(text).replaceAll(' ', '')}Page`\n\nexport type PageOptions = {\n model: string\n /** Import do JSON do layout, relativo à página (ex.: `../../dashboard/layouts/vendas.json`). */\n layoutImport: string\n title: string\n /** Endpoint do `runQuery` (padrão `/api/dashboard/query`). */\n endpoint: string\n /** App Router do Next: a página vira client component (`'use client'`). */\n clientDirective?: boolean\n /** Import do registro de tipos de gráfico do app (`export const charts`), relativo à página. */\n chartsImport?: string\n}\n\n/**\n * Valores trocados num template de página do app (`page.template`): o template é a página de um\n * dashboard com esses marcadores no lugar do que muda de um para outro.\n */\nexport const pageTemplateValues = (options: PageOptions): Record<string, string> => ({\n __MODEL__: options.model,\n __LAYOUT__: options.layoutImport,\n __TITLE__: options.title,\n __COMPONENT__: pageComponentName(options.model),\n __ENDPOINT__: options.endpoint\n})\n\nexport function fillPageTemplate(template: string, options: PageOptions) {\n let content = template\n for (const [marker, value] of Object.entries(pageTemplateValues(options)))\n content = content.replaceAll(marker, value)\n return content\n}\n\n/** Página padrão: provider, filtros e gráficos do layout, consultando o endpoint com `fetch`. */\nexport function pageFileCode(options: PageOptions) {\n const { model, layoutImport, title, endpoint, clientDirective, chartsImport } = options\n if (endpoint.includes(\"'\")) throw new ScaffoldError(`Endpoint inválido: ${endpoint}`)\n checkModelName(model)\n return [\n ...(clientDirective ? [\"'use client'\", ''] : []),\n 'import {',\n ' DashboardCanvas,',\n ' DashboardCharts,',\n ' type DashboardFetcher,',\n ' DashboardFilters,',\n ' DashboardProvider',\n \"} from '@bluemarble/bi/client'\",\n \"import type { ChartsLayout, DashboardResult } from '@bluemarble/bi/types'\",\n \"import { Box, Typography } from '@mui/material'\",\n ...(chartsImport ? [`import { charts } from '${chartsImport}'`] : []),\n `import layout from '${layoutImport}'`,\n '',\n '/**',\n ` * Dashboard do modelo \\`${model}\\`. Filtros, gráficos e o tamanho da tela vêm do layout JSON,`,\n ' * editável pela aba Layout do devtools (◆ BI, em desenvolvimento).',\n ' */',\n '',\n `const TITULO = ${literal(title)}`,\n 'const chartsLayout = layout as ChartsLayout',\n '',\n '// Fora do componente: referência estável, senão toda consulta refaria a cada render',\n 'const fetcher: DashboardFetcher = async (query) => {',\n ` const response = await fetch('${endpoint}', {`,\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json' },\",\n ' body: JSON.stringify(query)',\n ' })',\n ' if (!response.ok) throw new Error(await response.text())',\n ' return (await response.json()) as DashboardResult',\n '}',\n '',\n `export default function ${pageComponentName(model)}() {`,\n ' return (',\n ` <DashboardProvider model=\"${model}\" fetcher={fetcher}${chartsImport ? ' charts={charts}' : ''}>`,\n ' <Box sx={{ p: 2 }}>',\n ' {/* Com `canvas` no JSON, tela de tamanho fixo que escala; sem, responsivo */}',\n ' <DashboardCanvas layout={chartsLayout}>',\n \" <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>\",\n ' <Typography component=\"h1\" variant=\"h5\">',\n ' {TITULO}',\n ' </Typography>',\n ' <DashboardFilters layout={chartsLayout} />',\n ' <DashboardCharts layout={chartsLayout} />',\n ' </Box>',\n ' </DashboardCanvas>',\n ' </Box>',\n ' </DashboardProvider>',\n ' )',\n '}',\n ''\n ].join('\\n')\n}\n\n/**\n * Extensão das páginas do Pages Router pelo `pageExtensions` do `next.config` (ex.: `page.tsx` no\n * `custo`). Sem a opção (ou sem config), `tsx`.\n */\nexport function pageExtension(nextConfig: string | undefined) {\n const list = /pageExtensions\\s*:\\s*\\[([^\\]]*)\\]/.exec(nextConfig ?? '')?.[1]\n const extensions = [...(list ?? '').matchAll(/['\"]([^'\"]+)['\"]/g)].map((match) => match[1])\n return extensions.find((extension) => extension?.endsWith('tsx')) ?? 'tsx'\n}\n","#!/usr/bin/env node\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, dirname, join, relative, sep } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport {\n checkLayoutId,\n checkModelName,\n fillPageTemplate,\n kebabCase,\n layoutFileCode,\n modelFileCode,\n modelIndexCode,\n type PageOptions,\n pageExtension,\n pageFileCode,\n registerModel,\n ScaffoldError,\n titleCase\n} from './scaffold'\n\nconst HELP = `Uso: bm-bi new <nome> [opções]\n\nCria um dashboard vazio: o modelo (models/<nome>.ts, registrado em models/index.ts), o layout\n(layouts/<id>.json) e a página. Depois é montar tudo pelas abas Modelo e Layout do devtools.\n\nA página vai para o router do Next que existir: src/pages (ou pages) →\ndashboard/<id-em-kebab>/index.<ext>, com a extensão do pageExtensions do next.config; src/app (ou\napp) → dashboard/<id-em-kebab>/page.tsx, com 'use client'. Com <dir>/page.template, a página sai\ndele, trocando __MODEL__, __LAYOUT__ (import do JSON), __TITLE__, __COMPONENT__ e __ENDPOINT__.\n\nOpções:\n --id <id> id do layout (padrão: o nome do modelo)\n --title <texto> título da página (padrão: o id em palavras)\n --dir <pasta> pasta dos dashboards (padrão: src/dashboard)\n --models <pasta> pasta dos modelos (padrão: <dir>/models)\n --layouts <pasta> pasta dos layouts (padrão: <dir>/layouts)\n --page <arquivo> arquivo da página (padrão: pelo router do Next, ver acima)\n --skip-page não cria a página\n --template <arq.> template da página (padrão: <dir>/page.template, se existir)\n --endpoint <url> endpoint do runQuery na página padrão (padrão: /api/dashboard/query)\n -h, --help esta ajuda\n`\n\nconst exists = (path: string) => existsSync(path)\n\n/** Caminho de import de `to` a partir do arquivo `from` (sempre com `./` ou `../` e `/`). */\nfunction importPath(from: string, to: string) {\n const path = relative(dirname(from), to).split(sep).join('/')\n return path.startsWith('.') ? path : `./${path}`\n}\n\n/** Página pelo router do Next que existir no app (Pages primeiro, como o `custo`). */\nfunction defaultPagePath(cwd: string, slug: string) {\n for (const pages of ['src/pages', 'pages']) {\n if (!exists(join(cwd, pages))) continue\n const config = ['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.cjs']\n .map((file) => join(cwd, file))\n .find(exists)\n const extension = pageExtension(config && readFileSync(config, 'utf8'))\n return join(cwd, pages, 'dashboard', slug, `index.${extension}`)\n }\n for (const app of ['src/app', 'app'])\n if (exists(join(cwd, app))) return join(cwd, app, 'dashboard', slug, 'page.tsx')\n return undefined\n}\n\n/** `export const charts` em <dir>/charts: os tipos de gráfico do app entram no provider. */\nfunction chartsIndex(dir: string) {\n return ['index.ts', 'index.tsx']\n .map((file) => join(dir, 'charts', file))\n .find((path) => exists(path) && /export\\s+const\\s+charts\\b/.test(readFileSync(path, 'utf8')))\n}\n\nfunction main(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n id: { type: 'string' },\n dir: { type: 'string', default: 'src/dashboard' },\n models: { type: 'string' },\n layouts: { type: 'string' },\n title: { type: 'string' },\n page: { type: 'string' },\n 'skip-page': { type: 'boolean' },\n template: { type: 'string' },\n endpoint: { type: 'string', default: '/api/dashboard/query' },\n help: { type: 'boolean', short: 'h' }\n }\n })\n const [command, name, ...rest] = positionals\n if (values.help || !command) {\n process.stdout.write(HELP)\n return\n }\n if (command !== 'new') throw new ScaffoldError(`Comando desconhecido: ${command}\\n\\n${HELP}`)\n if (!name) throw new ScaffoldError(`Falta o nome do modelo.\\n\\n${HELP}`)\n if (rest.length) throw new ScaffoldError(`Argumentos a mais: ${rest.join(' ')}`)\n\n const id = values.id ?? name\n checkModelName(name)\n checkLayoutId(id)\n\n const cwd = process.cwd()\n const modelsDir = join(cwd, values.models ?? join(values.dir, 'models'))\n const layoutsDir = join(cwd, values.layouts ?? join(values.dir, 'layouts'))\n const modelPath = join(modelsDir, `${name}.ts`)\n const indexPath = join(modelsDir, 'index.ts')\n const layoutPath = join(layoutsDir, `${id}.json`)\n const dashboardDir = join(cwd, values.dir)\n const pagePath = values['skip-page']\n ? undefined\n : values.page\n ? join(cwd, values.page)\n : defaultPagePath(cwd, kebabCase(id))\n const templatePath = values.template\n ? join(cwd, values.template)\n : [join(dashboardDir, 'page.template')].find(exists)\n if (templatePath && !exists(templatePath))\n throw new ScaffoldError(`Template não encontrado: ${relative(cwd, templatePath)}`)\n\n // Confere tudo antes de escrever: ou cria o dashboard inteiro, ou nada\n for (const path of [modelPath, layoutPath, pagePath])\n if (path && existsSync(path)) throw new ScaffoldError(`Já existe: ${relative(cwd, path)}`)\n const index = existsSync(indexPath)\n ? registerModel(readFileSync(indexPath, 'utf8'), name)\n : modelIndexCode(name)\n let page: string | undefined\n if (pagePath) {\n const charts = chartsIndex(dashboardDir)\n const options: PageOptions = {\n model: name,\n layoutImport: importPath(pagePath, layoutPath),\n title: values.title ?? titleCase(id),\n endpoint: values.endpoint,\n // App Router: a página usa hooks, então é client component\n clientDirective:\n basename(pagePath).startsWith('page.') &&\n relative(cwd, pagePath).split(sep).includes('app'),\n chartsImport: charts && importPath(pagePath, dirname(charts))\n }\n page = templatePath\n ? fillPageTemplate(readFileSync(templatePath, 'utf8'), options)\n : pageFileCode(options)\n }\n\n const written: string[] = []\n const write = (path: string, content: string) => {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n written.push(relative(cwd, path))\n }\n write(modelPath, modelFileCode(name))\n if (index !== null) write(indexPath, index)\n write(layoutPath, layoutFileCode(id))\n if (pagePath && page !== undefined) write(pagePath, page)\n\n process.stdout.write(\n [\n ...written.map((path) => `✓ ${path}`),\n ...(pagePath\n ? templatePath\n ? [` (página do template ${relative(cwd, templatePath)})`]\n : []\n : values['skip-page']\n ? []\n : [\n '',\n 'Página não criada: não achei src/pages nem src/app. Use --page <arquivo>, com o layout:',\n ` <DashboardProvider model=\"${name}\" fetcher={fetcher}>`,\n ' <DashboardFilters layout={layout} />',\n ' <DashboardCharts layout={layout} />',\n ' </DashboardProvider>'\n ]),\n ''\n ].join('\\n')\n )\n}\n\ntry {\n main(process.argv.slice(2))\n} catch (error) {\n if (!(error instanceof ScaffoldError) && !(error instanceof TypeError)) throw error\n process.stderr.write(`${error.message}\\n`)\n process.exitCode = 1\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli/scaffold.ts","../src/cli/index.ts"],"sourcesContent":["import { literal, toModelCode } from '../codegen'\nimport type { ChartsLayout } from '../layout'\n\n/**\n * Arquivos de um dashboard novo: o modelo vazio (`models/<nome>.ts`), o registro dele no\n * `models/index.ts` e o layout vazio (`layouts/<id>.json`). Tudo como texto, sem tocar no disco —\n * quem grava é o CLI (`bm-bi new`).\n */\n\nexport class ScaffoldError extends Error {}\n\nconst IDENTIFIER = /^[A-Za-z_$][\\w$]*$/\nconst LAYOUT_ID = /^[\\w-]+$/\n\nexport function checkModelName(name: string) {\n if (!IDENTIFIER.test(name))\n throw new ScaffoldError(\n `Nome de modelo inválido: \"${name}\" (use um identificador JS, ex.: analiseCusto)`\n )\n}\n\nexport function checkLayoutId(id: string) {\n if (!LAYOUT_ID.test(id))\n throw new ScaffoldError(`Id de layout inválido: \"${id}\" (só letras, números, _ e -)`)\n}\n\n/** Modelo vazio, pronto para montar pelo diagrama da aba Modelo do devtools. */\nexport function modelFileCode(name: string) {\n checkModelName(name)\n return toModelCode({ name, tables: {}, dimensions: {}, measures: {} }).replace(\n 'export const',\n '/** Monte tabelas, relacionamentos, dimensões e medidas pela aba Modelo do devtools. */\\nexport const'\n )\n}\n\n/** Layout vazio, pronto para montar pela aba Layout do devtools. */\nexport function layoutFileCode(id: string) {\n checkLayoutId(id)\n const layout: ChartsLayout = { id, filters: [], charts: [] }\n return `${JSON.stringify(layout, null, 2)}\\n`\n}\n\n/** `models/index.ts` novo, com só este modelo. */\nexport function modelIndexCode(name: string) {\n checkModelName(name)\n return [\n `import { ${name} } from './${name}'`,\n '',\n '/** Modelos expostos pelo endpoint do dashboard (`runQuery(dashboardModels, ...)`). */',\n 'export const dashboardModels = {',\n ` ${name}`,\n '}',\n ''\n ].join('\\n')\n}\n\n/**\n * Acrescenta o modelo ao `models/index.ts` existente: o import depois do último import e o nome no\n * primeiro `export const x = { ... }`. Devolve `null` se o modelo já está registrado.\n */\nexport function registerModel(source: string, name: string): string | null {\n checkModelName(name)\n const importPath = new RegExp(`from\\\\s+['\"]\\\\./${name}(\\\\.ts)?['\"]`)\n if (importPath.test(source)) return null\n\n const object = /export\\s+const\\s+\\w+(?:\\s*:[^=]+)?\\s*=\\s*\\{/.exec(source)\n if (!object)\n throw new ScaffoldError(\n 'Não encontrei `export const <nome> = { ... }` no models/index.ts — registre o modelo à mão.'\n )\n const open = object.index + object[0].length - 1\n let depth = 0\n let close = -1\n for (let i = open; i < source.length; i++) {\n if (source[i] === '{') depth++\n else if (source[i] === '}' && --depth === 0) {\n close = i\n break\n }\n }\n if (close === -1) throw new ScaffoldError('Objeto de modelos do models/index.ts sem `}`.')\n\n const body = source.slice(open + 1, close)\n const items = body.trimEnd()\n let newBody: string\n if (!items.trim()) newBody = `\\n ${name}\\n`\n else if (!body.includes('\\n')) newBody = ` ${items.trim().replace(/,$/, '')}, ${name} `\n else newBody = `${items.endsWith(',') ? items : `${items},`}\\n ${name}\\n`\n let result = `${source.slice(0, open + 1)}${newBody}${source.slice(close)}`\n\n // Import logo depois do último import do arquivo (ou no topo, se não houver)\n const line = `import { ${name} } from './${name}'\\n`\n const imports = [...result.matchAll(/^import[\\s\\S]*?from\\s+['\"][^'\"]+['\"];?[^\\n]*\\n/gm)]\n const last = imports.at(-1)\n const at = last ? last.index + last[0].length : 0\n result = `${result.slice(0, at)}${line}${last ? '' : '\\n'}${result.slice(at)}`\n return result\n}\n\n// --- página ---\n\n/** `analiseCusto` → `analise-custo` (rota da página). */\nexport const kebabCase = (text: string) =>\n text\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2')\n .replace(/[_\\s]+/g, '-')\n .toLowerCase()\n\n/** `analiseCusto` → `Analise Custo` (título padrão da página). */\nexport const titleCase = (text: string) =>\n kebabCase(text)\n .split('-')\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n\n/** `analiseCusto` → `AnaliseCustoPage` (nome do componente). */\nexport const pageComponentName = (text: string) => `${titleCase(text).replaceAll(' ', '')}Page`\n\nexport type PageOptions = {\n model: string\n /** Import do JSON do layout, relativo à página (ex.: `../../dashboard/layouts/vendas.json`). */\n layoutImport: string\n title: string\n /** Endpoint do `runQuery` (padrão `/api/dashboard/query`). */\n endpoint: string\n /** App Router do Next: a página vira client component (`'use client'`). */\n clientDirective?: boolean\n /** Import do registro de tipos de gráfico do app (`export const charts`), relativo à página. */\n chartsImport?: string\n}\n\n/**\n * Valores trocados num template de página do app (`page.template`): o template é a página de um\n * dashboard com esses marcadores no lugar do que muda de um para outro.\n */\nexport const pageTemplateValues = (options: PageOptions): Record<string, string> => ({\n __MODEL__: options.model,\n __LAYOUT__: options.layoutImport,\n __TITLE__: options.title,\n __COMPONENT__: pageComponentName(options.model),\n __ENDPOINT__: options.endpoint\n})\n\nexport function fillPageTemplate(template: string, options: PageOptions) {\n let content = template\n for (const [marker, value] of Object.entries(pageTemplateValues(options)))\n content = content.replaceAll(marker, value)\n return content\n}\n\n/** Página padrão: provider, filtros e gráficos do layout, consultando o endpoint com `fetch`. */\nexport function pageFileCode(options: PageOptions) {\n const { model, layoutImport, title, endpoint, clientDirective, chartsImport } = options\n if (endpoint.includes(\"'\")) throw new ScaffoldError(`Endpoint inválido: ${endpoint}`)\n checkModelName(model)\n return [\n ...(clientDirective ? [\"'use client'\", ''] : []),\n 'import {',\n ' DashboardCanvas,',\n ' DashboardCharts,',\n ' type DashboardFetcher,',\n ' DashboardFilters,',\n ' DashboardProvider',\n \"} from '@bluemarble/bi/client'\",\n \"import type { ChartsLayout, DashboardResult } from '@bluemarble/bi/types'\",\n \"import { Box, Typography } from '@mui/material'\",\n ...(chartsImport ? [`import { charts } from '${chartsImport}'`] : []),\n `import layout from '${layoutImport}'`,\n '',\n '/**',\n ` * Dashboard do modelo \\`${model}\\`. Filtros, gráficos e o tamanho da tela vêm do layout JSON,`,\n ' * editável pela aba Layout do devtools (◆ BI, em desenvolvimento).',\n ' */',\n '',\n `const TITULO = ${literal(title)}`,\n 'const chartsLayout = layout as ChartsLayout',\n '',\n '// Fora do componente: referência estável, senão toda consulta refaria a cada render',\n 'const fetcher: DashboardFetcher = async (query) => {',\n ` const response = await fetch('${endpoint}', {`,\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json' },\",\n ' body: JSON.stringify(query)',\n ' })',\n ' if (!response.ok) throw new Error(await response.text())',\n ' return (await response.json()) as DashboardResult',\n '}',\n '',\n `export default function ${pageComponentName(model)}() {`,\n ' return (',\n ` <DashboardProvider model=\"${model}\" fetcher={fetcher}${chartsImport ? ' charts={charts}' : ''}>`,\n ' <Box sx={{ p: 2 }}>',\n ' {/* Com `canvas` no JSON, tela de tamanho fixo que escala; sem, responsivo */}',\n ' <DashboardCanvas layout={chartsLayout}>',\n \" <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>\",\n ' <Typography component=\"h1\" variant=\"h5\">',\n ' {TITULO}',\n ' </Typography>',\n ' <DashboardFilters layout={chartsLayout} />',\n ' <DashboardCharts layout={chartsLayout} />',\n ' </Box>',\n ' </DashboardCanvas>',\n ' </Box>',\n ' </DashboardProvider>',\n ' )',\n '}',\n ''\n ].join('\\n')\n}\n\n/**\n * Extensão das páginas do Pages Router pelo `pageExtensions` do `next.config` (ex.: `page.tsx` no\n * `custo`). Sem a opção (ou sem config), `tsx`.\n */\nexport function pageExtension(nextConfig: string | undefined) {\n return pageExtensions(nextConfig).find((extension) => extension.endsWith('tsx')) ?? 'tsx'\n}\n\n/** Itens do `pageExtensions` do `next.config` (lido como texto). */\nfunction pageExtensions(nextConfig: string | undefined) {\n const list = /pageExtensions\\s*:\\s*\\[([^\\]]*)\\]/.exec(nextConfig ?? '')?.[1]\n return [...(list ?? '').matchAll(/['\"]([^'\"]+)['\"]/g)].map((match) => match[1] as string)\n}\n\n// --- endpoint ---\n\nexport type ApiOptions = {\n /** Import do `models/index.ts` (o `dashboardModels`), relativo ao endpoint. */\n modelsImport: string\n /** Pastas do devtools, relativas à raiz do app (`process.cwd()` no servidor). */\n modelsDir: string\n layoutsDir: string\n snapshotsDir: string\n /** Import do registro de tipos de gráfico do servidor (`export const chartTypes`). */\n chartTypesImport?: string\n /** App Router do Next: `route.ts` com `POST(request)` em vez do handler do Pages Router. */\n appRouter?: boolean\n}\n\n/** Valores trocados num template de endpoint do app (`api.template`). */\nexport const apiTemplateValues = (options: ApiOptions): Record<string, string> => ({\n __MODELS__: options.modelsImport,\n __MODELS_DIR__: options.modelsDir,\n __LAYOUTS_DIR__: options.layoutsDir,\n __SNAPSHOTS_DIR__: options.snapshotsDir\n})\n\nexport function fillApiTemplate(template: string, options: ApiOptions) {\n let content = template\n for (const [marker, value] of Object.entries(apiTemplateValues(options)))\n content = content.replaceAll(marker, value)\n return content\n}\n\n/** `src/dashboard/models` → `'src', 'dashboard', 'models'` (argumentos do `path.join`). */\nconst pathArgs = (dir: string) =>\n dir\n .split(/[\\\\/]/)\n .filter(Boolean)\n .map((part) => literal(part))\n .join(', ')\n\n/**\n * Endpoint padrão do `runQuery` com Prisma: um só para todos os dashboards (só aceita os modelos do\n * `models/index.ts`), com o devtools ligado só em desenvolvimento.\n */\nexport function apiFileCode(options: ApiOptions) {\n const { modelsImport, chartTypesImport, appRouter } = options\n const dir = (path: string) => `path.join(process.cwd(), ${pathArgs(path)})`\n const run = (body: string, indent: string) =>\n [\n 'const result = await runQuery(',\n ` dashboardModels,`,\n ` ${body} as DashboardQuery,`,\n ' (sql, params) => prisma.$queryRawUnsafe<unknown[]>(sql, ...params),',\n ' { cacheTtlMs: CACHE_TTL_MS, devtools: DEVTOOLS }',\n ')'\n ].map((line) => `${indent}${line}`)\n return [\n \"import path from 'node:path'\",\n \"import { DashboardQueryError, runQuery } from '@bluemarble/bi/server'\",\n \"import type { DashboardQuery } from '@bluemarble/bi/types'\",\n \"import { PrismaClient } from '@prisma/client'\",\n ...(appRouter ? [] : [\"import type { NextApiRequest, NextApiResponse } from 'next'\"]),\n ...(chartTypesImport ? [`import { chartTypes } from '${chartTypesImport}'`] : []),\n `import { dashboardModels } from '${modelsImport}'`,\n '',\n '// Client próprio: rotas que chamam `prisma.$disconnect()` no client global derrubariam',\n '// consultas longas em andamento (\"Engine is not yet connected\").',\n 'const globalForPrisma = globalThis as unknown as { dashboardPrisma?: PrismaClient }',\n 'const prisma = globalForPrisma.dashboardPrisma ?? new PrismaClient()',\n \"if (process.env.NODE_ENV === 'development') globalForPrisma.dashboardPrisma = prisma\",\n '',\n 'const CACHE_TTL_MS = 5 * 60 * 1000',\n '',\n '// Devtools (debug, rascunho de modelo, salvar modelo/layout) aceita SQL vindo do browser: só em dev',\n \"const DEVTOOLS = process.env.NODE_ENV === 'development' && {\",\n ` modelsDir: ${dir(options.modelsDir)},`,\n ` snapshotsDir: ${dir(options.snapshotsDir)},`,\n ` layoutsDir: ${dir(options.layoutsDir)}${chartTypesImport ? ',' : ''}`,\n ...(chartTypesImport ? [' chartTypes'] : []),\n '}',\n '',\n '/**',\n ' * Endpoint genérico do `@bluemarble/bi`: recebe `{ model, dimensions, measures, filters }` e',\n ' * devolve as linhas agregadas. Só aceita nomes definidos nos modelos registrados.',\n ' */',\n ...(appRouter\n ? [\n 'export async function POST(request: Request) {',\n ' try {',\n ...run('(await request.json())', ' '),\n ' return Response.json(result)',\n ' } catch (error) {',\n ' if (error instanceof DashboardQueryError)',\n ' return Response.json({ error: error.message }, { status: 400 })',\n ' throw error',\n ' }',\n '}'\n ]\n : [\n 'export default async function handler(req: NextApiRequest, res: NextApiResponse) {',\n \" if (req.method !== 'POST') return res.status(405).setHeader('Allow', 'POST').end()\",\n ' try {',\n ...run('req.body', ' '),\n ' return res.json(result)',\n ' } catch (error) {',\n ' if (error instanceof DashboardQueryError)',\n ' return res.status(400).json({ error: error.message })',\n ' throw error',\n ' }',\n '}'\n ]),\n ''\n ].join('\\n')\n}\n\n/**\n * Extensão das rotas de API do Pages Router pelo `pageExtensions` do `next.config` (ex.: `api.ts`\n * no `custo`). Sem a opção (ou sem config), `ts`.\n */\nexport function apiExtension(nextConfig: string | undefined) {\n return pageExtensions(nextConfig).find((extension) => extension.endsWith('ts')) ?? 'ts'\n}\n","#!/usr/bin/env node\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, dirname, join, relative, sep } from 'node:path'\nimport { parseArgs } from 'node:util'\nimport {\n type ApiOptions,\n apiExtension,\n apiFileCode,\n checkLayoutId,\n checkModelName,\n fillApiTemplate,\n fillPageTemplate,\n kebabCase,\n layoutFileCode,\n modelFileCode,\n modelIndexCode,\n type PageOptions,\n pageExtension,\n pageFileCode,\n registerModel,\n ScaffoldError,\n titleCase\n} from './scaffold'\n\nconst HELP = `Uso: bm-bi new <nome> [opções]\n\nCria um dashboard vazio: o modelo (models/<nome>.ts, registrado em models/index.ts), o layout\n(layouts/<id>.json), a página e, se ainda não existir, o endpoint do runQuery (um só para todos os\ndashboards). Depois é montar tudo pelas abas Modelo e Layout do devtools.\n\nA página vai para o router do Next que existir: src/pages (ou pages) →\ndashboard/<id-em-kebab>/index.<ext>, com a extensão do pageExtensions do next.config; src/app (ou\napp) → dashboard/<id-em-kebab>/page.tsx, com 'use client'. Com <dir>/page.template, a página sai\ndele, trocando __MODEL__, __LAYOUT__ (import do JSON), __TITLE__, __COMPONENT__ e __ENDPOINT__.\n\nO endpoint vai para a rota do --endpoint no mesmo router: src/pages → api/dashboard/query.<ext> (ext\nde API do pageExtensions); src/app → api/dashboard/query/route.ts. O padrão usa Prisma\n($queryRawUnsafe); com <dir>/api.template, sai dele, trocando __MODELS__ (import do models/index),\n__MODELS_DIR__, __LAYOUTS_DIR__ e __SNAPSHOTS_DIR__ (pastas relativas à raiz do app).\n\nOpções:\n --id <id> id do layout (padrão: o nome do modelo)\n --title <texto> título da página (padrão: o id em palavras)\n --dir <pasta> pasta dos dashboards (padrão: src/dashboard)\n --models <pasta> pasta dos modelos (padrão: <dir>/models)\n --layouts <pasta> pasta dos layouts (padrão: <dir>/layouts)\n --page <arquivo> arquivo da página (padrão: pelo router do Next, ver acima)\n --skip-page não cria a página\n --template <arq.> template da página (padrão: <dir>/page.template, se existir)\n --endpoint <url> endpoint do runQuery (padrão: /api/dashboard/query)\n --api <arquivo> arquivo do endpoint (padrão: pela rota do --endpoint, ver acima)\n --skip-api não cria o endpoint\n --api-template <a.> template do endpoint (padrão: <dir>/api.template, se existir)\n --snapshots <pasta> pasta dos snapshots no endpoint (padrão: tests/dashboard/snapshots)\n -h, --help esta ajuda\n`\n\nconst exists = (path: string) => existsSync(path)\n\n/** Caminho de import de `to` a partir do arquivo `from` (sempre com `./` ou `../` e `/`). */\nfunction importPath(from: string, to: string) {\n const path = relative(dirname(from), to).split(sep).join('/')\n return path.startsWith('.') ? path : `./${path}`\n}\n\n/** Router do Next que existir no app (Pages primeiro, como o `custo`). */\nfunction nextRouter(cwd: string) {\n for (const pages of ['src/pages', 'pages']) {\n if (!exists(join(cwd, pages))) continue\n const config = ['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.cjs']\n .map((file) => join(cwd, file))\n .find(exists)\n return { app: false, dir: join(cwd, pages), config: config && readFileSync(config, 'utf8') }\n }\n for (const app of ['src/app', 'app'])\n if (exists(join(cwd, app))) return { app: true, dir: join(cwd, app), config: undefined }\n return undefined\n}\n\ntype Router = ReturnType<typeof nextRouter>\n\nfunction defaultPagePath(router: Router, slug: string) {\n if (!router) return undefined\n return router.app\n ? join(router.dir, 'dashboard', slug, 'page.tsx')\n : join(router.dir, 'dashboard', slug, `index.${pageExtension(router.config)}`)\n}\n\n/** Endpoint na rota do `--endpoint` (ex.: `/api/dashboard/query`). */\nfunction defaultApiPath(router: Router, endpoint: string) {\n if (!router) return undefined\n if (!/^(\\/[\\w-]+)+$/.test(endpoint))\n throw new ScaffoldError(\n `Endpoint inválido para criar a rota: ${endpoint} (use --api ou --skip-api)`\n )\n const route = endpoint.slice(1).split('/')\n return router.app\n ? join(router.dir, ...route, 'route.ts')\n : join(router.dir, ...route.slice(0, -1), `${route.at(-1)}.${apiExtension(router.config)}`)\n}\n\n/** `export const charts` em <dir>/charts: os tipos de gráfico do app entram no provider. */\nfunction chartsIndex(dir: string) {\n return ['index.ts', 'index.tsx']\n .map((file) => join(dir, 'charts', file))\n .find((path) => exists(path) && /export\\s+const\\s+charts\\b/.test(readFileSync(path, 'utf8')))\n}\n\n/** `export const chartTypes` em <dir>/charts/types.ts: o devtools do endpoint valida o layout. */\nfunction chartTypesFile(dir: string) {\n return ['types.ts', 'types.tsx']\n .map((file) => join(dir, 'charts', file))\n .find(\n (path) => exists(path) && /export\\s+const\\s+chartTypes\\b/.test(readFileSync(path, 'utf8'))\n )\n}\n\n/** Caminho relativo à raiz do app, sempre com `/`. */\nconst fromRoot = (cwd: string, path: string) => relative(cwd, path).split(sep).join('/')\n\nfunction main(argv: string[]) {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n id: { type: 'string' },\n dir: { type: 'string', default: 'src/dashboard' },\n models: { type: 'string' },\n layouts: { type: 'string' },\n title: { type: 'string' },\n page: { type: 'string' },\n 'skip-page': { type: 'boolean' },\n template: { type: 'string' },\n endpoint: { type: 'string', default: '/api/dashboard/query' },\n api: { type: 'string' },\n 'skip-api': { type: 'boolean' },\n 'api-template': { type: 'string' },\n snapshots: { type: 'string', default: 'tests/dashboard/snapshots' },\n help: { type: 'boolean', short: 'h' }\n }\n })\n const [command, name, ...rest] = positionals\n if (values.help || !command) {\n process.stdout.write(HELP)\n return\n }\n if (command !== 'new') throw new ScaffoldError(`Comando desconhecido: ${command}\\n\\n${HELP}`)\n if (!name) throw new ScaffoldError(`Falta o nome do modelo.\\n\\n${HELP}`)\n if (rest.length) throw new ScaffoldError(`Argumentos a mais: ${rest.join(' ')}`)\n\n const id = values.id ?? name\n checkModelName(name)\n checkLayoutId(id)\n\n const cwd = process.cwd()\n const modelsDir = join(cwd, values.models ?? join(values.dir, 'models'))\n const layoutsDir = join(cwd, values.layouts ?? join(values.dir, 'layouts'))\n const modelPath = join(modelsDir, `${name}.ts`)\n const indexPath = join(modelsDir, 'index.ts')\n const layoutPath = join(layoutsDir, `${id}.json`)\n const dashboardDir = join(cwd, values.dir)\n const router = nextRouter(cwd)\n const pagePath = values['skip-page']\n ? undefined\n : values.page\n ? join(cwd, values.page)\n : defaultPagePath(router, kebabCase(id))\n const apiPath = values['skip-api']\n ? undefined\n : values.api\n ? join(cwd, values.api)\n : defaultApiPath(router, values.endpoint)\n // O endpoint é um só para todos os dashboards: se já existe, fica como está\n const apiExists = !!apiPath && exists(apiPath)\n const templatePath = values.template\n ? join(cwd, values.template)\n : [join(dashboardDir, 'page.template')].find(exists)\n const apiTemplatePath = values['api-template']\n ? join(cwd, values['api-template'])\n : [join(dashboardDir, 'api.template')].find(exists)\n for (const path of [templatePath, apiTemplatePath])\n if (path && !exists(path))\n throw new ScaffoldError(`Template não encontrado: ${relative(cwd, path)}`)\n\n // Confere tudo antes de escrever: ou cria o dashboard inteiro, ou nada\n for (const path of [modelPath, layoutPath, pagePath])\n if (path && existsSync(path)) throw new ScaffoldError(`Já existe: ${relative(cwd, path)}`)\n const index = existsSync(indexPath)\n ? registerModel(readFileSync(indexPath, 'utf8'), name)\n : modelIndexCode(name)\n let page: string | undefined\n if (pagePath) {\n const charts = chartsIndex(dashboardDir)\n const options: PageOptions = {\n model: name,\n layoutImport: importPath(pagePath, layoutPath),\n title: values.title ?? titleCase(id),\n endpoint: values.endpoint,\n // App Router: a página usa hooks, então é client component\n clientDirective:\n basename(pagePath).startsWith('page.') &&\n relative(cwd, pagePath).split(sep).includes('app'),\n chartsImport: charts && importPath(pagePath, dirname(charts))\n }\n page = templatePath\n ? fillPageTemplate(readFileSync(templatePath, 'utf8'), options)\n : pageFileCode(options)\n }\n let api: string | undefined\n if (apiPath && !apiExists) {\n const chartTypes = chartTypesFile(dashboardDir)\n const options: ApiOptions = {\n modelsImport: importPath(apiPath, modelsDir),\n modelsDir: fromRoot(cwd, modelsDir),\n layoutsDir: fromRoot(cwd, layoutsDir),\n snapshotsDir: fromRoot(cwd, join(cwd, values.snapshots)),\n chartTypesImport: chartTypes && importPath(apiPath, chartTypes.replace(/\\.tsx?$/, '')),\n appRouter: basename(apiPath).startsWith('route.')\n }\n api = apiTemplatePath\n ? fillApiTemplate(readFileSync(apiTemplatePath, 'utf8'), options)\n : apiFileCode(options)\n }\n\n const written: string[] = []\n const write = (path: string, content: string) => {\n mkdirSync(dirname(path), { recursive: true })\n writeFileSync(path, content)\n written.push(relative(cwd, path))\n }\n write(modelPath, modelFileCode(name))\n if (index !== null) write(indexPath, index)\n write(layoutPath, layoutFileCode(id))\n if (pagePath && page !== undefined) write(pagePath, page)\n if (apiPath && api !== undefined) write(apiPath, api)\n\n process.stdout.write(\n [\n ...written.map((path) => `✓ ${path}`),\n ...(pagePath\n ? templatePath\n ? [` (página do template ${relative(cwd, templatePath)})`]\n : []\n : values['skip-page']\n ? []\n : [\n '',\n 'Página não criada: não achei src/pages nem src/app. Use --page <arquivo>, com o layout:',\n ` <DashboardProvider model=\"${name}\" fetcher={fetcher}>`,\n ' <DashboardFilters layout={layout} />',\n ' <DashboardCharts layout={layout} />',\n ' </DashboardProvider>'\n ]),\n ...(apiPath\n ? apiExists\n ? [`· ${relative(cwd, apiPath)} (endpoint já existe, mantido)`]\n : apiTemplatePath\n ? [` (endpoint do template ${relative(cwd, apiTemplatePath)})`]\n : []\n : values['skip-api']\n ? []\n : ['', 'Endpoint não criado: não achei src/pages nem src/app. Use --api <arquivo>.']),\n ''\n ].join('\\n')\n )\n}\n\ntry {\n main(process.argv.slice(2))\n} catch (error) {\n if (!(error instanceof ScaffoldError) && !(error instanceof TypeError)) throw error\n process.stderr.write(`${error.message}\\n`)\n process.exitCode = 1\n}\n"],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as ModelMeta, A as ChartTypeSpec, D as ChartDef, I as DonutLegendPosition, L as DonutValueContent, M as ChartsLayout, O as ChartNumberFormat, Q as ModelDef, S as BarValuePosition, T as CardStyle, c as DashboardFilters$1, d as DashboardResult, f as DashboardRow, h as FilterValue, i as ValueRule, j as ChartTypes, l as DashboardQuery, n as ColumnVerticalAlign, r as FormatRule, t as ColumnAlign, u as DashboardQueryDebug } from "../formatRules-DTKbov47.js";
|
|
2
2
|
import { ComponentType, MouseEvent, ReactNode } from "react";
|
|
3
3
|
//#region src/client/BarChart.d.ts
|
|
4
4
|
type BarChartProps = {
|
|
@@ -26,8 +26,36 @@ type BarChartProps = {
|
|
|
26
26
|
formatDimension?: (value: FilterValue) => string;
|
|
27
27
|
/** Esconde categorias sem valor em nenhuma medida. */
|
|
28
28
|
hideEmpty?: boolean;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Mostra todos os valores da dimensão no eixo, com ou sem dado nas medidas (zerados), na ordem
|
|
31
|
+
* da dimensão. Sobrepõe o `hideEmpty`.
|
|
32
|
+
*/
|
|
33
|
+
showEmpty?: boolean;
|
|
34
|
+
/** Valores nas barras (até 30 categorias; acima disso, só tooltip). */
|
|
30
35
|
showValues?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Onde fica o valor: `outside` (na ponta, fora), `insideEnd` (dentro, na ponta), `center` ou
|
|
38
|
+
* `insideBase`. Empilhado, `outside` não mostra os pedaços (só o `showTotal`).
|
|
39
|
+
*/
|
|
40
|
+
valuePosition?: BarValuePosition;
|
|
41
|
+
/** Cor do valor. Padrão: escuro fora da barra, branco dentro. */
|
|
42
|
+
valueColor?: string;
|
|
43
|
+
/** Tamanho do valor, em px. Padrão: 12. */
|
|
44
|
+
valueSize?: number;
|
|
45
|
+
/** Empilhado: total da pilha na ponta da barra. */
|
|
46
|
+
showTotal?: boolean;
|
|
47
|
+
/** Só mostra o valor (e o total) que atende à regra, ex.: `{ operator: '!=', value: 0 }`. */
|
|
48
|
+
valueRule?: ValueRule;
|
|
49
|
+
/**
|
|
50
|
+
* Com `clickToFilter`: o filtro da dimensão (e da série) esconde o resto em vez de só apagá-lo
|
|
51
|
+
* (a consulta passa a respeitar o próprio filtro). Padrão: `false` (apaga).
|
|
52
|
+
*/
|
|
53
|
+
hideUnselected?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também
|
|
56
|
+
* "(em branco)" e "Outros"). O que faltar segue a paleta.
|
|
57
|
+
*/
|
|
58
|
+
colors?: Record<string, string>;
|
|
31
59
|
clickToFilter?: boolean;
|
|
32
60
|
height?: number;
|
|
33
61
|
/** Rótulo por dimensão/medida (sobrepõe o dicionário do modelo). */
|
|
@@ -43,7 +71,7 @@ type BarChartProps = {
|
|
|
43
71
|
* Com `series`, uma medida quebrada pelos valores de outra dimensão:
|
|
44
72
|
* `<BarChart dimension="mes" series="tipo_descarga" measures={['volume']} stacked />`.
|
|
45
73
|
*/
|
|
46
|
-
declare function BarChart({ dimension, measures: measureList, series, formatSeries, maxSeries, orientation, stacked, sort, limit, formatDimension, hideEmpty, showValues, clickToFilter, height, labels, formats: formatsByMeasure, queryLabel }: BarChartProps): import("react").JSX.Element;
|
|
74
|
+
declare function BarChart({ dimension, measures: measureList, series, formatSeries, maxSeries, orientation, stacked, sort, limit, formatDimension, hideEmpty, showEmpty, showValues, valuePosition, valueColor, valueSize, showTotal, valueRule, hideUnselected, colors, clickToFilter, height, labels, formats: formatsByMeasure, queryLabel }: BarChartProps): import("react").JSX.Element;
|
|
47
75
|
//#endregion
|
|
48
76
|
//#region src/client/ComboChart.d.ts
|
|
49
77
|
type ComboChartProps = {
|
|
@@ -178,9 +206,30 @@ type DashboardChartsProps = {
|
|
|
178
206
|
formatters?: Record<string, (value: FilterValue) => string>;
|
|
179
207
|
/** Moldura de cada gráfico (título + conteúdo). */
|
|
180
208
|
panel?: ComponentType<ChartPanelProps>;
|
|
181
|
-
/** Espaço entre os cards (unidades do `theme.spacing`);
|
|
209
|
+
/** Espaço entre os cards (unidades do `theme.spacing`); `cards.rowGap`/`columnGap` do layout (px) têm prioridade. */
|
|
182
210
|
gap?: number;
|
|
183
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* `sx` da moldura de um card com o estilo do layout (`card` do `panel`). Só traz o que o layout
|
|
214
|
+
* definiu, para um `panel` do app espalhar por cima do visual dele:
|
|
215
|
+
* `<Paper sx={{ p: 2, borderRadius: 0, ...chartCardSx(card) }}>`.
|
|
216
|
+
*/
|
|
217
|
+
declare function chartCardSx(card: CardStyle): {
|
|
218
|
+
boxSizing: 'border-box';
|
|
219
|
+
p?: string | undefined;
|
|
220
|
+
bgcolor?: string | undefined;
|
|
221
|
+
borderWidth?: string | undefined;
|
|
222
|
+
borderStyle?: string | undefined;
|
|
223
|
+
borderColor?: string | undefined;
|
|
224
|
+
borderRadius?: string | undefined;
|
|
225
|
+
boxShadow?: number | undefined;
|
|
226
|
+
};
|
|
227
|
+
/** `sx` do título de um card com o estilo do layout (idem `chartCardSx`). */
|
|
228
|
+
declare function chartTitleSx(card: CardStyle): Partial<{
|
|
229
|
+
color: string | undefined;
|
|
230
|
+
fontSize: string | undefined;
|
|
231
|
+
textAlign: "center" | "left" | "right" | undefined;
|
|
232
|
+
}>;
|
|
184
233
|
/**
|
|
185
234
|
* Renderiza os gráficos de um layout declarativo num grid de 12 colunas (no celular, um por linha;
|
|
186
235
|
* dentro de um `<DashboardCanvas>` de tamanho fixo, sempre as 12). Em desenvolvimento, a aba
|
|
@@ -453,14 +502,23 @@ type DonutChartProps = {
|
|
|
453
502
|
formats?: Record<string, ChartNumberFormat>;
|
|
454
503
|
/** Casas decimais do percentual. Padrão: 1. */
|
|
455
504
|
percentDecimals?: number;
|
|
505
|
+
/** Lado da legenda. Padrão: `right` (nome, % e valor; em cima/embaixo, só o nome, em linha). */
|
|
506
|
+
legendPosition?: DonutLegendPosition;
|
|
507
|
+
/** Valor de cada fatia do lado de fora da rosca. */
|
|
508
|
+
showValues?: boolean;
|
|
509
|
+
/** O que o valor de fora mostra. Padrão: `both` (`44.981 (51%)`). */
|
|
510
|
+
valueContent?: DonutValueContent;
|
|
511
|
+
/** Total no furo da rosca. Padrão: `true`. */
|
|
512
|
+
showTotal?: boolean;
|
|
456
513
|
/** Nome da consulta no devtools. */
|
|
457
514
|
queryLabel?: string;
|
|
458
515
|
};
|
|
459
516
|
/**
|
|
460
|
-
* Rosca de uma medida por uma dimensão, com o total no centro e a legenda ao lado (nome, % e valor)
|
|
517
|
+
* Rosca de uma medida por uma dimensão, com o total no centro e a legenda ao lado (nome, % e valor)
|
|
518
|
+
* ou em cima/embaixo; `showValues` põe o valor de cada fatia do lado de fora.
|
|
461
519
|
* Ex.: `<DonutChart dimension="operacao_status" measure="custoTotal" />`.
|
|
462
520
|
*/
|
|
463
|
-
declare function DonutChart({ dimension, measure, maxSlices, formatDimension, clickToFilter, height, labels, formats, percentDecimals, queryLabel }: DonutChartProps): import("react").JSX.Element;
|
|
521
|
+
declare function DonutChart({ dimension, measure, maxSlices, formatDimension, clickToFilter, height, labels, formats, percentDecimals, legendPosition, showValues, valueContent, showTotal, queryLabel }: DonutChartProps): import("react").JSX.Element;
|
|
464
522
|
//#endregion
|
|
465
523
|
//#region src/client/FilterSelect.d.ts
|
|
466
524
|
type FilterSelectProps = {
|
|
@@ -554,5 +612,5 @@ declare function useModelQuery({ dimensions, measures, ignoreFilters, label, ski
|
|
|
554
612
|
};
|
|
555
613
|
};
|
|
556
614
|
//#endregion
|
|
557
|
-
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, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
615
|
+
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, chartCardSx, chartPlaceholder, chartTitleSx, fieldLabel, formatCurrency, formatMeasure, formatNumber, measureFormat, measureFormatter, numberOf, seriesColor, useClickFilter, useDashboard, useModelQuery };
|
|
558
616
|
//# sourceMappingURL=index.d.ts.map
|