@bluemarble/bi 0.6.3 → 0.6.5

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codegen-LoQzFAle.js","names":[],"sources":["../src/star.ts","../src/model.ts","../src/codegen.ts"],"sourcesContent":["/**\n * Modelo estrela (`tables` + `relationships`): tabelas fato e dimensão ligadas por relacionamentos\n * N:1, como no modelo do Power BI. Aqui ficam as regras do grafo — quem alcança quem e por qual\n * caminho —, usadas pela validação, pelo compilador e pelo diagrama do devtools.\n *\n * O filtro \"desce\" do lado 1 para o lado N: uma medida da fato `fPesagem` enxerga a dimensão\n * `dOperacao` se existe um caminho `fPesagem -> ... -> dOperacao` seguindo os relacionamentos no\n * sentido N -> 1 (snowflake vale: `fPesagem -> dOperacao -> dNavio`).\n */\n\nimport type { ModelDef, RelationshipDef } from './model'\n\nexport type ColumnRef = { table: string; column: string }\n\nexport type Edge = { from: ColumnRef; to: ColumnRef; index: number; inner: boolean }\n\n/** `'tabela.coluna'` -> `{ table, column }` (nulo se não tiver esse formato). */\nexport function parseColumnRef(ref: unknown): ColumnRef | null {\n if (typeof ref !== 'string') return null\n const match = /^([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(ref.trim())\n return match ? { table: match[1], column: match[2] } : null\n}\n\n/** Relacionamentos válidos do modelo, como arestas N -> 1. */\nexport function modelEdges(model: Pick<ModelDef, 'relationships'>): Edge[] {\n return (model.relationships ?? []).flatMap((relationship: RelationshipDef, index) => {\n const from = parseColumnRef(relationship?.from)\n const to = parseColumnRef(relationship?.to)\n return from && to ? [{ from, to, index, inner: relationship.inner === true }] : []\n })\n}\n\nconst escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Tabelas citadas no SQL como `tabela.coluna`. O que está entre aspas simples é ignorado, para\n * `'a.b'` num literal não contar como referência.\n */\nexport function referencedTables(sql: string, tables: string[]): string[] {\n const code = sql.replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n return tables.filter((name) => new RegExp(`(?<![\\\\w.\\`])${escapeRegex(name)}\\\\.`).test(code))\n}\n\n/** Passo de um caminho: entra em `table` pela aresta `edge` (de `edge.from.table`). */\nexport type PathStep = { table: string; edge: Edge }\n\nexport type Reach = {\n /** Tabela -> caminho mais curto a partir da origem (vazio para a própria origem). */\n paths: Map<string, PathStep[]>\n /**\n * Tabelas alcançadas por mais de um caminho (de qualquer tamanho): o filtro ficaria ambíguo. É a\n * mesma regra do Power BI, que só deixa um caminho ativo entre duas tabelas.\n */\n ambiguous: string[]\n}\n\n/** Busca em largura a partir de `start`, seguindo os relacionamentos no sentido N -> 1. */\nexport function reachFrom(start: string, edges: Edge[]): Reach {\n const paths = new Map<string, PathStep[]>([[start, []]])\n let frontier = [start]\n while (frontier.length) {\n const next: string[] = []\n for (const table of frontier) {\n for (const edge of edges) {\n if (edge.from.table !== table || paths.has(edge.to.table)) continue\n paths.set(edge.to.table, [...(paths.get(table) ?? []), { table: edge.to.table, edge }])\n next.push(edge.to.table)\n }\n }\n frontier = next\n }\n // Cada aresta saindo de uma tabela alcançada é um caminho até o destino dela\n const incoming = new Map<string, number>()\n for (const edge of edges)\n if (paths.has(edge.from.table))\n incoming.set(edge.to.table, (incoming.get(edge.to.table) ?? 0) + 1)\n const ambiguous = [...incoming]\n .filter(([table, count]) => table !== start && count > 1)\n .map(([table]) => table)\n return { paths, ambiguous }\n}\n\n/**\n * Tabelas que precisam entrar na consulta, na ordem do JOIN, para que a partir de `home` se chegue\n * a todas as `targets`. Como cada tabela é alcançada por um caminho só (o modelo valida ambiguidade),\n * a união dos caminhos é uma árvore: cada tabela entra uma vez, ligada pela aresta de quem veio antes.\n */\nexport function joinPlan(home: string, targets: Iterable<string>, edges: Edge[]): PathStep[] {\n const { paths } = reachFrom(home, edges)\n const plan: PathStep[] = []\n const added = new Set([home])\n const include = (target: string) => {\n for (const step of paths.get(target) ?? []) {\n if (added.has(step.table)) continue\n added.add(step.table)\n plan.push(step)\n }\n }\n for (const target of targets) include(target)\n // Relacionamento `inner` entra sempre que o lado N está na consulta: ele filtra mesmo sem\n // dimensão pedida (repete até não entrar mais nada, porque cada tabela nova pode ter os seus)\n for (let grew = true; grew;) {\n grew = false\n for (const edge of edges)\n if (edge.inner && added.has(edge.from.table) && !added.has(edge.to.table)) {\n include(edge.to.table)\n grew = true\n }\n }\n // Pai antes do filho: ordena pela profundidade do caminho\n const depth = (table: string) => paths.get(table)?.length ?? 0\n return plan.sort((a, b) => depth(a.table) - depth(b.table))\n}\n\n/**\n * Tabela de origem de uma medida `sum`: entre as tabelas que o SQL cita, a que alcança todas as\n * outras (ex.: `fPesagem.peso * dProduto.fator` agrega em `fPesagem`). Nulo se nenhuma alcança.\n */\nexport function measureHome(tables: string[], edges: Edge[]): string | null {\n if (!tables.length) return null\n return (\n tables.find((candidate) => {\n const { paths } = reachFrom(candidate, edges)\n return tables.every((table) => paths.has(table))\n }) ?? null\n )\n}\n","import { measureHome, modelEdges, parseColumnRef, reachFrom, referencedTables } from './star'\n\n/**\n * Tipos e validação do modelo semântico. Fica fora de `server/` porque o devtools (client) também\n * valida o rascunho editado antes de mandá-lo pro servidor.\n */\n\n/**\n * Dicionário: nome de negócio e explicação de cada campo. Os componentes usam como padrão (rótulo,\n * formatação) e o `ModelDictionary` vira a documentação do modelo para quem usa o dashboard.\n */\nexport type FieldMeta = {\n label?: string\n description?: string\n}\n\nexport const VALUE_FORMATS = ['number', 'integer', 'currency', 'percent'] as const\nexport type ValueFormat = (typeof VALUE_FORMATS)[number]\n\nexport type MeasureMeta = FieldMeta & {\n /** `percent` espera a fração (0.25 = 25%). */\n format?: ValueFormat\n /** Casas decimais; sem isso, vale o padrão do componente. */\n decimals?: number\n}\n\nexport type DimensionDef = FieldMeta & {\n sql: string\n type: 'number' | 'string'\n}\n\n/**\n * Agregações sobre uma expressão SQL (`tabela.coluna`). Todas valem nos dois formatos; no estrela,\n * agregam no grão da tabela de origem, como a soma.\n */\nexport const AGGREGATIONS = ['sum', 'count', 'countDistinct', 'avg', 'min', 'max'] as const\nexport type Aggregation = (typeof AGGREGATIONS)[number]\n\nexport const AGGREGATION_LABEL: Record<Aggregation, string> = {\n sum: 'Soma',\n count: 'Contagem',\n countDistinct: 'Contagem distinta',\n avg: 'Média',\n min: 'Mínimo',\n max: 'Máximo'\n}\n\n/** `COUNT` conta os valores não nulos; para contar linhas, use uma coluna que nunca é nula. */\nexport type AggregateMeasure = MeasureMeta & { type: Aggregation; sql: string }\nexport type SumMeasure = AggregateMeasure & { type: 'sum' }\nexport type RatioMeasure = MeasureMeta & { type: 'ratio'; numerator: string; denominator: string }\n/**\n * Combinação de outras medidas, já agregadas: `{nome}` vira o valor da medida (como `[Medida]` no\n * DAX). É o jeito de juntar medidas de fatos diferentes: `COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)`.\n */\nexport type FormulaMeasure = MeasureMeta & { type: 'formula'; expression: string }\nexport type MeasureDef = AggregateMeasure | RatioMeasure | FormulaMeasure\n\nexport const isAggregate = (measure: { type: unknown }): measure is AggregateMeasure =>\n AGGREGATIONS.includes(measure.type as Aggregation)\n\n/** O SQL agregado da medida: `SUM(x)`, `COUNT(DISTINCT x)`, `AVG(x)`... */\nexport function aggregateSql(measure: Pick<AggregateMeasure, 'type' | 'sql'>): string {\n if (measure.type === 'countDistinct') return `COUNT(DISTINCT ${measure.sql})`\n const fn = { sum: 'SUM', count: 'COUNT', avg: 'AVG', min: 'MIN', max: 'MAX' }[measure.type]\n return `${fn}(${measure.sql})`\n}\n\n/**\n * Tabela do modelo: o SQL é a \"consulta de origem\" (tabela, view ou SELECT). Não há tipo fato ou\n * dimensão: o papel sai dos relacionamentos (quem está no lado 1 filtra quem está no lado N) e de\n * onde ficam as medidas.\n */\nexport type TableDef = FieldMeta & {\n sql: string\n /** Coluna única da tabela; o diagrama usa ela para decidir o lado 1 ao criar relacionamentos. */\n key?: string\n}\n\n/** Relacionamento N:1 — o filtro do lado 1 (`to`) chega às linhas do lado N (`from`). */\nexport type RelationshipDef = {\n /** `tabela.coluna` do lado N (os lançamentos). */\n from: string\n /** `tabela.coluna` do lado 1 (a chave do cadastro). */\n to: string\n /**\n * \"Pressupor integridade referencial\" do Power BI: INNER JOIN em vez de LEFT JOIN, e a tabela do\n * lado 1 entra sempre na consulta da fato. Linhas do lado N sem par no lado 1 ficam de fora (em\n * vez de virar um grupo \"em branco\") — é o jeito de um filtro fixo da dimensão valer para a fato.\n */\n inner?: boolean\n}\n\n/**\n * Modelo estrela: `tables` (fato/dimensão) + `relationships`. Dimensões e medidas citam as colunas\n * como `tabela.coluna`; cada medida de agregação agrega no grão da própria tabela e chega às\n * dimensões seguindo os relacionamentos.\n */\nexport type ModelDef = {\n name: string\n tables: Record<string, TableDef>\n relationships?: RelationshipDef[]\n dimensions: Record<string, DimensionDef>\n measures: Record<string, MeasureDef>\n /**\n * Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo) e, se foi\n * redimensionado, o tamanho. Só visual: não muda nenhuma consulta. Card sem posição entra no\n * layout automático; sem tamanho, tem a largura padrão e a altura do conteúdo.\n */\n diagram?: Record<string, DiagramPosition>\n}\n\nexport type DiagramPosition = { x: number; y: number; width?: number; height?: number }\n\n/** Nomes citados como `{nome}` numa fórmula. */\nexport const formulaRefs = (expression: string) => [\n ...new Set([...expression.matchAll(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g)].map((match) => match[1]))\n]\n\n/** Medidas que uma medida usa diretamente (razão e fórmula). */\nexport function measureDeps(measure: MeasureDef): string[] {\n if (measure.type === 'ratio') return [measure.numerator, measure.denominator]\n if (measure.type === 'formula') return formulaRefs(measure.expression)\n return []\n}\n\nconst IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isText = (value: unknown): value is string => typeof value === 'string' && !!value.trim()\n\nfunction metaErrors(context: string, field: Record<string, unknown>): string[] {\n const errors: string[] = []\n for (const key of ['label', 'description'] as const) {\n if (field[key] !== undefined && typeof field[key] !== 'string')\n errors.push(`${context}: \\`${key}\\` precisa ser texto`)\n }\n if (field.format !== undefined && !VALUE_FORMATS.includes(field.format as ValueFormat))\n errors.push(`${context}: formato inválido \"${field.format}\"`)\n const { decimals } = field\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n errors.push(`${context}: \\`decimals\\` precisa ser um inteiro entre 0 e 10`)\n return errors\n}\n\n/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */\nexport type ModelMeta = {\n name: string\n /** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */\n dimensions: Record<string, FieldMeta & { type: DimensionDef['type']; table?: string }>\n measures: Record<\n string,\n MeasureMeta & {\n type: MeasureDef['type']\n numerator?: string\n denominator?: string\n /** Medidas que a fórmula combina (a expressão em si fica no servidor). */\n refs?: string[]\n }\n >\n}\n\nexport function modelMeta(model: ModelDef): ModelMeta {\n const tableNames = Object.keys(model.tables)\n const pick = <T extends FieldMeta & MeasureMeta>(field: T) => ({\n ...(field.label !== undefined && { label: field.label }),\n ...(field.description !== undefined && { description: field.description }),\n ...(field.format !== undefined && { format: field.format }),\n ...(field.decimals !== undefined && { decimals: field.decimals })\n })\n return {\n name: model.name,\n dimensions: Object.fromEntries(\n Object.entries(model.dimensions).map(([name, dim]) => [\n name,\n { type: dim.type, table: referencedTables(dim.sql, tableNames)[0], ...pick(dim) }\n ])\n ),\n measures: Object.fromEntries(\n Object.entries(model.measures).map(([name, measure]) => [\n name,\n {\n type: measure.type,\n // Contagem sem formato: inteiro (sem casas decimais)\n ...((measure.type === 'count' || measure.type === 'countDistinct') && {\n format: 'integer' as const\n }),\n ...pick(measure),\n ...(measure.type === 'ratio' && {\n numerator: measure.numerator,\n denominator: measure.denominator\n }),\n ...(measure.type === 'formula' && { refs: formulaRefs(measure.expression) })\n }\n ])\n )\n }\n}\n\n/**\n * Lista os problemas do modelo (vazio = válido). Confere também a forma do objeto, porque o rascunho\n * do devtools chega como JSON da request.\n */\nexport function validateModel(model: ModelDef): string[] {\n if (!isRecord(model)) return ['modelo inválido']\n if (!isRecord(model.dimensions) || !isRecord(model.measures))\n return ['`dimensions` e `measures` são obrigatórios']\n // Rascunho/arquivo antigo: o formato base + joins foi removido\n if ('base' in model || 'joins' in model)\n return ['o formato `base` + `joins` não existe mais: use `tables` + `relationships`']\n if (!isRecord(model.tables)) return ['`tables` é obrigatório']\n if (model.relationships !== undefined && !Array.isArray(model.relationships))\n return ['`relationships` precisa ser uma lista']\n\n if (model.diagram !== undefined && !diagramValid(model.diagram))\n return ['`diagram` precisa ser { nome: { x, y, width?, height? } } com números (tamanho > 0)']\n\n const errors: string[] = []\n const { tables } = model\n const names = [...Object.keys(model.dimensions), ...Object.keys(model.measures)]\n\n for (const name of [...names, ...Object.keys(tables)]) {\n if (!IDENTIFIER.test(name)) errors.push(`nome inválido: \"${name}\"`)\n else if (name.startsWith('__')) errors.push(`nome reservado: \"${name}\" (começa com \"__\")`)\n }\n\n const duplicated = names.find((name, index) => names.indexOf(name) !== index)\n if (duplicated) errors.push(`nome repetido: \"${duplicated}\"`)\n\n for (const [name, table] of Object.entries(tables)) {\n if (!isRecord(table) || !isText(table.sql)) {\n errors.push(`tabela \"${name}\" sem SQL`)\n continue\n }\n if (table.key !== undefined && (typeof table.key !== 'string' || !IDENTIFIER.test(table.key)))\n errors.push(`tabela \"${name}\": key inválida`)\n errors.push(...metaErrors(`tabela \"${name}\"`, table))\n }\n\n const tableNames = Object.keys(tables)\n errors.push(...relationshipErrors(model, tableNames))\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n if (!isRecord(dimension) || !isText(dimension.sql)) errors.push(`dimensão \"${name}\" sem SQL`)\n else if (dimension.type !== 'number' && dimension.type !== 'string')\n errors.push(`dimensão \"${name}\" com tipo inválido`)\n else {\n errors.push(...metaErrors(`dimensão \"${name}\"`, dimension))\n if (referencedTables(dimension.sql, tableNames).length !== 1)\n errors.push(`dimensão \"${name}\" precisa usar colunas de uma tabela só (\\`tabela.coluna\\`)`)\n }\n }\n\n const edges = modelEdges(model)\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isRecord(measure)) {\n errors.push(`medida \"${name}\" inválida`)\n } else if (isAggregate(measure)) {\n if (!isText(measure.sql)) errors.push(`medida \"${name}\" sem SQL`)\n else {\n const used = referencedTables(measure.sql, tableNames)\n if (!used.length)\n errors.push(`medida \"${name}\" precisa usar colunas de uma tabela (\\`tabela.coluna\\`)`)\n else if (!measureHome(used, edges))\n errors.push(\n `medida \"${name}\" usa ${used.join(', ')}, mas nenhuma delas chega às outras pelos relacionamentos`\n )\n }\n } else if (measure.type === 'ratio' || measure.type === 'formula') {\n if (measure.type === 'formula' && !isText(measure.expression))\n errors.push(`medida \"${name}\" sem expressão`)\n else if (measure.type === 'formula' && !formulaRefs(measure.expression).length)\n errors.push(`medida \"${name}\": a fórmula precisa citar medidas como {nome}`)\n for (const ref of measureDeps(measure)) {\n if (typeof ref !== 'string' || !model.measures[ref])\n errors.push(`medida \"${name}\" referencia \"${ref}\", que não existe`)\n }\n } else {\n errors.push(`medida \"${name}\" com tipo inválido`)\n }\n if (isRecord(measure)) errors.push(...metaErrors(`medida \"${name}\"`, measure))\n }\n if (errors.length) return errors\n\n // Medidas circulares (a -> b -> a) só estourariam na hora de compilar a consulta\n const visit = (name: string, stack: string[]): string | null => {\n if (stack.includes(name)) return [...stack, name].join(' -> ')\n for (const dep of measureDeps(model.measures[name])) {\n const cycle = visit(dep, [...stack, name])\n if (cycle) return cycle\n }\n return null\n }\n for (const name of Object.keys(model.measures)) {\n const cycle = visit(name, [])\n if (cycle) {\n errors.push(`medida circular: ${cycle}`)\n break\n }\n }\n\n return errors\n}\n\nconst diagramValid = (diagram: unknown) =>\n isRecord(diagram) &&\n Object.values(diagram).every(\n (point) =>\n isRecord(point) &&\n Number.isFinite(point.x) &&\n Number.isFinite(point.y) &&\n [point.width, point.height].every(\n (size) => size === undefined || (Number.isFinite(size) && (size as number) > 0)\n )\n )\n\nfunction relationshipErrors(model: ModelDef, tableNames: string[]): string[] {\n const errors: string[] = []\n const seen = new Set<string>()\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const context = `relacionamento ${index + 1}`\n if (!isRecord(relationship)) {\n errors.push(`${context} inválido`)\n continue\n }\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) {\n errors.push(`${context}: use \\`tabela.coluna\\` em \\`from\\` e \\`to\\``)\n continue\n }\n for (const ref of [from, to])\n if (!tableNames.includes(ref.table))\n errors.push(`${context}: tabela \"${ref.table}\" não existe`)\n if (from.table === to.table)\n errors.push(`${context}: liga a tabela \"${from.table}\" a ela mesma`)\n if (relationship.inner !== undefined && typeof relationship.inner !== 'boolean')\n errors.push(`${context}: \\`inner\\` precisa ser true/false`)\n const pair = `${from.table}->${to.table}`\n if (seen.has(pair))\n errors.push(`${context}: já existe um relacionamento de \"${from.table}\" para \"${to.table}\"`)\n seen.add(pair)\n }\n if (errors.length) return errors\n\n // Dois caminhos até a mesma tabela deixariam o filtro ambíguo (o Power BI deixa um inativo)\n const edges = modelEdges(model)\n for (const table of tableNames) {\n const { ambiguous, paths } = reachFrom(table, edges)\n if (ambiguous.length) {\n errors.push(\n `caminho ambíguo de \"${table}\" até \"${ambiguous[0]}\": há mais de um relacionamento levando lá`\n )\n break\n }\n // Ciclo: a tabela alcança a si mesma\n if (edges.some((edge) => edge.to.table === table && paths.has(edge.from.table))) {\n errors.push(`relacionamentos em ciclo passando por \"${table}\"`)\n break\n }\n }\n return errors\n}\n","import {\n type DiagramPosition,\n type FieldMeta,\n isAggregate,\n type MeasureDef,\n type MeasureMeta,\n type ModelDef,\n type TableDef\n} from './model'\n\n/**\n * Gera o código TS de um modelo (`defineModel({...})`). Usado pelo \"Copiar TS\" do devtools e pelo\n * \"Salvar no arquivo\" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.\n */\n\nconst diagramCode = ({ x, y, width, height }: DiagramPosition) =>\n `{ x: ${x}, y: ${y}${width === undefined ? '' : `, width: ${width}`}${height === undefined ? '' : `, height: ${height}`} }`\n\n/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_, item) =>\n item && typeof item === 'object' && !Array.isArray(item)\n ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : item\n )\n}\n\nexport function literal(text: string) {\n if (text.includes('\\n'))\n return `\\`${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('`', '\\\\`').replaceAll('${', '\\\\${')}\\``\n return `'${text.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}'`\n}\n\nfunction metaEntries(field: FieldMeta & MeasureMeta) {\n const entries: string[] = []\n if (field.label !== undefined) entries.push(`label: ${literal(field.label)}`)\n if (field.description !== undefined) entries.push(`description: ${literal(field.description)}`)\n if (field.format !== undefined) entries.push(`format: '${field.format}'`)\n if (field.decimals !== undefined) entries.push(`decimals: ${field.decimals}`)\n return entries\n}\n\nfunction measureCode(measure: MeasureDef) {\n const meta = metaEntries(measure)\n const args = isAggregate(measure)\n ? [literal(measure.sql)]\n : measure.type === 'formula'\n ? [literal(measure.expression)]\n : [literal(measure.numerator), literal(measure.denominator)]\n if (meta.length) args.push(`{ ${meta.join(', ')} }`)\n return `${measure.type}(${args.join(', ')})`\n}\n\nfunction tableCode(table: TableDef) {\n const fields = [\n ...(table.key !== undefined ? [`key: ${literal(table.key)}`] : []),\n `sql: ${literal(table.sql)}`,\n ...metaEntries(table)\n ]\n return `{ ${fields.join(', ')} }`\n}\n\nconst entries = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.entries(record)\n .map(([name, value]) => ` ${name}: ${render(value)}`)\n .join(',\\n')\n\n/** `{ ... }` com uma entrada por linha, ou `{}` quando vazio (modelo recém-criado). */\nconst block = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.keys(record).length ? `{\\n${entries(record, render)}\\n }` : '{}'\n\n/** O objeto passado ao `defineModel`, começando em `{` (sem indentação na primeira linha). */\nexport function modelObjectCode(model: ModelDef): string {\n const lines = ['{', ` name: ${literal(model.name)},`]\n lines.push(` tables: ${block(model.tables, tableCode)},`)\n if (model.relationships?.length)\n lines.push(\n ` relationships: [\\n${model.relationships\n .map(\n (relationship) =>\n ` { from: ${literal(relationship.from)}, to: ${literal(relationship.to)}${relationship.inner ? ', inner: true' : ''} }`\n )\n .join(',\\n')}\\n ],`\n )\n lines.push(\n ` dimensions: ${block(model.dimensions, (dim) =>\n [`{ sql: ${literal(dim.sql)}`, `type: '${dim.type}'`, ...metaEntries(dim)]\n .join(', ')\n .concat(' }')\n )},`,\n ` measures: ${block(model.measures, measureCode)}${model.diagram ? ',' : ''}`\n )\n if (model.diagram) lines.push(` diagram: {\\n${entries(model.diagram, diagramCode)}\\n }`)\n lines.push('}')\n return lines.join('\\n')\n}\n\n/** Helpers do `@bluemarble/bi/server` que o código do modelo usa. */\nexport function modelImports(model: ModelDef) {\n // O helper tem o nome do tipo (`sum(...)`, `countDistinct(...)`, `formula(...)`)\n const types = new Set(Object.values(model.measures).map((measure) => measure.type))\n return ['defineModel', ...types].sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }))\n}\n\n/** Arquivo completo, pronto para colar em `src/dashboard/models/<nome>.ts`. */\nexport function toModelCode(model: ModelDef): string {\n return [\n `import { ${modelImports(model).join(', ')} } from '@bluemarble/bi/server'`,\n '',\n `export const ${model.name} = defineModel(${modelObjectCode(model)})`,\n ''\n ].join('\\n')\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { c as VALUE_FORMATS, u as isAggregate } from "./codegen-DkfI6UJr.js";
1
+ import { c as VALUE_FORMATS, u as isAggregate } from "./codegen-LoQzFAle.js";
2
2
  //#region src/formatRules.ts
3
3
  const COLUMN_ALIGNS = [
4
4
  "left",
@@ -168,6 +168,12 @@ const CHART_TYPE_LABEL = {
168
168
  donut: "Rosca",
169
169
  table: "Tabela"
170
170
  };
171
+ const BAR_VALUE_POSITIONS = [
172
+ "outside",
173
+ "insideEnd",
174
+ "center",
175
+ "insideBase"
176
+ ];
171
177
  const isBuiltinChart = (chart) => CHART_TYPES.includes(chart.type);
172
178
  const LAYOUT_ID = /^[A-Za-z0-9_-]{1,80}$/;
173
179
  const common = (height) => ({
@@ -324,11 +330,35 @@ const CHART_FIELDS = {
324
330
  label: "Dimensão (categorias)",
325
331
  section: "Dados"
326
332
  },
327
- formatDimension,
333
+ formatDimension: {
334
+ ...formatDimension,
335
+ of: "dimension"
336
+ },
328
337
  measures: {
329
338
  kind: "measures",
330
339
  required: true,
331
340
  label: "Medidas (uma série cada)",
341
+ hint: "Com a série (legenda), só a primeira medida é usada",
342
+ section: "Dados"
343
+ },
344
+ series: {
345
+ kind: "dimension",
346
+ label: "Série (legenda)",
347
+ hint: "Quebra a medida pelos valores desta dimensão: uma série por valor, com cor fixa por valor (ex.: tipo de descarga)",
348
+ section: "Dados"
349
+ },
350
+ formatSeries: {
351
+ ...formatDimension,
352
+ label: "Formatar série",
353
+ of: "series"
354
+ },
355
+ maxSeries: {
356
+ kind: "integer",
357
+ min: 2,
358
+ max: 8,
359
+ default: 6,
360
+ label: "Máx. séries",
361
+ hint: "Com a série: as menores além dessa quantidade viram \"Outros\"",
332
362
  section: "Dados"
333
363
  },
334
364
  sort: {
@@ -345,7 +375,7 @@ const CHART_FIELDS = {
345
375
  },
346
376
  default: "dimension",
347
377
  label: "Ordem",
348
- hint: "Maior/menor primeiro usa a primeira medida",
378
+ hint: "Maior/menor primeiro usa a primeira medida (com a série, o total da barra)",
349
379
  section: "Dados"
350
380
  },
351
381
  limit: {
@@ -358,6 +388,13 @@ const CHART_FIELDS = {
358
388
  },
359
389
  clickToFilter,
360
390
  hideEmpty,
391
+ showEmpty: {
392
+ kind: "boolean",
393
+ default: false,
394
+ label: "Mostrar categorias sem dados",
395
+ hint: "Todos os valores da dimensão no eixo, zerados quando não há dado (ex.: todos os meses). Sobrepõe \"Esconder grupos vazios\"",
396
+ section: "Dados"
397
+ },
361
398
  orientation: {
362
399
  kind: "enum",
363
400
  values: ["vertical", "horizontal"],
@@ -373,11 +410,54 @@ const CHART_FIELDS = {
373
410
  hint: "Só faz sentido com medidas somáveis (partes de um total)",
374
411
  section: "Aparência"
375
412
  },
413
+ colors: {
414
+ kind: "colors",
415
+ values: "series",
416
+ label: "Cores",
417
+ hint: "Uma cor por medida ou, com a série, por valor dela; vazio = paleta padrão",
418
+ section: "Aparência"
419
+ },
376
420
  showValues: {
377
421
  kind: "boolean",
378
422
  default: true,
379
423
  label: "Mostrar valores",
380
- section: "Aparência"
424
+ section: "Valores"
425
+ },
426
+ valuePosition: {
427
+ kind: "enum",
428
+ values: BAR_VALUE_POSITIONS,
429
+ labels: {
430
+ outside: "fora, na ponta",
431
+ insideEnd: "dentro, na ponta",
432
+ center: "dentro, no meio",
433
+ insideBase: "dentro, na base"
434
+ },
435
+ default: "outside",
436
+ label: "Posição do valor",
437
+ hint: "Empilhado: fora não cabe em cada pedaço — use dentro, ou o total na ponta",
438
+ section: "Valores"
439
+ },
440
+ valueColor: {
441
+ kind: "color",
442
+ default: "#333333",
443
+ label: "Cor do valor",
444
+ hint: "Sem cor escolhida: escuro fora da barra, branco dentro",
445
+ section: "Valores"
446
+ },
447
+ valueSize: {
448
+ kind: "integer",
449
+ min: 8,
450
+ max: 32,
451
+ default: 12,
452
+ label: "Tamanho do valor (px)",
453
+ section: "Valores"
454
+ },
455
+ showTotal: {
456
+ kind: "boolean",
457
+ default: false,
458
+ label: "Total na ponta (empilhado)",
459
+ hint: "Soma da pilha na ponta da barra; só com \"Empilhar séries\"",
460
+ section: "Valores"
381
461
  },
382
462
  labels: LABELS_FIELD,
383
463
  formats: FORMATS_FIELD
@@ -755,6 +835,7 @@ function fieldError(spec, value) {
755
835
  if (error) return `regra ${index + 1}: ${error}`;
756
836
  }
757
837
  return null;
838
+ case "colors": return isRecord(value) && Object.values(value).every((color) => typeof color === "string" && /^#[0-9a-fA-F]{3,8}$/.test(color)) ? null : "precisa ser { série: cor (#rrggbb) }";
758
839
  case "formats":
759
840
  if (!isRecord(value)) return "precisa ser { medida: formato }";
760
841
  for (const [measure, format] of Object.entries(value)) {
@@ -935,10 +1016,17 @@ function layoutModelWarnings(layout, model, types = BUILTIN_CHART_TYPES) {
935
1016
  const { dimensions, measures } = chartReferences(chart, types);
936
1017
  for (const dimension of dimensions) if (!model.dimensions[dimension]) warnings.push(`${name}: dimensão "${dimension}" não existe`);
937
1018
  for (const measure of measures) if (!model.measures[measure]) warnings.push(`${name}: medida "${measure}" não existe`);
1019
+ if (isBuiltinChart(chart) && chart.type === "bar" && chart.series) {
1020
+ if (chart.series === chart.dimension) warnings.push(`${name}: a série é a própria dimensão das categorias`);
1021
+ if (chart.measures.length > 1) warnings.push(`${name}: com série, só a primeira medida ("${chart.measures[0]}") é usada`);
1022
+ }
938
1023
  const columns = chartColumns(chart, types);
939
1024
  for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {
940
1025
  const value = chart[key];
941
1026
  if (spec.kind === "column" && isName(value) && !columns.includes(value)) warnings.push(spec.outside ? isModelField(model, value) ? "" : `${name}: "${value}" (${key}) não existe no modelo` : `${name}: "${value}" (${key}) não é coluna do gráfico`);
1027
+ if (spec.kind === "colors" && isRecord(value) && !(spec.values && chart[spec.values])) {
1028
+ for (const series of Object.keys(value)) if (!measures.includes(series)) warnings.push(`${name}: cor de "${series}" (${key}), que não é medida do gráfico`);
1029
+ }
942
1030
  if (spec.kind === "formatRules" && Array.isArray(value)) value.forEach((rule, index) => {
943
1031
  if (!isRecord(rule)) return;
944
1032
  const context = `${name}: regra ${index + 1} (${key})`;
@@ -1057,4 +1145,4 @@ function layoutFileContent(layout) {
1057
1145
  //#endregion
1058
1146
  export { RULE_OPERATOR_LABEL as C, RULE_OPERATORS as S, rowRuleStyles as T, tableColumns as _, FILTER_FIELDS as a, COLUMN_VERTICAL_ALIGNS as b, chartOfType as c, filterReferences as d, isBuiltinChart as f, resolveChartTypes as g, percentColumn as h, CARD_FIELDS as i, chartReferences as l, layoutModelWarnings as m, CANVAS_PRESETS as n, chartCardStyle as o, layoutFileContent as p, CARDS_FIELDS as r, chartColumns as s, BUILTIN_CHART_TYPES as t, customChartProps as u, validateLayout as v, UNARY_OPERATORS as w, NUMERIC_OPERATORS as x, COLUMN_ALIGNS as y };
1059
1147
 
1060
- //# sourceMappingURL=layout-DAFeHIuP.js.map
1148
+ //# sourceMappingURL=layout-CBzYDoqE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-CBzYDoqE.js","names":["isRecord"],"sources":["../src/formatRules.ts","../src/layout.ts"],"sourcesContent":["/**\n * Formatação condicional da tabela: regras que mudam a cor do texto, o fundo, o negrito e o\n * alinhamento (horizontal e vertical) de uma célula ou da linha inteira conforme o valor de uma dimensão/medida da linha (como a \"formatação\n * condicional\" do Power BI). Usado pelo `DataTable`, pela validação do layout e pelo devtools.\n *\n * ```json\n * \"formatRules\": [\n * { \"field\": \"custoTon\", \"operator\": \">\", \"value\": 100, \"color\": \"#c62828\" },\n * { \"field\": \"margem\", \"operator\": \"<\", \"value\": 0, \"target\": \"row\", \"background\": \"#ffebee\" }\n * ]\n * ```\n */\n\nimport type { DashboardRow } from './types'\n\nexport const COLUMN_ALIGNS = ['left', 'center', 'right'] as const\nexport type ColumnAlign = (typeof COLUMN_ALIGNS)[number]\nexport const COLUMN_VERTICAL_ALIGNS = ['top', 'middle', 'bottom'] as const\nexport type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number]\n\nexport const RULE_OPERATORS = [\n '>',\n '>=',\n '<',\n '<=',\n '=',\n '!=',\n 'between',\n 'contains',\n 'empty',\n 'notEmpty'\n] as const\nexport type RuleOperator = (typeof RULE_OPERATORS)[number]\n\nexport const RULE_OPERATOR_LABEL: Record<RuleOperator, string> = {\n '>': 'maior que',\n '>=': 'maior ou igual a',\n '<': 'menor que',\n '<=': 'menor ou igual a',\n '=': 'igual a',\n '!=': 'diferente de',\n between: 'entre',\n contains: 'contém',\n empty: 'vazio',\n notEmpty: 'não vazio'\n}\n\n/** Operadores que comparam com número (`value`, e `to` no `between`). */\nexport const NUMERIC_OPERATORS: readonly RuleOperator[] = ['>', '>=', '<', '<=', 'between']\n/** Operadores sem valor. */\nexport const UNARY_OPERATORS: readonly RuleOperator[] = ['empty', 'notEmpty']\n\nexport type FormatRule = {\n /**\n * Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma\n * dimensão de fora divide as linhas se tiver mais de um valor por linha).\n */\n field: string\n operator: RuleOperator\n /** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */\n value?: number | string\n /** Fim do intervalo do `between` (inclusivo, como o `value`). */\n to?: number\n /** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */\n target?: 'cell' | 'row'\n /** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */\n column?: string\n /** Cor do texto. */\n color?: string\n /** Cor de fundo. */\n background?: string\n bold?: boolean\n /** Alinhamento horizontal da célula (ou das células da linha). */\n align?: ColumnAlign\n /** Alinhamento vertical da célula (ou das células da linha). */\n verticalAlign?: ColumnVerticalAlign\n}\n\nexport type RuleStyle = Pick<\n FormatRule,\n 'color' | 'background' | 'bold' | 'align' | 'verticalAlign'\n>\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\nconst isColor = (value: unknown) => typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\nconst RULE_KEYS = [\n 'field',\n 'operator',\n 'value',\n 'to',\n 'target',\n 'column',\n 'color',\n 'background',\n 'bold',\n 'align',\n 'verticalAlign'\n]\n\n/** Problema de forma de uma regra (`null` = válida). */\nexport function formatRuleError(rule: unknown): string | null {\n if (!isRecord(rule)) return 'precisa ser um objeto'\n const unknown = Object.keys(rule).find((key) => !RULE_KEYS.includes(key))\n if (unknown) return `campo desconhecido \\`${unknown}\\``\n if (typeof rule.field !== 'string' || !rule.field) return '`field` é obrigatório'\n if (!RULE_OPERATORS.includes(rule.operator as RuleOperator))\n return `\\`operator\\` precisa ser ${RULE_OPERATORS.join(', ')}`\n const operator = rule.operator as RuleOperator\n if (NUMERIC_OPERATORS.includes(operator)) {\n if (typeof rule.value !== 'number' || !Number.isFinite(rule.value))\n return '`value` precisa ser um número'\n if (operator === 'between' && (typeof rule.to !== 'number' || !Number.isFinite(rule.to)))\n return '`to` precisa ser um número (fim do intervalo)'\n } else if (!UNARY_OPERATORS.includes(operator)) {\n if (typeof rule.value !== 'number' && typeof rule.value !== 'string')\n return '`value` precisa ser texto ou número'\n }\n if (operator !== 'between' && rule.to !== undefined) return '`to` só vale com `between`'\n if (rule.target !== undefined && rule.target !== 'cell' && rule.target !== 'row')\n return '`target` precisa ser cell ou row'\n if (rule.column !== undefined && (typeof rule.column !== 'string' || !rule.column))\n return '`column` precisa ser texto'\n if (rule.target === 'row' && rule.column !== undefined) return '`column` não vale com row'\n for (const key of ['color', 'background'])\n if (rule[key] !== undefined && !isColor(rule[key]))\n return `\\`${key}\\` precisa ser uma cor (#rrggbb)`\n if (rule.bold !== undefined && typeof rule.bold !== 'boolean')\n return '`bold` precisa ser true/false'\n if (rule.align !== undefined && !COLUMN_ALIGNS.includes(rule.align as ColumnAlign))\n return `\\`align\\` precisa ser ${COLUMN_ALIGNS.join(', ')}`\n if (\n rule.verticalAlign !== undefined &&\n !COLUMN_VERTICAL_ALIGNS.includes(rule.verticalAlign as ColumnVerticalAlign)\n )\n return `\\`verticalAlign\\` precisa ser ${COLUMN_VERTICAL_ALIGNS.join(', ')}`\n if (\n rule.color === undefined &&\n rule.background === undefined &&\n !rule.bold &&\n rule.align === undefined &&\n rule.verticalAlign === undefined\n )\n return 'precisa de `color`, `background`, `bold`, `align` ou `verticalAlign`'\n return null\n}\n\nconst isEmpty = (value: unknown) => value === null || value === undefined || value === ''\n\n/** A regra vale para esse valor? Valor vazio só casa com `empty` (e `!=`). */\nexport function matchesRule(rule: FormatRule, value: DashboardRow[string] | undefined): boolean {\n if (rule.operator === 'empty') return isEmpty(value)\n if (rule.operator === 'notEmpty') return !isEmpty(value)\n if (isEmpty(value)) return rule.operator === '!='\n const number = typeof value === 'number' ? value : Number(value)\n const target = rule.value\n switch (rule.operator) {\n case '>':\n return number > Number(target)\n case '>=':\n return number >= Number(target)\n case '<':\n return number < Number(target)\n case '<=':\n return number <= Number(target)\n case 'between': {\n const [low, high] = [Number(target), Number(rule.to)].sort((a, b) => a - b)\n return number >= (low as number) && number <= (high as number)\n }\n case '=':\n case '!=': {\n // Número com número compara o valor (1 = \"1.0\"); o resto, o texto sem caixa\n const equal =\n typeof target === 'number' || (Number.isFinite(number) && typeof value === 'number')\n ? number === Number(target)\n : String(value).toLowerCase() === String(target).toLowerCase()\n return rule.operator === '=' ? equal : !equal\n }\n case 'contains':\n return String(value)\n .toLowerCase()\n .includes(String(target ?? '').toLowerCase())\n }\n}\n\n/**\n * Estilo de cada coluna de uma linha: as regras `row` valem para todas, as `cell` só para a coluna\n * delas, por cima das de linha. Entre regras do mesmo alcance, a de baixo ganha.\n */\nexport function rowRuleStyles(\n rules: FormatRule[],\n row: DashboardRow,\n columns: string[]\n): Record<string, RuleStyle> {\n const rowStyle: RuleStyle = {}\n const cells: Record<string, RuleStyle> = {}\n for (const rule of rules) {\n if (!matchesRule(rule, row[rule.field])) continue\n const style = ruleStyle(rule)\n if (rule.target === 'row') Object.assign(rowStyle, style)\n else {\n const column = rule.column ?? rule.field\n cells[column] = { ...cells[column], ...style }\n }\n }\n return Object.fromEntries(columns.map((column) => [column, { ...rowStyle, ...cells[column] }]))\n}\n\nexport function ruleStyle(rule: FormatRule): RuleStyle {\n return {\n ...(rule.color && { color: rule.color }),\n ...(rule.background && { background: rule.background }),\n ...(rule.bold && { bold: true }),\n ...(rule.align && { align: rule.align }),\n ...(rule.verticalAlign && { verticalAlign: rule.verticalAlign })\n }\n}\n","/**\n * Layout de gráficos de uma página: lista declarativa (JSON) que o `<DashboardCharts>` renderiza. É\n * JSON — e não JSX — para o devtools conseguir adicionar e editar gráficos pelo navegador e gravar o\n * arquivo de volta. Fica fora de `server/` porque o client e o servidor validam o mesmo formato.\n *\n * Os campos de cada tipo são descritos em `CHART_FIELDS`: é de lá que saem a validação, os avisos\n * de dimensão/medida inexistente e o formulário da aba Layout do devtools. O app registra tipos\n * próprios (`ChartTypeSpec`) no `<DashboardProvider charts>` e no `devtools.chartTypes` do servidor;\n * as funções daqui recebem o registro completo (`resolveChartTypes`).\n */\n\nimport {\n COLUMN_ALIGNS,\n COLUMN_VERTICAL_ALIGNS,\n type ColumnAlign,\n type ColumnVerticalAlign,\n type FormatRule,\n formatRuleError\n} from './formatRules'\nimport { isAggregate, type ModelDef, VALUE_FORMATS, type ValueFormat } from './model'\n\nexport const CHART_TYPES = ['kpi', 'combo', 'bar', 'donut', 'table'] as const\nexport type ChartType = (typeof CHART_TYPES)[number]\n\nexport const CHART_TYPE_LABEL: Record<ChartType, string> = {\n kpi: 'KPI (card)',\n combo: 'Combo (barras + linha)',\n bar: 'Barras',\n donut: 'Rosca',\n table: 'Tabela'\n}\n\n/** Colunas do grid em telas largas; cada gráfico ocupa `span` delas (no celular, a linha toda). */\nexport const LAYOUT_COLUMNS = 12\n\ntype BaseChartDef = {\n /** Identificador estável do gráfico dentro do layout (letras, números, \"-\" e \"_\"). */\n id: string\n title: string\n /** Colunas ocupadas (1 a 12). Padrão: 12. */\n span?: number\n height?: number\n /** Aparência do card deste gráfico (sobrepõe o `cards` do layout). */\n card?: CardStyle\n}\n\n/**\n * Formato de uma medida num gráfico: sobrepõe o `format`/`decimals` do dicionário do modelo só\n * naquele gráfico.\n */\nexport type ChartNumberFormat = {\n format?: ValueFormat\n /** Casas decimais (0 a 10). */\n decimals?: number\n /** Notação curta: 1,2 mil / 3,4 mi. */\n compact?: boolean\n /** Texto antes do número (ex.: \"US$ \"). */\n prefix?: string\n /** Texto depois do número (ex.: \" t\"). */\n suffix?: string\n}\n\nexport {\n COLUMN_ALIGNS,\n COLUMN_VERTICAL_ALIGNS,\n type ColumnAlign,\n type ColumnVerticalAlign\n} from './formatRules'\n\n/** Rótulos e formatos do gráfico, por nome de dimensão/medida (sobrepõem o dicionário). */\ntype ColumnsDisplay = {\n /** `{ dimensãoOuMedida: 'Rótulo' }`: legenda, tooltip e cabeçalho. */\n labels?: Record<string, string>\n /** `{ medida: { decimals: 1, ... } }`. */\n formats?: Record<string, ChartNumberFormat>\n}\n\nexport type ComboChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'combo'\n dimension: string\n bar: string\n line: string\n detail?: string\n /** Nome de um formatter passado ao `<DashboardCharts formatters>` (funções não cabem no JSON). */\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n maxLabels?: number\n barColor?: string\n lineColor?: string\n }\n\nexport type BarChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'bar'\n dimension: string\n /** Uma série por medida (ou, com `series`, só a primeira medida). */\n measures: string[]\n /**\n * Dimensão da legenda: quebra a medida pelos valores dela, uma série por valor (a \"Legenda\" do\n * Power BI). Usa só a primeira medida.\n */\n series?: string\n formatSeries?: string\n /** Séries além dessa quantidade viram \"Outros\" (com `series`). */\n maxSeries?: number\n orientation?: 'vertical' | 'horizontal'\n stacked?: boolean\n /**\n * `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida (com `series`, pelo\n * total da barra).\n */\n sort?: 'dimension' | 'desc' | 'asc'\n /** Só os N primeiros depois de ordenar. */\n limit?: number\n formatDimension?: string\n clickToFilter?: boolean\n hideEmpty?: boolean\n /** Todos os valores da dimensão no eixo, com ou sem dado (sobrepõe `hideEmpty`). */\n showEmpty?: boolean\n showValues?: boolean\n /** Onde fica o valor de cada barra. Padrão: `outside` (na ponta, fora da barra). */\n valuePosition?: BarValuePosition\n /** Cor do valor. Padrão: escuro fora da barra, branco dentro. */\n valueColor?: string\n /** Tamanho do valor, em px. Padrão: 12. */\n valueSize?: number\n /** Empilhado: total da pilha na ponta. */\n showTotal?: boolean\n /**\n * Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também\n * \"(em branco)\" e \"Outros\"). O que faltar segue a paleta.\n */\n colors?: Record<string, string>\n }\n\nexport const BAR_VALUE_POSITIONS = ['outside', 'insideEnd', 'center', 'insideBase'] as const\nexport type BarValuePosition = (typeof BAR_VALUE_POSITIONS)[number]\n\nexport type DonutChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'donut'\n dimension: string\n measure: string\n /** Fatias além dessa quantidade viram \"Outros\". */\n maxSlices?: number\n /** Casas decimais do percentual. Padrão: 1. */\n percentDecimals?: number\n formatDimension?: string\n clickToFilter?: boolean\n }\n\nexport type TableChartDef = BaseChartDef &\n ColumnsDisplay & {\n type: 'table'\n dimensions?: string[]\n measures: string[]\n /**\n * Medidas com coluna de participação no total: valor da linha ÷ soma da coluna. Medida que não\n * está em `measures` é consultada só para o percentual.\n */\n percentOf?: string[]\n /** Casas decimais das colunas de percentual. Padrão: 1. */\n percentDecimals?: number\n /**\n * Ordem das colunas misturando dimensões, medidas e percentuais (`%:medida`), ex.: uma medida\n * antes de uma dimensão. Ausente = dimensões e depois medidas; percentual fora da ordem vem logo\n * depois da medida dele (ou no fim, se a medida não é coluna).\n */\n columnOrder?: string[]\n /** Formatter por dimensão (`{ mes: 'nomeMes' }`). */\n formatDimensions?: Record<string, string>\n /** Linha de total, consultada sem as dimensões (razões saem certas, não somadas). */\n showTotal?: boolean\n /**\n * Dimensão ou medida da ordem inicial; o usuário reordena clicando no cabeçalho. Pode estar fora\n * da tabela (ex.: número do mês para ordenar o nome do mês): entra na consulta sem coluna.\n */\n sortBy?: string\n sortDirection?: 'asc' | 'desc'\n clickToFilter?: boolean\n /** Formatação condicional: cor do texto/fundo da célula ou da linha conforme uma regra. */\n formatRules?: FormatRule[]\n /** Bordas entre as células (grade). Padrão: só a linha embaixo de cada linha. */\n cellBorders?: boolean\n /** Cor das bordas da grade. */\n borderColor?: string\n /**\n * Alinhamento horizontal por coluna (`{ coluna: 'center' }`, `%:medida` para o percentual), no\n * cabeçalho, nas células e no total. Padrão: dimensão à esquerda, número à direita.\n */\n align?: Record<string, ColumnAlign>\n /** Alinhamento vertical por coluna (`top`, `middle`, `bottom`). Padrão: `middle`. */\n verticalAlign?: Record<string, ColumnVerticalAlign>\n }\n\nexport type KpiChartDef = Omit<BaseChartDef, 'height'> &\n ColumnsDisplay & {\n type: 'kpi'\n /** Medida do número grande (total com os filtros da página). */\n measure: string\n /** Medida extra numa linha menor embaixo (ex.: volume, custo/t). */\n secondary?: string\n }\n\n/** Gráficos da lib (`type` em `CHART_TYPES`). */\nexport type BuiltinChartDef =\n | KpiChartDef\n | ComboChartDef\n | BarChartDef\n | DonutChartDef\n | TableChartDef\n\n/** Gráfico de um tipo registrado pelo app: os campos vêm do `ChartTypeSpec` dele. */\nexport type CustomChartDef = BaseChartDef & { type: string; [field: string]: unknown }\n\nexport type ChartDef = BuiltinChartDef | CustomChartDef\n\nexport const isBuiltinChart = (chart: ChartDef): chart is BuiltinChartDef =>\n (CHART_TYPES as readonly string[]).includes(chart.type)\n\n/** Filtro (slicer) da página: múltipla escolha de uma dimensão, com filtro cruzado. */\nexport type FilterDef = {\n dimension: string\n /** Padrão: `label` da dimensão no dicionário. */\n label?: string\n width?: number\n /** Nome de um formatter do `<DashboardFilters formatters>` para as opções. */\n formatLabel?: string\n}\n\n/**\n * Tela de tamanho fixo: o `<DashboardCanvas>` desenha o dashboard nessas dimensões e o escala para\n * caber na largura da tela, mantendo a proporção — os gráficos não se reorganizam (como o \"Ajustar à\n * largura\" do Power BI).\n */\nexport type CanvasDef = {\n /** Largura de projeto, em px (ex.: 1920). */\n width: number\n /** Altura mínima de projeto, em px (ex.: 1080); conteúdo mais alto estende a tela. */\n height?: number\n}\n\n/** Moldura de um gráfico (o card): ausente = padrão do componente. */\nexport type CardStyle = {\n background?: string\n borderColor?: string\n /** Espessura da borda, em px (0 = sem borda). */\n borderWidth?: number\n /** Arredondamento dos cantos, em px. */\n borderRadius?: number\n /** Sombra (elevation do MUI, 0 a 24). */\n shadow?: number\n /** Espaço interno, em px. */\n padding?: number\n titleColor?: string\n /** Tamanho do título, em px. */\n titleSize?: number\n titleAlign?: 'left' | 'center' | 'right'\n}\n\n/** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */\nexport type CardsStyle = CardStyle & {\n /** Espaço entre os cards, em px. */\n gap?: number\n}\n\nexport type ChartsLayout = {\n /** Nome do arquivo (`<layoutsDir>/<id>.json`) e chave do rascunho no devtools. */\n id: string\n /** Ausente = responsivo (12 colunas em telas largas, um gráfico por linha no celular). */\n canvas?: CanvasDef\n /** Estilo padrão dos cards e espaço entre eles; cada gráfico sobrepõe com `card`. */\n cards?: CardsStyle\n /** Renderizados pelo `<DashboardFilters>`, na ordem. */\n filters?: FilterDef[]\n charts: ChartDef[]\n}\n\nexport const LAYOUT_ID = /^[A-Za-z0-9_-]{1,80}$/\n\n// --- campos por tipo ---\n\nexport type FieldSpec = {\n label: string\n hint?: string\n /** Agrupamento no formulário do devtools. */\n section:\n | 'Gráfico'\n | 'Dados'\n | 'Aparência'\n | 'Valores'\n | 'Rótulos e formatos'\n | 'Formatação condicional'\n | 'Filtro'\n | 'Tela'\n | 'Card'\n required?: boolean\n /** Valor que o componente usa quando o campo não está no JSON. */\n default?: unknown\n} & (\n | { kind: 'text' }\n | { kind: 'color' }\n | { kind: 'boolean' }\n | { kind: 'integer'; min: number; max: number }\n | { kind: 'enum'; values: readonly string[]; labels?: Record<string, string> }\n /** Nome de dimensão/medida do modelo (uma ou uma lista). */\n | { kind: 'dimension' | 'measure' }\n | { kind: 'dimensions' }\n /**\n * `percent`: cada medida vira uma coluna de % do total (`%:medida`), que entra no `columnOrder`\n * como as outras colunas.\n */\n | { kind: 'measures'; percent?: boolean }\n /**\n * Nome de um formatter do `<DashboardCharts formatters>`. `of`: campo `dimension` que ele formata\n * (o devtools edita no ⚙ daquela dimensão); dispensável com uma dimensão e um formatter só.\n */\n | { kind: 'formatter'; of?: string }\n /** `{ dimensão: formatter }`. */\n | { kind: 'formatters' }\n /**\n * Uma das dimensões/medidas escolhidas no próprio gráfico. `outside`: também qualquer dimensão ou\n * medida do modelo (o componente consulta o que estiver fora do gráfico, sem mostrar).\n */\n | { kind: 'column'; outside?: boolean }\n /** `{ dimensãoOuMedida: rótulo }` das colunas do gráfico. */\n | { kind: 'labels' }\n /** `{ medida: ChartNumberFormat }` das medidas do gráfico. */\n | { kind: 'formats' }\n /** `{ coluna: 'left' | 'center' | 'right' }`: alinhamento horizontal de cada coluna. */\n | { kind: 'aligns' }\n /** `{ coluna: 'top' | 'middle' | 'bottom' }`: alinhamento vertical de cada coluna. */\n | { kind: 'verticalAligns' }\n /**\n * Ordem das colunas (dimensões e medidas misturadas). O devtools junta os campos `dimensions` e\n * `measures` da mesma seção numa lista só e grava a ordem aqui quando não é a padrão.\n */\n | { kind: 'columnOrder' }\n /** Regras de formatação condicional (`FormatRule[]`). */\n | { kind: 'formatRules' }\n /**\n * `{ série: '#rrggbb' }`: uma cor por medida do gráfico ou, quando o campo `dimension` em\n * `values` está preenchido, por valor dessa dimensão (o devtools não lista os valores; digita).\n */\n | { kind: 'colors'; values?: string }\n)\n\nconst common = (height: number): Record<string, FieldSpec> => ({\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n span: {\n kind: 'integer',\n min: 1,\n max: LAYOUT_COLUMNS,\n default: LAYOUT_COLUMNS,\n label: 'Largura',\n hint: 'Colunas ocupadas em telas largas; no celular, cada gráfico ocupa a linha toda (menos com a tela de tamanho fixo)',\n section: 'Gráfico'\n },\n height: {\n kind: 'integer',\n min: 120,\n max: 2000,\n default: height,\n label: 'Altura (px)',\n section: 'Gráfico'\n }\n})\n\nconst formatDimension: FieldSpec = {\n kind: 'formatter',\n label: 'Formatar dimensão',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n}\nconst clickToFilter: FieldSpec = {\n kind: 'boolean',\n default: false,\n label: 'Clique filtra o dashboard',\n hint: 'Ctrl/Cmd+clique acumula; clicar de novo no único selecionado limpa',\n section: 'Dados'\n}\nconst hideEmpty: FieldSpec = {\n kind: 'boolean',\n default: true,\n label: 'Esconder grupos vazios',\n section: 'Dados'\n}\n\n/**\n * Rótulo de cada dimensão/medida do gráfico. Um tipo do app ganha o mesmo editor com um campo\n * `{ kind: 'labels' }` (o componente recebe `labels`; ver `fieldLabel`) e `{ kind: 'formats' }`.\n */\nconst LABELS_FIELD: FieldSpec = {\n kind: 'labels',\n label: 'Rótulos',\n hint: 'Só neste gráfico; vazio = rótulo do dicionário do modelo',\n section: 'Rótulos e formatos'\n}\n/** Formato de cada medida do gráfico (o componente recebe `formats`; ver `measureFormatter`). */\nconst FORMATS_FIELD: FieldSpec = {\n kind: 'formats',\n label: 'Formatos',\n hint: 'Só neste gráfico; vazio = formato do dicionário do modelo',\n section: 'Rótulos e formatos'\n}\nconst percentDecimals: FieldSpec = {\n kind: 'integer',\n min: 0,\n max: 6,\n default: 1,\n label: 'Casas do %',\n section: 'Dados'\n}\n\nexport const CHART_FIELDS: Record<ChartType, Record<string, FieldSpec>> = {\n kpi: {\n title: { kind: 'text', label: 'Título', section: 'Gráfico' },\n // KPI tem a altura do conteúdo: só título e largura\n span: common(0).span as FieldSpec,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n secondary: {\n kind: 'measure',\n label: 'Medida secundária',\n hint: 'Linha menor embaixo do número (ex.: volume, custo/t)',\n section: 'Dados'\n },\n labels: {\n ...LABELS_FIELD,\n hint: 'O da medida principal aparece acima do número; o da secundária, na linha de baixo'\n },\n formats: FORMATS_FIELD\n },\n combo: {\n ...common(360),\n maxLabels: {\n kind: 'integer',\n min: 0,\n max: 1000,\n default: 24,\n label: 'Máx. rótulos',\n hint: 'Acima dessa quantidade de barras, os valores aparecem só no tooltip',\n section: 'Gráfico'\n },\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (eixo X)', section: 'Dados' },\n formatDimension,\n bar: { kind: 'measure', required: true, label: 'Barra', section: 'Dados' },\n line: { kind: 'measure', required: true, label: 'Linha', section: 'Dados' },\n detail: { kind: 'measure', label: 'Detalhe (2ª linha do rótulo)', section: 'Dados' },\n clickToFilter,\n hideEmpty,\n barColor: { kind: 'color', default: '#F58220', label: 'Cor da barra', section: 'Aparência' },\n lineColor: { kind: 'color', default: '#555555', label: 'Cor da linha', section: 'Aparência' },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD\n },\n bar: {\n ...common(360),\n dimension: {\n kind: 'dimension',\n required: true,\n label: 'Dimensão (categorias)',\n section: 'Dados'\n },\n formatDimension: { ...formatDimension, of: 'dimension' },\n measures: {\n kind: 'measures',\n required: true,\n label: 'Medidas (uma série cada)',\n hint: 'Com a série (legenda), só a primeira medida é usada',\n section: 'Dados'\n },\n series: {\n kind: 'dimension',\n label: 'Série (legenda)',\n hint: 'Quebra a medida pelos valores desta dimensão: uma série por valor, com cor fixa por valor (ex.: tipo de descarga)',\n section: 'Dados'\n },\n formatSeries: { ...formatDimension, label: 'Formatar série', of: 'series' },\n maxSeries: {\n kind: 'integer',\n min: 2,\n max: 8,\n default: 6,\n label: 'Máx. séries',\n hint: 'Com a série: as menores além dessa quantidade viram \"Outros\"',\n section: 'Dados'\n },\n sort: {\n kind: 'enum',\n values: ['dimension', 'desc', 'asc'],\n labels: { dimension: 'pela dimensão', desc: 'maior primeiro', asc: 'menor primeiro' },\n default: 'dimension',\n label: 'Ordem',\n hint: 'Maior/menor primeiro usa a primeira medida (com a série, o total da barra)',\n section: 'Dados'\n },\n limit: {\n kind: 'integer',\n min: 1,\n max: 1000,\n label: 'Só os N primeiros',\n hint: 'Aplicado depois de ordenar (ex.: top 10 navios)',\n section: 'Dados'\n },\n clickToFilter,\n hideEmpty,\n showEmpty: {\n kind: 'boolean',\n default: false,\n label: 'Mostrar categorias sem dados',\n hint: 'Todos os valores da dimensão no eixo, zerados quando não há dado (ex.: todos os meses). Sobrepõe \"Esconder grupos vazios\"',\n section: 'Dados'\n },\n orientation: {\n kind: 'enum',\n values: ['vertical', 'horizontal'],\n default: 'vertical',\n label: 'Orientação',\n hint: 'Horizontal cabe nomes longos (navios, funções)',\n section: 'Aparência'\n },\n stacked: {\n kind: 'boolean',\n default: false,\n label: 'Empilhar séries',\n hint: 'Só faz sentido com medidas somáveis (partes de um total)',\n section: 'Aparência'\n },\n colors: {\n kind: 'colors',\n values: 'series',\n label: 'Cores',\n hint: 'Uma cor por medida ou, com a série, por valor dela; vazio = paleta padrão',\n section: 'Aparência'\n },\n showValues: { kind: 'boolean', default: true, label: 'Mostrar valores', section: 'Valores' },\n valuePosition: {\n kind: 'enum',\n values: BAR_VALUE_POSITIONS,\n labels: {\n outside: 'fora, na ponta',\n insideEnd: 'dentro, na ponta',\n center: 'dentro, no meio',\n insideBase: 'dentro, na base'\n },\n default: 'outside',\n label: 'Posição do valor',\n hint: 'Empilhado: fora não cabe em cada pedaço — use dentro, ou o total na ponta',\n section: 'Valores'\n },\n valueColor: {\n kind: 'color',\n default: '#333333',\n label: 'Cor do valor',\n hint: 'Sem cor escolhida: escuro fora da barra, branco dentro',\n section: 'Valores'\n },\n valueSize: {\n kind: 'integer',\n min: 8,\n max: 32,\n default: 12,\n label: 'Tamanho do valor (px)',\n section: 'Valores'\n },\n showTotal: {\n kind: 'boolean',\n default: false,\n label: 'Total na ponta (empilhado)',\n hint: 'Soma da pilha na ponta da barra; só com \"Empilhar séries\"',\n section: 'Valores'\n },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD\n },\n donut: {\n ...common(300),\n dimension: { kind: 'dimension', required: true, label: 'Dimensão (fatias)', section: 'Dados' },\n formatDimension,\n measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' },\n maxSlices: {\n kind: 'integer',\n min: 2,\n max: 8,\n default: 6,\n label: 'Máx. fatias',\n hint: 'As menores além dessa quantidade viram \"Outros\"',\n section: 'Dados'\n },\n clickToFilter,\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD,\n percentDecimals\n },\n table: {\n ...common(400),\n dimensions: { kind: 'dimensions', label: 'Dimensões (linhas)', section: 'Dados' },\n formatDimensions: {\n kind: 'formatters',\n label: 'Formatar dimensões',\n hint: 'Formatters passados ao <DashboardCharts formatters={{ ... }}> da página',\n section: 'Dados'\n },\n measures: { kind: 'measures', required: true, label: 'Medidas (colunas)', section: 'Dados' },\n columnOrder: {\n kind: 'columnOrder',\n label: 'Ordem das colunas',\n hint: 'Dimensões, medidas e % do total em qualquer ordem; vazio = dimensões e depois medidas (cada % logo depois da medida)',\n section: 'Dados'\n },\n percentOf: {\n kind: 'measures',\n percent: true,\n label: '% do total',\n hint: 'Coluna extra com a participação de cada linha: valor da linha ÷ soma da coluna. Faz sentido com medidas somáveis (custo, volume), não com razões.',\n section: 'Dados'\n },\n sortBy: {\n kind: 'column',\n outside: true,\n label: 'Ordenar por',\n hint: 'Ordem inicial; quem usa reordena clicando no cabeçalho. Pode ser uma dimensão/medida fora da tabela (ex.: número do mês para o nome do mês) — uma dimensão de fora divide as linhas se tiver mais de um valor por linha.',\n section: 'Dados'\n },\n sortDirection: {\n kind: 'enum',\n values: ['desc', 'asc'],\n labels: { desc: 'decrescente', asc: 'crescente' },\n default: 'desc',\n label: 'Direção',\n section: 'Dados'\n },\n showTotal: { kind: 'boolean', default: true, label: 'Linha de total', section: 'Dados' },\n clickToFilter: {\n ...clickToFilter,\n hint: 'Clicar numa linha filtra pela primeira dimensão'\n },\n cellBorders: {\n kind: 'boolean',\n default: false,\n label: 'Bordas entre as células',\n hint: 'Grade em volta de cada célula; desligado = só a linha embaixo de cada linha',\n section: 'Aparência'\n },\n borderColor: {\n kind: 'color',\n default: '#e0e0e0',\n label: 'Cor das bordas',\n section: 'Aparência'\n },\n labels: LABELS_FIELD,\n formats: FORMATS_FIELD,\n align: {\n kind: 'aligns',\n label: 'Alinhamento',\n hint: 'Alinhamento horizontal de cada coluna; vazio = dimensão à esquerda, número à direita',\n section: 'Rótulos e formatos'\n },\n verticalAlign: {\n kind: 'verticalAligns',\n label: 'Alinhamento vertical',\n hint: 'Alinhamento vertical de cada coluna (aparece com texto em mais de uma linha); vazio = no meio',\n section: 'Rótulos e formatos'\n },\n percentDecimals,\n formatRules: {\n kind: 'formatRules',\n label: 'Regras',\n hint: 'Muda a cor do texto, o fundo ou o negrito da célula ou da linha conforme o valor de uma dimensão/medida (pode ser de fora da tabela). Regras de linha primeiro, de célula por cima; a de baixo ganha.',\n section: 'Formatação condicional'\n }\n }\n}\n\n// --- registro de tipos ---\n\n/**\n * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e\n * `height` entram sozinhos (`height: null` = sem altura, como o KPI) — `fields` tem só os do\n * componente. Objeto puro (sem React): o mesmo arquivo serve o client e o servidor.\n *\n * ```ts\n * export const rankingType = {\n * label: 'Ranking',\n * height: 300,\n * fields: {\n * dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Dados' },\n * measure: { kind: 'measure', required: true, label: 'Medida', section: 'Dados' }\n * }\n * } satisfies ChartTypeSpec\n * ```\n */\nexport type ChartTypeSpec = {\n label: string\n fields: Record<string, FieldSpec>\n /** Altura padrão em px (padrão: 300); `null` = o componente tem a altura do conteúdo. */\n height?: number | null\n}\n\n/** Registro completo (lib + app), com os campos comuns já incluídos. Ver `resolveChartTypes`. */\nexport type ChartTypes = Record<string, { label: string; fields: Record<string, FieldSpec> }>\n\nexport const BUILTIN_CHART_TYPES: ChartTypes = Object.fromEntries(\n CHART_TYPES.map((type) => [type, { label: CHART_TYPE_LABEL[type], fields: CHART_FIELDS[type] }])\n)\n\n/**\n * Junta os tipos da lib com os do app. Nome igual a um tipo da lib ou fora do padrão de id é erro\n * (aparece na subida, não no meio da edição).\n */\nexport function resolveChartTypes(custom: Record<string, ChartTypeSpec> = {}): ChartTypes {\n const types: ChartTypes = { ...BUILTIN_CHART_TYPES }\n for (const [name, spec] of Object.entries(custom)) {\n if (BUILTIN_CHART_TYPES[name])\n throw new Error(`Tipo de gráfico \"${name}\" já existe na lib: escolha outro nome`)\n if (!LAYOUT_ID.test(name))\n throw new Error(`Tipo de gráfico \"${name}\": use letras, números, \"-\" e \"_\"`)\n const { title, span, height } = common(spec.height ?? 300)\n const base = spec.height === null ? { title, span } : { title, span, height }\n types[name] = {\n label: spec.label,\n fields: { ...base, ...spec.fields } as Record<string, FieldSpec>\n }\n }\n return types\n}\n\n/**\n * Props que o componente de um tipo do app recebe: os campos do gráfico no JSON (com os `default`\n * dos campos aplicados e os formatters já trocados pelas funções), sem `id`, `type`, `title` e\n * `span`, mais o `queryLabel` (nome da consulta no devtools).\n */\nexport function customChartProps(\n chart: ChartDef,\n types: ChartTypes,\n formatters: Record<string, unknown>\n): Record<string, unknown> {\n const values = chart as Record<string, unknown>\n const props: Record<string, unknown> = {}\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n if (key === 'title' || key === 'span') continue\n const value = values[key] ?? spec.default\n if (value === undefined) continue\n if (spec.kind === 'formatter') props[key] = formatters[value as string]\n else if (spec.kind === 'formatters')\n props[key] = Object.fromEntries(\n Object.entries(value as Record<string, string>)\n .filter(([, name]) => formatters[name])\n .map(([dimension, name]) => [dimension, formatters[name]])\n )\n else props[key] = value\n }\n return { ...props, queryLabel: `${chart.title || chart.id} · ${chart.type}` }\n}\n\nexport const FILTER_FIELDS: Record<string, FieldSpec> = {\n dimension: { kind: 'dimension', required: true, label: 'Dimensão', section: 'Filtro' },\n label: {\n kind: 'text',\n label: 'Rótulo',\n hint: 'Padrão: rótulo da dimensão no dicionário',\n section: 'Filtro'\n },\n width: {\n kind: 'integer',\n min: 80,\n max: 800,\n default: 200,\n label: 'Largura (px)',\n section: 'Filtro'\n },\n formatLabel: {\n kind: 'formatter',\n label: 'Formatar opções',\n hint: 'Formatters passados ao <DashboardFilters formatters={{ ... }}> da página',\n section: 'Filtro'\n }\n}\n\nexport const CANVAS_FIELDS: Record<string, FieldSpec> = {\n width: {\n kind: 'integer',\n min: 320,\n max: 7680,\n required: true,\n label: 'Largura (px)',\n section: 'Tela'\n },\n height: {\n kind: 'integer',\n min: 200,\n max: 4320,\n label: 'Altura mínima (px)',\n hint: 'Conteúdo mais alto que isso estende a tela',\n section: 'Tela'\n }\n}\n\n/** Campos de `CardStyle` (o `card` de um gráfico). */\nexport const CARD_FIELDS: Record<string, FieldSpec> = {\n background: { kind: 'color', default: '#ffffff', label: 'Fundo', section: 'Card' },\n borderColor: { kind: 'color', default: '#e0e0e0', label: 'Cor da borda', section: 'Card' },\n borderWidth: {\n kind: 'integer',\n min: 0,\n max: 8,\n default: 1,\n label: 'Borda (px)',\n hint: '0 = sem borda',\n section: 'Card'\n },\n borderRadius: {\n kind: 'integer',\n min: 0,\n max: 40,\n default: 4,\n label: 'Cantos (px)',\n section: 'Card'\n },\n shadow: {\n kind: 'integer',\n min: 0,\n max: 24,\n default: 0,\n label: 'Sombra',\n hint: 'Elevação do MUI: 0 = sem sombra, 1 a 4 = sutil, acima disso = forte',\n section: 'Card'\n },\n padding: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço interno (px)',\n section: 'Card'\n },\n titleColor: { kind: 'color', default: '#212121', label: 'Cor do título', section: 'Card' },\n titleSize: {\n kind: 'integer',\n min: 10,\n max: 40,\n default: 16,\n label: 'Tamanho do título (px)',\n section: 'Card'\n },\n titleAlign: {\n kind: 'enum',\n values: ['left', 'center', 'right'],\n labels: { left: 'à esquerda', center: 'centralizado', right: 'à direita' },\n default: 'left',\n label: 'Alinhamento do título',\n section: 'Card'\n }\n}\n\n/** Campos de `CardsStyle` (`layout.cards`): o espaço entre os cards e o estilo padrão deles. */\nexport const CARDS_FIELDS: Record<string, FieldSpec> = {\n gap: {\n kind: 'integer',\n min: 0,\n max: 64,\n default: 12,\n label: 'Espaço entre cards (px)',\n section: 'Card'\n },\n ...CARD_FIELDS\n}\n\n/** Estilo de um card: o padrão do layout com o do gráfico por cima. */\nexport function chartCardStyle(layout: ChartsLayout, chart: ChartDef): CardStyle {\n const { gap: _gap, ...defaults } = layout.cards ?? {}\n return { ...defaults, ...chart.card }\n}\n\n/** Tamanhos comuns de tela para o devtools. */\nexport const CANVAS_PRESETS: CanvasDef[] = [\n { width: 1920, height: 1080 },\n { width: 1600, height: 900 },\n { width: 1366, height: 768 },\n { width: 1280, height: 720 }\n]\n\n// --- validação ---\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isName = (value: unknown): value is string => typeof value === 'string' && !!value\n\nfunction fieldError(spec: FieldSpec, value: unknown): string | null {\n if (value === undefined) return spec.required ? 'é obrigatório' : null\n switch (spec.kind) {\n case 'text':\n return typeof value === 'string' ? null : 'precisa ser texto'\n case 'color':\n return typeof value === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(value)\n ? null\n : 'precisa ser uma cor (#rrggbb)'\n case 'boolean':\n return typeof value === 'boolean' ? null : 'precisa ser true/false'\n case 'integer':\n return typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= spec.min &&\n value <= spec.max\n ? null\n : `precisa ser um inteiro de ${spec.min} a ${spec.max}`\n case 'enum':\n return spec.values.includes(value as string) ? null : `precisa ser ${spec.values.join(', ')}`\n case 'dimension':\n case 'measure':\n case 'formatter':\n case 'column':\n return isName(value) ? null : spec.required ? 'é obrigatório' : 'precisa ser texto'\n case 'dimensions':\n case 'measures':\n case 'columnOrder':\n if (!Array.isArray(value) || !value.every(isName)) return 'precisa ser uma lista de nomes'\n if (spec.required && !value.length) return 'precisa de pelo menos um item'\n return new Set(value).size === value.length ? null : 'tem item repetido'\n case 'formatters':\n return isRecord(value) && Object.values(value).every(isName)\n ? null\n : 'precisa ser { dimensão: formatter }'\n case 'labels':\n return isRecord(value) && Object.values(value).every((label) => typeof label === 'string')\n ? null\n : 'precisa ser { coluna: rótulo }'\n case 'aligns':\n return isRecord(value) &&\n Object.values(value).every((align) => COLUMN_ALIGNS.includes(align as ColumnAlign))\n ? null\n : `precisa ser { coluna: ${COLUMN_ALIGNS.join(' | ')} }`\n case 'verticalAligns':\n return isRecord(value) &&\n Object.values(value).every((align) =>\n COLUMN_VERTICAL_ALIGNS.includes(align as ColumnVerticalAlign)\n )\n ? null\n : `precisa ser { coluna: ${COLUMN_VERTICAL_ALIGNS.join(' | ')} }`\n case 'formatRules': {\n if (!Array.isArray(value)) return 'precisa ser uma lista de regras'\n for (const [index, rule] of value.entries()) {\n const error = formatRuleError(rule)\n if (error) return `regra ${index + 1}: ${error}`\n }\n return null\n }\n case 'colors':\n return isRecord(value) &&\n Object.values(value).every(\n (color) => typeof color === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(color)\n )\n ? null\n : 'precisa ser { série: cor (#rrggbb) }'\n case 'formats': {\n if (!isRecord(value)) return 'precisa ser { medida: formato }'\n for (const [measure, format] of Object.entries(value)) {\n const error = numberFormatError(format)\n if (error) return `\"${measure}\": ${error}`\n }\n return null\n }\n }\n}\n\nconst NUMBER_FORMAT_KEYS = ['format', 'decimals', 'compact', 'prefix', 'suffix']\n\nfunction numberFormatError(format: unknown): string | null {\n if (!isRecord(format)) return 'precisa ser um objeto'\n const unknown = Object.keys(format).find((key) => !NUMBER_FORMAT_KEYS.includes(key))\n if (unknown) return `campo desconhecido \\`${unknown}\\``\n const { decimals } = format\n if (format.format !== undefined && !VALUE_FORMATS.includes(format.format as ValueFormat))\n return `formato inválido \"${String(format.format)}\"`\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n return '`decimals` precisa ser um inteiro de 0 a 10'\n if (format.compact !== undefined && typeof format.compact !== 'boolean')\n return '`compact` precisa ser true/false'\n for (const key of ['prefix', 'suffix'])\n if (format[key] !== undefined && typeof format[key] !== 'string')\n return `\\`${key}\\` precisa ser texto`\n return null\n}\n\n/**\n * Problemas de forma do layout (vazio = válido). Confere a forma do objeto porque o rascunho do\n * devtools chega como JSON da request.\n */\nexport function validateLayout(\n layout: ChartsLayout,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n if (!isRecord(layout)) return ['layout inválido']\n if (typeof layout.id !== 'string' || !LAYOUT_ID.test(layout.id))\n return ['`id` do layout: letras, números, \"-\" e \"_\" (até 80)']\n if (!Array.isArray(layout.charts)) return ['`charts` precisa ser uma lista']\n\n const errors: string[] = []\n const ids = new Set<string>()\n layout.charts.forEach((chart: unknown, index) => {\n const context = `gráfico ${index + 1}`\n if (!isRecord(chart)) {\n errors.push(`${context} inválido`)\n return\n }\n if (typeof chart.id !== 'string' || !LAYOUT_ID.test(chart.id))\n errors.push(`${context}: \\`id\\` inválido`)\n else if (ids.has(chart.id)) errors.push(`${context}: id repetido \"${chart.id}\"`)\n else ids.add(chart.id)\n\n const fields =\n typeof chart.type === 'string' && Object.hasOwn(types, chart.type)\n ? types[chart.type]?.fields\n : undefined\n if (!fields) {\n errors.push(`${context}: tipo inválido \"${chart.type}\"`)\n return\n }\n if (typeof chart.title !== 'string') errors.push(`${context}: \\`title\\` precisa ser texto`)\n const rest = Object.fromEntries(Object.entries(fields).filter(([key]) => key !== 'title'))\n errors.push(...fieldsErrors(context, rest, chart, ['id', 'type', 'title', 'card']))\n if (chart.card !== undefined) {\n if (!isRecord(chart.card)) errors.push(`${context}: \\`card\\` precisa ser um objeto`)\n else errors.push(...fieldsErrors(`${context}: card`, CARD_FIELDS, chart.card))\n }\n })\n\n if (layout.cards !== undefined) {\n if (!isRecord(layout.cards)) errors.push('`cards` precisa ser um objeto')\n else errors.push(...fieldsErrors('cards', CARDS_FIELDS, layout.cards))\n }\n\n if (layout.canvas !== undefined) {\n if (!isRecord(layout.canvas)) errors.push('`canvas` precisa ser { width, height? }')\n else errors.push(...fieldsErrors('canvas', CANVAS_FIELDS, layout.canvas))\n }\n\n if (layout.filters !== undefined) {\n if (!Array.isArray(layout.filters)) return [...errors, '`filters` precisa ser uma lista']\n const dimensions = new Set<string>()\n layout.filters.forEach((filter: unknown, index) => {\n const context = `filtro ${index + 1}`\n if (!isRecord(filter)) {\n errors.push(`${context} inválido`)\n return\n }\n errors.push(...fieldsErrors(context, FILTER_FIELDS, filter))\n if (isName(filter.dimension)) {\n if (dimensions.has(filter.dimension))\n errors.push(`${context}: dimensão repetida \"${filter.dimension}\"`)\n dimensions.add(filter.dimension)\n }\n })\n }\n return errors\n}\n\nfunction fieldsErrors(\n context: string,\n fields: Record<string, FieldSpec>,\n values: Record<string, unknown>,\n extraKeys: string[] = []\n) {\n const errors: string[] = []\n for (const [key, spec] of Object.entries(fields)) {\n const error = fieldError(spec, values[key])\n if (error) errors.push(`${context}: \\`${key}\\` ${error}`)\n }\n const known = new Set([...extraKeys, ...Object.keys(fields)])\n for (const key of Object.keys(values))\n if (!known.has(key)) errors.push(`${context}: campo desconhecido \\`${key}\\``)\n return errors\n}\n\n/** Dimensões, medidas e formatters que o gráfico referencia (lidos pelos campos do tipo). */\nexport const chartReferences = (chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) =>\n fieldReferences(types[chart.type]?.fields ?? {}, chart)\n\n/** Idem para um filtro da página. */\nexport const filterReferences = (filter: FilterDef) => fieldReferences(FILTER_FIELDS, filter)\n\nfunction fieldReferences(fields: Record<string, FieldSpec>, item: object) {\n const refs = { dimensions: [] as string[], measures: [] as string[], formatters: [] as string[] }\n const values = item as Record<string, unknown>\n for (const [key, spec] of Object.entries(fields)) {\n const value = values[key]\n if (value === undefined || value === '') continue\n if (spec.kind === 'dimension') refs.dimensions.push(value as string)\n if (spec.kind === 'dimensions') refs.dimensions.push(...(value as string[]))\n if (spec.kind === 'measure') refs.measures.push(value as string)\n if (spec.kind === 'measures') refs.measures.push(...(value as string[]))\n if (spec.kind === 'formatter') refs.formatters.push(value as string)\n if (spec.kind === 'formatters') refs.formatters.push(...Object.values(value as object))\n }\n return refs\n}\n\n/**\n * Colunas que o gráfico mostra (para `sortBy`, rótulos): as dimensões e medidas escolhidas nele e os\n * percentuais (`%:medida`), na ordem do campo `columnOrder` (se o tipo tiver) — ver `tableColumns`.\n */\nexport function chartColumns(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\n const values = chart as Record<string, unknown>\n const columns: string[] = []\n const percents: string[] = []\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n const value = values[key]\n if (value === undefined || value === '') continue\n if (spec.kind === 'dimension' || spec.kind === 'measure') columns.push(value as string)\n if (spec.kind === 'dimensions') columns.push(...(value as string[]))\n if (spec.kind === 'measures') (spec.percent ? percents : columns).push(...(value as string[]))\n }\n return tableColumns([...new Set(columns)], [...new Set(percents)], columnOrderOf(chart, types))\n}\n\n/** Prefixo da coluna de % do total de uma medida no `columnOrder` (`%:custoTotal`). */\nexport const PERCENT_PREFIX = '%:'\nexport const percentColumn = (measure: string) => `${PERCENT_PREFIX}${measure}`\n\n/**\n * Ordem das colunas: as de `order` primeiro, o resto depois, na ordem em que veio (`orderedColumns`).\n * O % de uma medida que não está em `order` vem logo depois da medida (ou no fim, se ela não é\n * coluna) — layouts sem `%:` no `columnOrder` continuam como antes.\n */\nexport function tableColumns(columns: string[], percentOf: string[], order?: string[]) {\n const percents = percentOf.map(percentColumn)\n const result = orderedColumns(\n [...columns, ...percents.filter((column) => order?.includes(column))],\n order\n )\n for (const measure of percentOf) {\n const column = percentColumn(measure)\n if (result.includes(column)) continue\n const at = result.indexOf(measure)\n if (at === -1) result.push(column)\n else result.splice(at + 1, 0, column)\n }\n return result\n}\n\n/** Valor do campo `columnOrder` do gráfico (o tipo pode não ter). */\nexport function columnOrderOf(chart: ChartDef, types: ChartTypes = BUILTIN_CHART_TYPES) {\n const values = chart as Record<string, unknown>\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {}))\n if (spec.kind === 'columnOrder' && Array.isArray(values[key])) return values[key] as string[]\n return undefined\n}\n\n/** `columns` na ordem de `order`; os que não estão nela vão depois, na ordem em que vieram. */\nexport function orderedColumns(columns: string[], order: string[] | undefined) {\n if (!order?.length) return columns\n const first = order.filter((name) => columns.includes(name))\n return [...first, ...columns.filter((name) => !first.includes(name))]\n}\n\n/**\n * Dimensões/medidas que o layout usa e o modelo não tem. Não impede salvar (o modelo pode estar\n * sendo editado junto), mas o gráfico vai mostrar erro.\n */\nexport function layoutModelWarnings(\n layout: ChartsLayout,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): string[] {\n const warnings: string[] = []\n for (const chart of layout.charts) {\n const name = `\"${chart.title || chart.id}\"`\n const { dimensions, measures } = chartReferences(chart, types)\n for (const dimension of dimensions)\n if (!model.dimensions[dimension]) warnings.push(`${name}: dimensão \"${dimension}\" não existe`)\n for (const measure of measures)\n if (!model.measures[measure]) warnings.push(`${name}: medida \"${measure}\" não existe`)\n if (isBuiltinChart(chart) && chart.type === 'bar' && chart.series) {\n if (chart.series === chart.dimension)\n warnings.push(`${name}: a série é a própria dimensão das categorias`)\n if (chart.measures.length > 1)\n warnings.push(`${name}: com série, só a primeira medida (\"${chart.measures[0]}\") é usada`)\n }\n // Campos `column` (ex.: `sortBy`) e as chaves de `labels`/`formats` precisam ser colunas\n // escolhidas no gráfico\n const columns = chartColumns(chart, types)\n for (const [key, spec] of Object.entries(types[chart.type]?.fields ?? {})) {\n const value = (chart as Record<string, unknown>)[key]\n if (spec.kind === 'column' && isName(value) && !columns.includes(value))\n warnings.push(\n spec.outside\n ? isModelField(model, value)\n ? ''\n : `${name}: \"${value}\" (${key}) não existe no modelo`\n : `${name}: \"${value}\" (${key}) não é coluna do gráfico`\n )\n // Cores por medida (sem a dimensão de valores preenchida): a chave tem que ser medida do gráfico\n if (\n spec.kind === 'colors' &&\n isRecord(value) &&\n !(spec.values && (chart as Record<string, unknown>)[spec.values])\n )\n for (const series of Object.keys(value))\n if (!measures.includes(series))\n warnings.push(`${name}: cor de \"${series}\" (${key}), que não é medida do gráfico`)\n if (spec.kind === 'formatRules' && Array.isArray(value))\n value.forEach((rule: unknown, index) => {\n if (!isRecord(rule)) return\n const context = `${name}: regra ${index + 1} (${key})`\n if (\n isName(rule.field) &&\n !columns.includes(rule.field) &&\n !isModelField(model, rule.field)\n )\n warnings.push(`${context}: \"${rule.field}\" não existe no modelo`)\n const painted = rule.target === 'row' ? undefined : (rule.column ?? rule.field)\n if (isName(painted) && !columns.includes(painted))\n warnings.push(`${context}: \"${painted}\" não é coluna do gráfico (nada é pintado)`)\n })\n if (\n (spec.kind === 'labels' ||\n spec.kind === 'formats' ||\n spec.kind === 'aligns' ||\n spec.kind === 'verticalAligns') &&\n isRecord(value)\n )\n for (const column of Object.keys(value))\n if (!columns.includes(column))\n warnings.push(`${name}: \"${column}\" (${key}) não é coluna do gráfico`)\n if (spec.kind === 'columnOrder' && Array.isArray(value))\n for (const column of value)\n if (isName(column) && !columns.includes(column))\n warnings.push(`${name}: \"${column}\" (${key}) não é coluna do gráfico`)\n }\n }\n for (const filter of layout.filters ?? [])\n if (!model.dimensions[filter.dimension])\n warnings.push(\n `filtro \"${filter.label || filter.dimension}\": dimensão \"${filter.dimension}\" não existe`\n )\n return warnings.filter(Boolean)\n}\n\nconst isModelField = (model: Pick<ModelDef, 'dimensions' | 'measures'>, name: string) =>\n Object.hasOwn(model.dimensions, name) || Object.hasOwn(model.measures, name)\n\n/**\n * Gráfico novo de um tipo, reaproveitando o que der do anterior (troca de tipo no devtools) ou\n * escolhendo dimensão/medidas do modelo.\n */\nexport function chartOfType(\n type: string,\n model: Pick<ModelDef, 'dimensions' | 'measures'>,\n base: Pick<BaseChartDef, 'id' | 'title' | 'span'> & Partial<ChartDef>,\n types: ChartTypes = BUILTIN_CHART_TYPES\n): ChartDef {\n const { id, title, span, card, labels, formats } = base as BaseChartDef & ColumnsDisplay\n const previous = chartReferences(base as ChartDef, types)\n const measures = Object.entries(model.measures)\n const firstSum = measures.find(([, measure]) => isAggregate(measure))?.[0]\n const firstRatio = measures.find(([, measure]) => measure.type === 'ratio')?.[0]\n const dimension = previous.dimensions[0] ?? Object.keys(model.dimensions)[0] ?? ''\n const measure = previous.measures[0] ?? firstSum ?? measures[0]?.[0] ?? ''\n // Rótulos e formatos seguem se o tipo novo tiver os campos (as chaves que sobrarem viram aviso)\n const fields = types[type]?.fields ?? {}\n const shared = {\n id,\n title,\n ...(span !== undefined && { span }),\n ...(card && { card }),\n ...(labels && fields.labels?.kind === 'labels' && { labels }),\n ...(formats && fields.formats?.kind === 'formats' && { formats })\n }\n switch (type) {\n case 'kpi':\n return {\n ...shared,\n type,\n measure,\n ...(previous.measures[1] && { secondary: previous.measures[1] })\n }\n case 'combo':\n return {\n ...shared,\n type,\n dimension,\n bar: measure,\n line: previous.measures[1] ?? firstRatio ?? measure\n }\n case 'bar':\n return {\n ...shared,\n type,\n dimension,\n measures: previous.measures.length ? previous.measures : [measure]\n }\n case 'donut':\n return { ...shared, type, dimension, measure }\n case 'table':\n return {\n ...shared,\n type,\n dimensions: previous.dimensions.length ? previous.dimensions : [dimension],\n measures: previous.measures.length ? previous.measures : [measure]\n }\n }\n\n // Tipo do app: preenche os campos obrigatórios, na ordem, com o que já estava escolhido\n const chart: CustomChartDef = { ...shared, type }\n const used = { dimensions: 0, measures: 0 }\n for (const [key, spec] of Object.entries(fields)) {\n if (!spec.required || key === 'title' || key === 'span') continue\n switch (spec.kind) {\n case 'dimension':\n chart[key] = previous.dimensions[used.dimensions++] ?? dimension\n break\n case 'measure':\n chart[key] = previous.measures[used.measures++] ?? measure\n break\n case 'dimensions':\n chart[key] = previous.dimensions.length ? previous.dimensions : [dimension]\n break\n case 'measures':\n chart[key] = previous.measures.length ? previous.measures : [measure]\n break\n case 'enum':\n chart[key] = spec.default ?? spec.values[0]\n break\n case 'integer':\n chart[key] = spec.default ?? spec.min\n break\n case 'boolean':\n chart[key] = spec.default ?? false\n break\n case 'text':\n case 'color':\n if (spec.default !== undefined) chart[key] = spec.default\n break\n }\n }\n return chart\n}\n\n/** Conteúdo do arquivo `<id>.json`. */\nexport function layoutFileContent(layout: ChartsLayout) {\n return `${JSON.stringify(layout, null, 2)}\\n`\n}\n"],"mappings":"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"}
@@ -101,8 +101,10 @@ type DiagramPosition = {
101
101
  /** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */
102
102
  type ModelMeta = {
103
103
  name: string;
104
+ /** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */
104
105
  dimensions: Record<string, FieldMeta & {
105
106
  type: DimensionDef['type'];
107
+ table?: string;
106
108
  }>;
107
109
  measures: Record<string, MeasureMeta & {
108
110
  type: MeasureDef['type'];
@@ -374,19 +376,47 @@ type ComboChartDef = BaseChartDef & ColumnsDisplay & {
374
376
  type BarChartDef = BaseChartDef & ColumnsDisplay & {
375
377
  type: 'bar';
376
378
  dimension: string;
377
- /** Uma série por medida. */
379
+ /** Uma série por medida (ou, com `series`, só a primeira medida). */
378
380
  measures: string[];
381
+ /**
382
+ * Dimensão da legenda: quebra a medida pelos valores dela, uma série por valor (a "Legenda" do
383
+ * Power BI). Usa só a primeira medida.
384
+ */
385
+ series?: string;
386
+ formatSeries?: string;
387
+ /** Séries além dessa quantidade viram "Outros" (com `series`). */
388
+ maxSeries?: number;
379
389
  orientation?: 'vertical' | 'horizontal';
380
390
  stacked?: boolean;
381
- /** `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida. */
391
+ /**
392
+ * `dimension` = ordem da dimensão; `desc`/`asc` = pela primeira medida (com `series`, pelo
393
+ * total da barra).
394
+ */
382
395
  sort?: 'dimension' | 'desc' | 'asc';
383
396
  /** Só os N primeiros depois de ordenar. */
384
397
  limit?: number;
385
398
  formatDimension?: string;
386
399
  clickToFilter?: boolean;
387
400
  hideEmpty?: boolean;
401
+ /** Todos os valores da dimensão no eixo, com ou sem dado (sobrepõe `hideEmpty`). */
402
+ showEmpty?: boolean;
388
403
  showValues?: boolean;
404
+ /** Onde fica o valor de cada barra. Padrão: `outside` (na ponta, fora da barra). */
405
+ valuePosition?: BarValuePosition;
406
+ /** Cor do valor. Padrão: escuro fora da barra, branco dentro. */
407
+ valueColor?: string;
408
+ /** Tamanho do valor, em px. Padrão: 12. */
409
+ valueSize?: number;
410
+ /** Empilhado: total da pilha na ponta. */
411
+ showTotal?: boolean;
412
+ /**
413
+ * Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também
414
+ * "(em branco)" e "Outros"). O que faltar segue a paleta.
415
+ */
416
+ colors?: Record<string, string>;
389
417
  };
418
+ declare const BAR_VALUE_POSITIONS: readonly ['outside', 'insideEnd', 'center', 'insideBase'];
419
+ type BarValuePosition = (typeof BAR_VALUE_POSITIONS)[number];
390
420
  type DonutChartDef = BaseChartDef & ColumnsDisplay & {
391
421
  type: 'donut';
392
422
  dimension: string;
@@ -512,7 +542,7 @@ type FieldSpec = {
512
542
  label: string;
513
543
  hint?: string;
514
544
  /** Agrupamento no formulário do devtools. */
515
- section: 'Gráfico' | 'Dados' | 'Aparência' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
545
+ section: 'Gráfico' | 'Dados' | 'Aparência' | 'Valores' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
516
546
  required?: boolean;
517
547
  /** Valor que o componente usa quando o campo não está no JSON. */
518
548
  default?: unknown;
@@ -545,9 +575,13 @@ type FieldSpec = {
545
575
  kind: 'measures';
546
576
  percent?: boolean;
547
577
  } |
548
- /** Nome de um formatter do `<DashboardCharts formatters>`. */
578
+ /**
579
+ * Nome de um formatter do `<DashboardCharts formatters>`. `of`: campo `dimension` que ele formata
580
+ * (o devtools edita no ⚙ daquela dimensão); dispensável com uma dimensão e um formatter só.
581
+ */
549
582
  {
550
583
  kind: 'formatter';
584
+ of?: string;
551
585
  } |
552
586
  /** `{ dimensão: formatter }`. */
553
587
  {
@@ -587,6 +621,14 @@ type FieldSpec = {
587
621
  /** Regras de formatação condicional (`FormatRule[]`). */
588
622
  {
589
623
  kind: 'formatRules';
624
+ } |
625
+ /**
626
+ * `{ série: '#rrggbb' }`: uma cor por medida do gráfico ou, quando o campo `dimension` em
627
+ * `values` está preenchido, por valor dessa dimensão (o devtools não lista os valores; digita).
628
+ */
629
+ {
630
+ kind: 'colors';
631
+ values?: string;
590
632
  });
591
633
  /**
592
634
  * Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e
@@ -631,5 +673,5 @@ declare function validateLayout(layout: ChartsLayout, types?: ChartTypes): strin
631
673
  */
632
674
  declare function layoutModelWarnings(layout: ChartsLayout, model: Pick<ModelDef, 'dimensions' | 'measures'>, types?: ChartTypes): string[];
633
675
  //#endregion
634
- export { TableDef as $, DashboardQueryDebug as A, SchemaTable as B, ColumnVerticalAlign as C, DashboardDevtoolsRequest as D, DashboardBatchResult as E, FilterValue as F, FormulaMeasure as G, Aggregation as H, ModelColumns as I, ModelDef as J, MeasureDef as K, ModelValidation as L, DashboardRow as M, DashboardSnapshot as N, DashboardFilters as O, DevtoolsCapabilities as P, SumMeasure as Q, ProfileStep as R, ColumnAlign as S, DashboardBatchQuery as T, DimensionDef as U, AggregateMeasure as V, FieldMeta as W, RatioMeasure as X, ModelMeta as Y, RelationshipDef as Z, KpiChartDef as _, CardsStyle as a, resolveChartTypes as b, ChartType as c, ChartsLayout as d, ValueFormat as et, ComboChartDef as f, FilterDef as g, FieldSpec as h, CardStyle as i, DashboardResult as j, DashboardQuery as k, ChartTypeSpec as l, DonutChartDef as m, BuiltinChartDef as n, validateModel as nt, ChartDef as o, CustomChartDef as p, MeasureMeta as q, CanvasDef as r, ChartNumberFormat as s, BarChartDef as t, modelMeta as tt, ChartTypes as u, TableChartDef as v, FormatRule as w, validateLayout as x, layoutModelWarnings as y, SaveModelResult as z };
635
- //# sourceMappingURL=layout-Bn1OiRov.d.ts.map
676
+ export { SumMeasure as $, DashboardQuery as A, SaveModelResult as B, ColumnAlign as C, DashboardBatchResult as D, DashboardBatchQuery as E, DevtoolsCapabilities as F, FieldMeta as G, AggregateMeasure as H, FilterValue as I, MeasureMeta as J, FormulaMeasure as K, ModelColumns as L, DashboardResult as M, DashboardRow as N, DashboardDevtoolsRequest as O, DashboardSnapshot as P, RelationshipDef as Q, ModelValidation as R, validateLayout as S, FormatRule as T, Aggregation as U, SchemaTable as V, DimensionDef as W, ModelMeta as X, ModelDef as Y, RatioMeasure as Z, FilterDef as _, CardStyle as a, layoutModelWarnings as b, ChartNumberFormat as c, ChartTypes as d, TableDef as et, ChartsLayout as f, FieldSpec as g, DonutChartDef as h, CanvasDef as i, DashboardQueryDebug as j, DashboardFilters as k, ChartType as l, CustomChartDef as m, BarValuePosition as n, modelMeta as nt, CardsStyle as o, ComboChartDef as p, MeasureDef as q, BuiltinChartDef as r, validateModel as rt, ChartDef as s, BarChartDef as t, ValueFormat as tt, ChartTypeSpec as u, KpiChartDef as v, ColumnVerticalAlign as w, resolveChartTypes as x, TableChartDef as y, ProfileStep as z };
677
+ //# sourceMappingURL=layout-DsG-zRIO.d.ts.map
@@ -1,4 +1,4 @@
1
- import { $ as TableDef, G as FormulaMeasure, H as Aggregation, J as ModelDef, K as MeasureDef, M as DashboardRow, Q as SumMeasure, U as DimensionDef, V as AggregateMeasure, W as FieldMeta, X as RatioMeasure, Z as RelationshipDef, b as resolveChartTypes, d as ChartsLayout, et as ValueFormat, j as DashboardResult, k as DashboardQuery, l as ChartTypeSpec, nt as validateModel, q as MeasureMeta, tt as modelMeta, x as validateLayout, y as layoutModelWarnings } from "../layout-Bn1OiRov.js";
1
+ import { $ as SumMeasure, A as DashboardQuery, G as FieldMeta, H as AggregateMeasure, J as MeasureMeta, K as FormulaMeasure, M as DashboardResult, N as DashboardRow, Q as RelationshipDef, S as validateLayout, U as Aggregation, W as DimensionDef, Y as ModelDef, Z as RatioMeasure, b as layoutModelWarnings, et as TableDef, f as ChartsLayout, nt as modelMeta, q as MeasureDef, rt as validateModel, tt as ValueFormat, u as ChartTypeSpec, x as resolveChartTypes } from "../layout-DsG-zRIO.js";
2
2
  //#region src/codegen.d.ts
3
3
  /** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */
4
4
  declare function canonicalJson(value: unknown): string;
@@ -1,6 +1,6 @@
1
- import { _ as reachFrom, a as toModelCode, d as modelMeta, f as validateModel, g as parseColumnRef, h as modelEdges, i as modelObjectCode, l as aggregateSql, m as measureHome, o as AGGREGATIONS, p as joinPlan, r as modelImports, t as canonicalJson, u as isAggregate, v as referencedTables } from "../codegen-DkfI6UJr.js";
1
+ import { _ as reachFrom, a as toModelCode, d as modelMeta, f as validateModel, g as parseColumnRef, h as modelEdges, i as modelObjectCode, l as aggregateSql, m as measureHome, o as AGGREGATIONS, p as joinPlan, r as modelImports, t as canonicalJson, u as isAggregate, v as referencedTables } from "../codegen-LoQzFAle.js";
2
2
  import { n as diffRows, t as describeDiffs } from "../compare-DloZhg1T.js";
3
- import { g as resolveChartTypes, m as layoutModelWarnings, p as layoutFileContent, v as validateLayout } from "../layout-DAFeHIuP.js";
3
+ import { g as resolveChartTypes, m as layoutModelWarnings, p as layoutFileContent, v as validateLayout } from "../layout-CBzYDoqE.js";
4
4
  //#region src/server/compile.ts
5
5
  var DashboardQueryError = class extends Error {};
6
6
  const MAX_FILTER_VALUES = 1e3;
package/dist/types.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as TableDef, A as DashboardQueryDebug, B as SchemaTable, D as DashboardDevtoolsRequest, E as DashboardBatchResult, F as FilterValue, G as FormulaMeasure, I as ModelColumns, J as ModelDef, K as MeasureDef, L as ModelValidation, M as DashboardRow, N as DashboardSnapshot, O as DashboardFilters, P as DevtoolsCapabilities, R as ProfileStep, T as DashboardBatchQuery, U as DimensionDef, W as FieldMeta, Y as ModelMeta, Z as RelationshipDef, _ as KpiChartDef, a as CardsStyle, c as ChartType, d as ChartsLayout, et as ValueFormat, f as ComboChartDef, g as FilterDef, h as FieldSpec, i as CardStyle, j as DashboardResult, k as DashboardQuery, l as ChartTypeSpec, m as DonutChartDef, n as BuiltinChartDef, o as ChartDef, p as CustomChartDef, q as MeasureMeta, r as CanvasDef, s as ChartNumberFormat, t as BarChartDef, u as ChartTypes, v as TableChartDef, z as SaveModelResult } from "./layout-Bn1OiRov.js";
1
+ import { A as DashboardQuery, B as SaveModelResult, D as DashboardBatchResult, E as DashboardBatchQuery, F as DevtoolsCapabilities, G as FieldMeta, I as FilterValue, J as MeasureMeta, K as FormulaMeasure, L as ModelColumns, M as DashboardResult, N as DashboardRow, O as DashboardDevtoolsRequest, P as DashboardSnapshot, Q as RelationshipDef, R as ModelValidation, V as SchemaTable, W as DimensionDef, X as ModelMeta, Y as ModelDef, _ as FilterDef, a as CardStyle, c as ChartNumberFormat, d as ChartTypes, et as TableDef, f as ChartsLayout, g as FieldSpec, h as DonutChartDef, i as CanvasDef, j as DashboardQueryDebug, k as DashboardFilters, l as ChartType, m as CustomChartDef, o as CardsStyle, p as ComboChartDef, q as MeasureDef, r as BuiltinChartDef, s as ChartDef, t as BarChartDef, tt as ValueFormat, u as ChartTypeSpec, v as KpiChartDef, y as TableChartDef, z as ProfileStep } from "./layout-DsG-zRIO.js";
2
2
  export { type BarChartDef, type BuiltinChartDef, type CanvasDef, type CardStyle, type CardsStyle, type ChartDef, type ChartNumberFormat, type ChartType, type ChartTypeSpec, type ChartTypes, type ChartsLayout, type ComboChartDef, type CustomChartDef, DashboardBatchQuery, DashboardBatchResult, DashboardDevtoolsRequest, DashboardFilters, DashboardQuery, DashboardQueryDebug, DashboardResult, DashboardRow, DashboardSnapshot, DevtoolsCapabilities, type DimensionDef, type DonutChartDef, type FieldMeta, type FieldSpec, type FilterDef, FilterValue, type FormulaMeasure, type KpiChartDef, type MeasureDef, type MeasureMeta, ModelColumns, type ModelDef, type ModelMeta, ModelValidation, ProfileStep, type RelationshipDef, SaveModelResult, SchemaTable, type TableChartDef, type TableDef, type ValueFormat };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluemarble/bi",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Camada semântica (defineModel) + componentes de dashboard e devtools para substituir o Power BI",
5
5
  "license": "MIT",
6
6
  "author": "bluemarble",