diff --git a/packages/ui-dashboard/package.json b/packages/ui-dashboard/package.json index cb57e1a..dd68b15 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.2", + "version": "0.2.3", "description": "Dashboard UI components for the Sentio platform, decoupled from backend services.", "exports": { ".": { diff --git a/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx index 253d7f5..cff82fa 100644 --- a/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx +++ b/packages/ui-dashboard/src/charts/options/OptionPanels.stories.tsx @@ -4,11 +4,13 @@ import { LineControls } from './LineControls' import { LabelControls } from './LabelControls' import { PieChartControls } from './PieChartControls' import { BarGaugeControls } from './BarGaugeControls' +import { ValueControls } from './ValueControls' import type { LineConfigLike, LabelConfigLike, PieConfigLike, - BarGaugeConfigLike + BarGaugeConfigLike, + ValueConfigLike } from '../../types' function Frame({ @@ -80,3 +82,25 @@ export const BarGauge: Story = () => { ) } BarGauge.meta = { description: 'Bar-gauge direction/calculation/sort options' } + +export const Value: Story = () => { + const [config, setConfig] = useState({ + valueFormatter: 'NumberFormatter', + style: 'Standard', + maxFractionDigits: 2 + }) + return ( + + + + ) +} +Value.meta = { + description: 'Value formatter (number/date/string mapping) + prefix/suffix' +} diff --git a/packages/ui-dashboard/src/charts/options/ValueControls.tsx b/packages/ui-dashboard/src/charts/options/ValueControls.tsx new file mode 100644 index 0000000..526f9a9 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/ValueControls.tsx @@ -0,0 +1,77 @@ +import { defaults } from 'lodash' +import { Checkbox, DisclosurePanel } from '@sentio/ui-core' +import { produce } from 'immer' +import { + ValueFormatters, + ValueOptions, + type ValueFormatter +} from './ValueOptions' +import type { ValueConfigLike } from '../../types' + +interface Props { + config?: ValueConfigLike + defaultOpen?: boolean + onChange: (config: ValueConfigLike) => void + formatters?: ValueFormatter[] + showPrefix?: boolean + showSuffix?: boolean +} + +export const defaultConfig: ValueConfigLike = { + valueFormatter: 'NumberFormatter', + showValueLabel: false, + maxSignificantDigits: 3, + dateFormat: 'LLL', + mappingRules: [], + style: 'None', + maxFractionDigits: 2 +} + +export const ValueControls = ({ + config, + defaultOpen, + onChange, + formatters = ValueFormatters, + showPrefix, + showSuffix +}: Props) => { + config = defaults(config || {}, defaultConfig) + function toggleShowValueLabel(checked: boolean) { + config && + onChange( + produce(config, (draft) => void (draft.showValueLabel = checked)) + ) + } + function toggleTooltipTotal(checked: boolean) { + config && + onChange(produce(config, (draft) => void (draft.tooltipTotal = checked))) + } + return ( + + + +
+ + +
+
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/ValueOptions.tsx b/packages/ui-dashboard/src/charts/options/ValueOptions.tsx new file mode 100644 index 0000000..864a516 --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/ValueOptions.tsx @@ -0,0 +1,321 @@ +import { produce } from 'immer' +import { ComboInput, classNames } from '@sentio/ui-core' +import { ValueStringMapping } from './ValueStringMapping' +import type { + MappingRuleLike, + ValueConfigLike, + ValueFormatterLike, + ValueStyleLike +} from '../../types' + +export interface ValueFormatter { + label: string + value: ValueFormatterLike +} + +export const ValueFormatters: ValueFormatter[] = [ + { label: 'Number', value: 'NumberFormatter' }, + { label: 'Date', value: 'DateFormatter' }, + { label: 'String', value: 'StringFormatter' } +] + +interface Props { + config: ValueConfigLike + defaultOpen?: boolean + onChange: (config: ValueConfigLike) => void + formatters?: ValueFormatter[] + showPrefix?: boolean + showSuffix?: boolean +} + +export const defaultConfig: ValueConfigLike = { + valueFormatter: 'NumberFormatter', + showValueLabel: false, + maxSignificantDigits: 3, + dateFormat: 'LLL', + mappingRules: [], + style: 'None' +} + +const dateFormats = [ + { label: 'Localized format', value: 'LLL' }, + { label: 'ISO String', value: 'YYYY-MM-DDTHH:mm:ss.sssZ' } +] + +const CurrencySymbols = [ + { label: 'USD', value: '$' }, + { label: 'EUR', value: '€' }, + { label: 'GBP', value: '£' }, + { label: 'CNY or JPY', value: '¥' }, + { label: 'BTC', value: 'Ƀ' }, + { label: 'ETH', value: 'Ξ' } +] + +export const ValueOptions = ({ + config, + onChange, + formatters = ValueFormatters, + showPrefix, + showSuffix +}: Props) => { + function onChangeDateFormat(f: string) { + onChange(produce(config, (draft) => void (draft.dateFormat = f))) + } + function onChangeFormatter(f: ValueFormatterLike) { + onChange(produce(config, (draft) => void (draft.valueFormatter = f))) + } + function onChangeSymbol(symbol?: string) { + onChange(produce(config, (draft) => void (draft.currencySymbol = symbol))) + } + function onStyleChange(notation: ValueStyleLike) { + onChange( + produce(config, (draft) => { + draft.style = notation + }) + ) + } + function onDigitsChange(value: string, option: string) { + onChange( + produce(config, (draft) => { + const d = draft as Record + if (value) { + const maxSignificantDigits = parseInt(value) + if (maxSignificantDigits >= 0 && maxSignificantDigits <= 20) { + d[option] = maxSignificantDigits + } + } else { + delete d[option] + } + }) + ) + } + + function onMappingRulesChange(rules: MappingRuleLike[]) { + onChange(produce(config, (draft) => void (draft.mappingRules = rules))) + } + + function onPrefixChange(value: string) { + onChange( + produce(config, (draft) => { + if (value) { + draft.prefix = value + } else { + delete draft.prefix + } + }) + ) + } + + function onSuffixChange(value: string) { + onChange( + produce(config, (draft) => { + if (value) { + draft.suffix = value + } else { + delete draft.suffix + } + }) + ) + } + + function numberAddons(style: ValueStyleLike) { + switch (style) { + case 'None': + return ( + <> + + Fraction Digits + + + + ) + case 'Percent': + case 'Standard': + return ( + <> + + Fraction Digits + + + onDigitsChange(e.target.value, 'maxFractionDigits') + } + /> + + ) + case 'Currency': + return ( + <> + + Symbol + +
+ s.value)} + displayFn={(s) => { + const name = CurrencySymbols.find((c) => c.value === s)?.label + return `${name} (${s})` + }} + placeholder={'$'} + value={config?.currencySymbol} + /> +
+ + Precision + + onDigitsChange(e.target.value, 'precision')} + /> + + ) + default: + return ( + <> + + Max significant digits + + + onDigitsChange(e.target.value, 'maxSignificantDigits') + } + /> + + ) + } + } + + return ( + <> +
+
+ + Value formatter + + + {config.valueFormatter == 'NumberFormatter' && ( + <> + + Style + + + {config.style && numberAddons(config.style)} + + )} + {config.valueFormatter == 'DateFormatter' && ( + <> + + Date format + + + + )} +
+
+ + {/* Prefix and Suffix Configuration */} + {(showPrefix || showSuffix) && ( +
+
+ {showPrefix && ( +
+
Prefix
+ onPrefixChange(e.target.value)} + /> +
+ )} + {showSuffix && ( +
+
Suffix
+ onSuffixChange(e.target.value)} + /> +
+ )} +
+
+ )} + + {config.valueFormatter == 'StringFormatter' && ( + + )} + + ) +} diff --git a/packages/ui-dashboard/src/charts/options/ValueStringMapping.tsx b/packages/ui-dashboard/src/charts/options/ValueStringMapping.tsx new file mode 100644 index 0000000..5ae8b4b --- /dev/null +++ b/packages/ui-dashboard/src/charts/options/ValueStringMapping.tsx @@ -0,0 +1,161 @@ +import { LuPlus, LuTrash2 } from 'react-icons/lu' +import { Button, classNames } from '@sentio/ui-core' +import { produce } from 'immer' +import type { MappingRuleLike } from '../../types' + +const operators = { + '>': 'greater than', + '>=': 'greater or equal', + '==': 'equal', + '!=': 'not equal', + '<': 'less than', + '<=': 'less or equal' +} + +interface Props { + rules: MappingRuleLike[] + onChange: (rules: MappingRuleLike[]) => void +} + +const renderTreeLine = (index: number, isLast: boolean) => { + return ( +
+
+
+
+ {/*
*/} +
+
+ ) +} + +export function ValueStringMapping({ rules, onChange }: Props) { + const addRule = () => { + onChange( + produce(rules, (draft) => { + draft = draft || [] + draft.push({ + comparison: '==', + value: 0, + text: '' + }) + }) + ) + } + + function removeRule(index: number) { + onChange( + produce(rules, (draft) => { + draft.splice(index, 1) + }) + ) + } + + function changeRule(index: number, field: string, value: any) { + onChange( + produce(rules, (draft) => { + ;(draft[index] as Record)[field] = value + }) + ) + } + + return ( +
+ {(rules || []).map((rule, index) => { + const isLast = index === (rules || []).length - 1 + return ( +
+ {renderTreeLine(index, isLast)} + + If value is + + + { + changeRule(index, 'value', e.target.value) + }} + /> + + , then show + + { + changeRule(index, 'text', e.target.value) + }} + /> + {/* TODO: implement color mapping in data-grid component */} + {/* + also set color + + + { + changeRule(index, 'colorTheme', colorTheme?.value) + }} + /> + +
*/} + +
+ ) + })} + +
+ ) +} diff --git a/packages/ui-dashboard/src/charts/options/index.ts b/packages/ui-dashboard/src/charts/options/index.ts index 5c2bb8d..1ca50bd 100644 --- a/packages/ui-dashboard/src/charts/options/index.ts +++ b/packages/ui-dashboard/src/charts/options/index.ts @@ -1,5 +1,5 @@ // Chart option panels (phase 3b). Data-coupled panels (Data/Series/TimeRange/ -// Table/Scatter/Yaxis/Xaxis, value-formatting trio) follow in later slices. +// Table/Scatter/Yaxis/Xaxis) follow in later slices. export { LineControls } from './LineControls' export { LabelControls } from './LabelControls' export { @@ -10,3 +10,14 @@ export { BarGaugeControls, defaultConfig as defaultBarGaugeConfig } from './BarGaugeControls' +export { + ValueOptions, + ValueFormatters, + defaultConfig as defaultValueConfig, + type ValueFormatter +} from './ValueOptions' +export { + ValueControls, + defaultConfig as defaultValueControlsConfig +} from './ValueControls' +export { ValueStringMapping } from './ValueStringMapping'