diff --git a/packages/ui-dashboard/package.json b/packages/ui-dashboard/package.json
index 18a24d5..a89905c 100644
--- a/packages/ui-dashboard/package.json
+++ b/packages/ui-dashboard/package.json
@@ -1,7 +1,7 @@
{
"private": false,
"name": "@sentio/ui-dashboard",
- "version": "0.2.0",
+ "version": "0.2.1",
"description": "Dashboard UI components for the Sentio platform, decoupled from backend services.",
"exports": {
".": {
diff --git a/packages/ui-dashboard/src/charts/index.ts b/packages/ui-dashboard/src/charts/index.ts
index 8e52abd..57dd2d0 100644
--- a/packages/ui-dashboard/src/charts/index.ts
+++ b/packages/ui-dashboard/src/charts/index.ts
@@ -14,6 +14,9 @@ export { ChartLegend } from './ChartLegend'
// Refresh affordance
export { RefreshContext, RefreshButton } from './RefreshContext'
+// Option panels
+export * from './options'
+
// Theme
export { sentioColors } from './theme/sentio-colors'
export { sentioTheme, sentioThemeDark } from './theme/sentio-theme'
diff --git a/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx
new file mode 100644
index 0000000..ed9d0d9
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/BarGaugeControls.tsx
@@ -0,0 +1,163 @@
+import { produce } from 'immer'
+import { defaults } from 'lodash'
+import { DisclosurePanel } from '@sentio/ui-core'
+import type {
+ BarGaugeConfigLike,
+ CalculationLike,
+ DirectionLike,
+ SortByLike
+} from '../../types'
+
+interface Props {
+ config?: BarGaugeConfigLike
+ defaultOpen?: boolean
+ onChange: (config: BarGaugeConfigLike) => void
+}
+
+export const defaultConfig: BarGaugeConfigLike = {
+ direction: 'HORIZONTAL',
+ calculation: 'LAST',
+ sort: {
+ sortBy: 'ByName',
+ orderDesc: true
+ }
+}
+
+const directionItems = [
+ { label: 'Horizontal', value: 'HORIZONTAL' },
+ { label: 'Vertical', value: 'VERTICAL' }
+]
+
+const CalculationItems = [
+ { label: 'Last', value: 'LAST' },
+ { label: 'First', value: 'FIRST' },
+ { label: 'Total', value: 'TOTAL' },
+ { label: 'Mean', value: 'MEAN' },
+ { label: 'Max', value: 'MAX' },
+ { label: 'Min', value: 'MIN' }
+]
+
+const sortByItems = [
+ { label: 'Name', value: 'ByName' },
+ { label: 'Value', value: 'ByValue' }
+]
+
+const orderItems = [
+ { label: 'Ascendant', value: 'false' },
+ { label: 'Descendant', value: 'true' }
+]
+
+export function BarGaugeControls({ config, defaultOpen, onChange }: Props) {
+ config = defaults(config, defaultConfig)
+
+ function onCalculationChange(cal: CalculationLike) {
+ config &&
+ onChange(produce(config, (draft) => void (draft.calculation = cal)))
+ }
+
+ function onDirectionChange(dir: DirectionLike) {
+ config && onChange(produce(config, (draft) => void (draft.direction = dir)))
+ }
+
+ function onOrderChange(orderDesc: boolean) {
+ config &&
+ onChange(
+ produce(config, (draft) => {
+ draft.sort = draft.sort || {}
+ draft.sort.orderDesc = orderDesc
+ })
+ )
+ }
+
+ function onSortByChange(sortBy: SortByLike) {
+ config &&
+ onChange(
+ produce(config, (draft) => {
+ draft.sort = draft.sort || {}
+ draft.sort.sortBy = sortBy
+ })
+ )
+ }
+
+ return (
+
+
+
+
+ Direction
+
+
+
+
+
+
+ Calculation
+
+
+
+
+
+
+ Sort by
+
+
+
+
+
+
+ )
+}
diff --git a/packages/ui-dashboard/src/charts/options/LabelControls.tsx b/packages/ui-dashboard/src/charts/options/LabelControls.tsx
new file mode 100644
index 0000000..b194e86
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/LabelControls.tsx
@@ -0,0 +1,108 @@
+import { useEffect, useMemo } from 'react'
+import { produce } from 'immer'
+import { Button as NewButton, DisclosurePanel, HelpIcon } from '@sentio/ui-core'
+import type { LabelConfigLike } from '../../types'
+
+interface Props {
+ config?: LabelConfigLike
+ setConfig: (value: LabelConfigLike) => void
+ defaultOpen?: boolean
+}
+
+const initialConfig: LabelConfigLike = {
+ columns: [],
+ alias: ''
+}
+
+export const LabelControls = ({ config, setConfig, defaultOpen }: Props) => {
+ // Migrate existing columns config to alias on component mount
+ useEffect(() => {
+ if (config?.columns && config.columns.length > 0 && !config.alias) {
+ const aliasParts: string[] = []
+ config.columns.forEach((colConfig) => {
+ if (!colConfig.name) return // Skip if name is undefined
+
+ if (colConfig.showLabel === false && colConfig.showValue === false) {
+ // ignore
+ } else if (colConfig.showValue === false) {
+ aliasParts.push(colConfig.name)
+ } else {
+ aliasParts.push(`{{${colConfig.name}}}`)
+ }
+ })
+
+ if (aliasParts.length > 0) {
+ const migratedAlias = aliasParts.join(', ')
+ setConfig(
+ produce(config, (draft) => {
+ draft.alias = migratedAlias
+ draft.columns = [] // Clear the old columns config
+ })
+ )
+ }
+ }
+ }, [config, setConfig])
+
+ const onAliasChanged = (alias: string) => {
+ setConfig(
+ produce(config ?? initialConfig, (draft) => {
+ draft.alias = alias
+ })
+ )
+ }
+
+ const _defaultOpen = useMemo(() => {
+ if (defaultOpen) {
+ return true
+ }
+ return (
+ config?.alias !== '' || (config?.columns && config.columns.length > 0)
+ )
+ }, [config, defaultOpen])
+
+ return (
+
+
+
+
+ Label Alias
+
+ Series name override or template.
+
+ Ex.{' '}
+
+ {'{{contract}}'}
+ {' '}
+ will be replaced with the value of the contract label.
+
+
+ }
+ />
+
+
onAliasChanged(e.target.value)}
+ placeholder="Enter alias..."
+ className="focus:border-primary-500 sm:text-icontent border-main inline-flex w-64 items-center rounded-r-md border px-2"
+ />
+
+ {
+ setConfig(initialConfig)
+ }}
+ >
+ Reset
+
+
+
+ )
+}
diff --git a/packages/ui-dashboard/src/charts/options/LineControls.tsx b/packages/ui-dashboard/src/charts/options/LineControls.tsx
new file mode 100644
index 0000000..462be8d
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/LineControls.tsx
@@ -0,0 +1,54 @@
+import { produce } from 'immer'
+import {
+ DisclosurePanel,
+ NewButtonGroup as ButtonGroup,
+ Checkbox
+} from '@sentio/ui-core'
+import type { LineConfigLike, LineStyleLike } from '../../types'
+
+const lineStyles: { label: string; value: LineStyleLike }[] = [
+ { label: 'Solid', value: 'Solid' },
+ { label: 'Dotted', value: 'Dotted' }
+]
+
+interface Props {
+ config?: LineConfigLike
+ defaultOpen?: boolean
+ onChange: (config: LineConfigLike) => void
+}
+
+export const LineControls = ({ config, defaultOpen, onChange }: Props) => {
+ const setStyle = (style: LineStyleLike) => {
+ onChange(
+ produce(config || {}, (draft) => {
+ draft.style = style
+ })
+ )
+ }
+ const setSmooth = (smooth: boolean) => {
+ onChange(
+ produce(config || {}, (draft) => {
+ draft.smooth = smooth
+ })
+ )
+ }
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx
new file mode 100644
index 0000000..253d7f5
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx
@@ -0,0 +1,82 @@
+import type { Story } from '@ladle/react'
+import { useState } from 'react'
+import { LineControls } from './LineControls'
+import { LabelControls } from './LabelControls'
+import { PieChartControls } from './PieChartControls'
+import { BarGaugeControls } from './BarGaugeControls'
+import type {
+ LineConfigLike,
+ LabelConfigLike,
+ PieConfigLike,
+ BarGaugeConfigLike
+} from '../../types'
+
+function Frame({
+ children,
+ value
+}: {
+ children: React.ReactNode
+ value: unknown
+}) {
+ return (
+
+ {children}
+
+ {JSON.stringify(value, null, 2)}
+
+
+ )
+}
+
+export const Line: Story = () => {
+ const [config, setConfig] = useState({
+ style: 'Solid',
+ smooth: false
+ })
+ return (
+
+
+
+ )
+}
+Line.meta = { description: 'Line style options' }
+
+export const Label: Story = () => {
+ const [config, setConfig] = useState({
+ alias: '{{contract}}',
+ columns: []
+ })
+ return (
+
+
+
+ )
+}
+Label.meta = { description: 'Series label alias/template' }
+
+export const Pie: Story = () => {
+ const [config, setConfig] = useState({
+ pieType: 'Pie',
+ calculation: 'LAST',
+ showValue: true
+ })
+ return (
+
+
+
+ )
+}
+Pie.meta = { description: 'Pie/donut options' }
+
+export const BarGauge: Story = () => {
+ const [config, setConfig] = useState({
+ direction: 'HORIZONTAL',
+ calculation: 'LAST'
+ })
+ return (
+
+
+
+ )
+}
+BarGauge.meta = { description: 'Bar-gauge direction/calculation/sort options' }
diff --git a/packages/ui-dashboard/src/charts/options/PieChartControls.tsx b/packages/ui-dashboard/src/charts/options/PieChartControls.tsx
new file mode 100644
index 0000000..2209d6f
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/PieChartControls.tsx
@@ -0,0 +1,119 @@
+import { produce } from 'immer'
+import { defaults } from 'lodash'
+import {
+ Checkbox,
+ DisclosurePanel,
+ NewButtonGroup as ButtonGroup
+} from '@sentio/ui-core'
+import type { CalculationLike, PieConfigLike, PieTypeLike } from '../../types'
+
+interface Props {
+ config?: PieConfigLike
+ defaultOpen?: boolean
+ onChange: (config: PieConfigLike) => void
+}
+
+export const defaultConfig: PieConfigLike = {
+ pieType: 'Pie',
+ calculation: 'LAST',
+ showPercent: true,
+ showValue: true,
+ absValue: false
+}
+
+const CalculationItems = [
+ { label: 'Last', value: 'LAST' },
+ { label: 'First', value: 'FIRST' },
+ { label: 'Total', value: 'TOTAL' },
+ { label: 'Mean', value: 'MEAN' },
+ { label: 'Max', value: 'MAX' },
+ { label: 'Min', value: 'MIN' }
+]
+
+const PieTypeItems: { label: string; value: PieTypeLike }[] = [
+ { label: 'Pie', value: 'Pie' },
+ { label: 'Donut', value: 'Donut' }
+]
+
+export function PieChartControls({ config, defaultOpen, onChange }: Props) {
+ config = defaults(config, defaultConfig)
+
+ function onCalculationChange(cal: CalculationLike) {
+ config &&
+ onChange(produce(config, (draft) => void (draft.calculation = cal)))
+ }
+
+ function onPieTypeChange(pieType: PieTypeLike) {
+ config &&
+ onChange(produce(config, (draft) => void (draft.pieType = pieType)))
+ }
+
+ function toggle(
+ field: 'showValue' | 'showPercent' | 'absValue',
+ value: boolean
+ ) {
+ config &&
+ onChange(
+ produce(config, (draft) => {
+ draft[field] = value
+ })
+ )
+ }
+
+ return (
+
+
+
+
+
+
+
+ Calculation
+
+
+
+
toggle('showValue', v)}
+ label="Show value"
+ labelClassName="whitespace-nowrap"
+ />
+ toggle('showPercent', v)}
+ label="Show percent"
+ labelClassName="whitespace-nowrap"
+ />
+ toggle('absValue', v)}
+ label="Use absolute values"
+ labelClassName="whitespace-nowrap"
+ />
+
+
+ )
+}
diff --git a/packages/ui-dashboard/src/charts/options/index.ts b/packages/ui-dashboard/src/charts/options/index.ts
new file mode 100644
index 0000000..5c2bb8d
--- /dev/null
+++ b/packages/ui-dashboard/src/charts/options/index.ts
@@ -0,0 +1,12 @@
+// Chart option panels (phase 3b). Data-coupled panels (Data/Series/TimeRange/
+// Table/Scatter/Yaxis/Xaxis, value-formatting trio) follow in later slices.
+export { LineControls } from './LineControls'
+export { LabelControls } from './LabelControls'
+export {
+ PieChartControls,
+ defaultConfig as defaultPieConfig
+} from './PieChartControls'
+export {
+ BarGaugeControls,
+ defaultConfig as defaultBarGaugeConfig
+} from './BarGaugeControls'
diff --git a/packages/ui-dashboard/src/types/chart.ts b/packages/ui-dashboard/src/types/chart.ts
index a6ea17c..f60bb9c 100644
--- a/packages/ui-dashboard/src/types/chart.ts
+++ b/packages/ui-dashboard/src/types/chart.ts
@@ -1,9 +1,18 @@
+import type { DurationLike } from '@sentio/ui-core'
import type {
ChartTypeLike,
DataSourceTypeLike,
NoteFontSizeLike,
NoteAlignmentLike,
- NoteVerticalAlignmentLike
+ NoteVerticalAlignmentLike,
+ CalculationLike,
+ DirectionLike,
+ MarkerTypeLike,
+ SortByLike,
+ ValueFormatterLike,
+ ValueStyleLike,
+ PieTypeLike,
+ LineStyleLike
} from './enums'
// Type-only import; the chart <-> dashboard type cycle is erased at compile time.
import type { GroupLike } from './dashboard'
@@ -11,12 +20,18 @@ import type { GroupLike } from './dashboard'
/*
* Chart-level shapes (chart / note / chart config / overlay graph).
*
- * NOTE: ChartConfigLike is intentionally a MINIMAL subset for now. The full set
- * of fields (xAxis, tableConfig, pieConfig, valueConfig, mapping rules, color
- * themes, etc.) is fleshed out alongside the chart-rendering components, adding
- * only the fields those components actually read.
+ * ChartConfigLike and its sub-interfaces are structural mirrors of the proto
+ * `ChartConfig` message tree (all fields optional) — they back the chart render
+ * components and the per-chart option panels. Deep payloads that the option
+ * panels don't edit (e.g. an overridden TimeRange) are kept opaque (`unknown`).
*/
+/** Sort key + direction (x-axis, bar-gauge). */
+export interface SortLike {
+ sortBy?: SortByLike
+ orderDesc?: boolean
+}
+
/** Y-axis configuration. */
export interface YAxisConfigLike {
min?: string
@@ -27,9 +42,171 @@ export interface YAxisConfigLike {
name?: string
}
-/** Minimal subset of a chart's config. Extended alongside chart components. */
+/** X-axis configuration. */
+export interface XAxisConfigLike {
+ type?: string
+ min?: string
+ max?: string
+ scale?: boolean
+ name?: string
+ column?: string
+ sort?: SortLike
+ format?: string
+}
+
+/** Text/background color overrides for value + mapping rules. */
+export interface ColorThemeLike {
+ textColor?: string
+ backgroundColor?: string
+ themeType?: string
+}
+
+/** A value→text/color mapping rule. */
+export interface MappingRuleLike {
+ comparison?: string
+ value?: number | 'NaN' | 'Infinity' | '-Infinity'
+ text?: string
+ colorTheme?: ColorThemeLike
+}
+
+/** Number/string/date value formatting. */
+export interface ValueConfigLike {
+ valueFormatter?: ValueFormatterLike
+ showValueLabel?: boolean
+ maxSignificantDigits?: number
+ dateFormat?: string
+ mappingRules?: MappingRuleLike[]
+ style?: ValueStyleLike
+ maxFractionDigits?: number
+ precision?: number
+ currencySymbol?: string
+ tooltipTotal?: boolean
+ prefix?: string
+ suffix?: string
+}
+
+/** Line chart style. */
+export interface LineConfigLike {
+ style?: LineStyleLike
+ smooth?: boolean
+}
+
+/** Pie/donut config. */
+export interface PieConfigLike {
+ pieType?: PieTypeLike
+ showPercent?: boolean
+ showValue?: boolean
+ calculation?: CalculationLike
+ absValue?: boolean
+}
+
+/** Bar-gauge config. */
+export interface BarGaugeConfigLike {
+ direction?: DirectionLike
+ calculation?: CalculationLike
+ sort?: SortLike
+}
+
+/** Query-value (single big number) config. */
+export interface QueryValueConfigLike {
+ colorTheme?: ColorThemeLike
+ showBackgroundChart?: boolean
+ calculation?: CalculationLike
+ seriesCalculation?: CalculationLike
+}
+
+/** Per-column label config (event/log tables). */
+export interface LabelConfigColumnLike {
+ name?: string
+ showLabel?: boolean
+ showValue?: boolean
+}
+
+/** Series label config. */
+export interface LabelConfigLike {
+ columns?: LabelConfigColumnLike[]
+ alias?: string
+}
+
+/** Scatter chart config. */
+export interface ScatterConfigLike {
+ symbolSize?: string
+ color?: string
+ minSize?: number
+ maxSize?: number
+}
+
+/** A mark line/area on a chart. */
+export interface MarkerLike {
+ type?: MarkerTypeLike
+ value?: number | 'NaN' | 'Infinity' | '-Infinity'
+ color?: string
+ label?: string
+ valueX?: string
+}
+
+/** Series count limit. */
+export interface DataConfigLike {
+ seriesLimit?: number
+}
+
+/** Per-series override (currently just chart type). */
+export interface SeriesConfigSeriesLike {
+ type?: ChartTypeLike
+}
+
+/** Per-series config keyed by series id. */
+export interface SeriesConfigLike {
+ series?: { [key: string]: SeriesConfigSeriesLike }
+}
+
+/** Table column sort entry. */
+export interface ColumnSortLike {
+ column?: string
+ orderDesc?: boolean
+}
+
+/** Table chart config. */
+export interface TableConfigLike {
+ calculation?: CalculationLike
+ showColumns?: { [key: string]: boolean }
+ sortColumns?: ColumnSortLike[]
+ columnOrders?: string[]
+ columnWidths?: { [key: string]: number }
+ showPlainData?: boolean
+ calculations?: { [key: string]: CalculationLike }
+ valueConfigs?: { [key: string]: ValueConfigLike }
+ rowLimit?: number
+}
+
+/** Compare-to-previous-period offset. */
+export interface CompareTimeLike {
+ ago?: DurationLike
+}
+
+/** Per-chart time-range override. `timeRange` payload kept opaque. */
+export interface TimeRangeOverrideLike {
+ enabled?: boolean
+ timeRange?: unknown
+ compareTime?: CompareTimeLike
+}
+
+/** A chart's full config — structural mirror of proto `ChartConfig`. */
export interface ChartConfigLike {
yAxis?: YAxisConfigLike
+ xAxis?: XAxisConfigLike
+ lineConfig?: LineConfigLike
+ valueConfig?: ValueConfigLike
+ pieConfig?: PieConfigLike
+ barGauge?: BarGaugeConfigLike
+ queryValueConfig?: QueryValueConfigLike
+ tableConfig?: TableConfigLike
+ labelConfig?: LabelConfigLike
+ scatterConfig?: ScatterConfigLike
+ seriesConfig?: SeriesConfigLike
+ dataConfig?: DataConfigLike
+ timeRangeOverride?: TimeRangeOverrideLike
+ markers?: MarkerLike[]
}
/** A note (text) panel's content and styling. */
diff --git a/packages/ui-dashboard/src/types/enums.ts b/packages/ui-dashboard/src/types/enums.ts
index e438574..d0e6abd 100644
--- a/packages/ui-dashboard/src/types/enums.ts
+++ b/packages/ui-dashboard/src/types/enums.ts
@@ -47,3 +47,29 @@ export type NoteAlignmentLike = 'LEFT' | 'CENTER' | 'RIGHT'
/** Note panel vertical alignment. */
export type NoteVerticalAlignmentLike = 'TOP' | 'MIDDLE' | 'BOTTOM'
+
+// ── chart-config enums (mirror proto ChartConfig.* enums) ──────────────────
+
+/** Series/value aggregation calculation. */
+export type CalculationLike = 'LAST' | 'FIRST' | 'MEAN' | 'TOTAL' | 'ALL' | 'MIN' | 'MAX'
+
+/** Bar-gauge layout direction. */
+export type DirectionLike = 'HORIZONTAL' | 'VERTICAL'
+
+/** Mark type for chart markers. */
+export type MarkerTypeLike = 'LINE' | 'AREA' | 'LINEX'
+
+/** Sort key for axis/bar ordering. */
+export type SortByLike = 'ByName' | 'ByValue'
+
+/** Value formatter family. */
+export type ValueFormatterLike = 'NumberFormatter' | 'DateFormatter' | 'StringFormatter'
+
+/** Number value display style. */
+export type ValueStyleLike = 'Standard' | 'Compact' | 'Scientific' | 'Percent' | 'Currency' | 'None'
+
+/** Pie vs donut. */
+export type PieTypeLike = 'Pie' | 'Donut'
+
+/** Line style. */
+export type LineStyleLike = 'Solid' | 'Dotted'