Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/components/custom-select.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { createElement } from 'react'
import PropTypes from 'prop-types'
import { DropdownSelect, Icons } from '@eqworks/lumen-labs'

Expand All @@ -14,8 +14,9 @@ export const DROPDOWN_SELECT_CLASSES = {
}
const { root, ...baseClasses } = DROPDOWN_SELECT_CLASSES

const CustomSelect = ({ classes, onClear, fullWidth, ...props }) => (
<DropdownSelect simple
const CustomSelect = ({ multiSelect, data, value, onSelect, classes, onClear, fullWidth, icons, ...props }) => (
<DropdownSelect
simple={!icons}
classes={{
root: fullWidth ? [root, 'w-full'].join(' ') : root,
...baseClasses,
Expand All @@ -24,6 +25,35 @@ const CustomSelect = ({ classes, onClear, fullWidth, ...props }) => (
overflow='vertical'
endIcon={<Icons.ArrowDown size='md' />}
onDelete={onClear}
multiSelect={multiSelect}
data={
icons
? [{
items: data.map((d, i) => ({
title: d,
startIcon: createElement(icons[i], { size: 'sm' }),
})),
}]
: data
}
value={
icons
? multiSelect
? (value || []).map(title => ({ title })) || []
: { title: value }
: value
}
onSelect={
icons
? v => (
onSelect(
multiSelect
? Object.values(v).map(({ title }) => title).filter(Boolean)
: v.title
)
)
: onSelect
}
{...props}
/>
)
Expand All @@ -37,6 +67,7 @@ CustomSelect.propTypes = {
onSelect: PropTypes.func.isRequired,
onClear: PropTypes.func,
fullWidth: PropTypes.bool,
icons: PropTypes.arrayOf(PropTypes.elementType),
}
CustomSelect.defaultProps = {
classes: {},
Expand All @@ -45,6 +76,7 @@ CustomSelect.defaultProps = {
value: '',
onClear: () => { },
fullWidth: false,
icons: null,
}

export default CustomSelect
4 changes: 4 additions & 0 deletions src/components/linked-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const LinkedSelect = ({
disableSubMessage,
customRender,
customRenderSub,
icons,
}) => {
const [choice, setChoice] = useState(init)
const [subChoice, setSubChoice] = useState(subInit)
Expand Down Expand Up @@ -77,6 +78,7 @@ const LinkedSelect = ({
onSelect={setChoice}
onClear={() => setChoice('')}
placeholder={placeholders[0]}
{...(icons && { icons })}
/>
}
</div>
Expand Down Expand Up @@ -132,6 +134,7 @@ LinkedSelect.propTypes = {
disableSubMessage: PropTypes.string,
customRender: PropTypes.func,
customRenderSub: PropTypes.func,
icons: PropTypes.array,
}

LinkedSelect.defaultProps = {
Expand All @@ -145,6 +148,7 @@ LinkedSelect.defaultProps = {
disableSubMessage: '',
customRender: null,
customRenderSub: null,
icons: null,
}

export default LinkedSelect
4 changes: 4 additions & 0 deletions src/components/plural-linked-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const PluralLinkedSelect = ({
headerIcons,
titles,
values,
valueIcons,
primaryKey,
secondaryKey,
data,
Expand All @@ -65,6 +66,7 @@ const PluralLinkedSelect = ({
className={`${i > 0 ? 'mt-2' : ''}`}
callback={([_k, _v]) => callback(i, { [primaryKey]: _k, [secondaryKey]: _v })}
data={data.filter(d => primary === d || !values.map(v => v[primaryKey]).includes(d))}
{...(valueIcons && { icons: valueIcons })}
init={primary}
subData={subData}
subInit={values[i]?.[secondaryKey]}
Expand Down Expand Up @@ -139,6 +141,7 @@ PluralLinkedSelect.propTypes = {
callback: PropTypes.func.isRequired,
deleteCallback: PropTypes.func,
values: PropTypes.arrayOf(PropTypes.object).isRequired,
valueIcons: PropTypes.array,
disableSubs: PropTypes.bool,
disableSubMessage: PropTypes.string,
addMessage: PropTypes.string,
Expand All @@ -151,6 +154,7 @@ PluralLinkedSelect.defaultProps = {
titles: [],
headerIcons: [],
deleteCallback: () => {},
valueIcons: null,
disableSubs: false,
disableSubMessage: '',
addMessage: 'Add',
Expand Down
50 changes: 50 additions & 0 deletions src/constants/columns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Icons } from '@eqworks/lumen-labs'
import { priceStringToNumeric } from '../util/numeric'
import { isString } from '../util/string-manipulation'


export const columnTypes = {
NUMERIC: 'Numeric',
DATE: 'Date',
STRING: 'String',
PRICE: 'Price',
}

export const columnTypeInfo = {
// 'primitives':
[columnTypes.NUMERIC]: {
Icon: Icons.Hash,
validate: (v, name) => {
const res = !isNaN(v)
return name === undefined
? res
: res && !name.endsWith('_id')
},
},
[columnTypes.STRING]: {
Icon: Icons.Edit,
validate: isString,
},
// subtypes:
[columnTypes.PRICE]: {
parentTypes: [
columnTypes.NUMERIC,
],
Icon: Icons.Dollar,
validate: (v) => isString(v) && !isNaN(priceStringToNumeric(v)),
normalize: (c) => c.map(priceStringToNumeric),
},
[columnTypes.DATE]: {
parentTypes: [
columnTypes.STRING,
],
Icon: Icons.Table, // looks like a calendar
validate: (v) => {
if (!isString(v)) {
return false
}
const sample = new Date(v)
return sample instanceof Date && !isNaN(sample)
},
},
}
15 changes: 9 additions & 6 deletions src/controls/editor-mode/filters.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useMemo } from 'react'

import { DateRange, Icons } from '@eqworks/lumen-labs'

Expand All @@ -20,11 +20,16 @@ const Filters = () => {
const groups = useStoreState((state) => state.groups)
const groupFilter = useStoreState((state) => state.groupFilter)
const filters = useStoreState((state) => state.filters)
const columns = useStoreState((state) => state.columns)
const columnsAnalysis = useStoreState((state) => state.columnsAnalysis)
const domain = useStoreState((state) => state.domain)
const domainIsDate = useStoreState((state) => state.domainIsDate)

const filterData = useMemo(() => (
Object.fromEntries(Object.entries(columnsAnalysis)
.filter(([, { min, max, isNumeric }]) => isNumeric && min !== max)
.map(([c, { Icon }]) => [c, { Icon }]))
), [columnsAnalysis])

return (
<WidgetControlCard
clear={() => resetValue({ filters, groupFilter })}
Expand Down Expand Up @@ -68,10 +73,8 @@ const Filters = () => {
values={filters}
primaryKey='key'
secondaryKey='filter'
data={columns.map(({ name }) => name).filter(c => {
const { min, max, isNumeric } = columnsAnalysis[c] || {}
return isNumeric && min !== max
})}
valueIcons={Object.values(filterData).map(({ Icon }) => Icon)}
data={Object.keys(filterData)}
subData={[]}
callback={(i, { key }) => {
if (i === filters.length) {
Expand Down
29 changes: 13 additions & 16 deletions src/controls/shared/domain-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const DomainControls = () => {
const update = useStoreActions(actions => actions.update)
const userUpdate = useStoreActions(actions => actions.userUpdate)
const resetValue = useStoreActions(actions => actions.resetValue)
const columns = useStoreState((state) => state.columns)
const type = useStoreState((state) => state.type)
const group = useStoreState((state) => state.group)
const groupKey = useStoreState((state) => state.groupKey)
Expand All @@ -28,18 +27,15 @@ const DomainControls = () => {
// local state
const groupingOptional = useMemo(() => typeInfo[type]?.groupingOptional, [type])

const eligibleGroupKeyValues = useMemo(() => (
columns.map(({ name }) => name)
.filter(c => !columnsAnalysis[c]?.isNumeric)
), [columns, columnsAnalysis])

const eligibleDomainValues = useMemo(() => (
columns.map(({ name }) => name)
.filter(c =>
(groupingOptional || eligibleGroupKeyValues.includes(c))
&& !(valueKeys.map(({ key }) => key).includes(c))
)
), [columns, eligibleGroupKeyValues, groupingOptional, valueKeys])
Object.fromEntries(
Object.entries(columnsAnalysis)
.filter(([c, { isNumeric }]) =>
(groupingOptional || !isNumeric)
&& !(valueKeys.map(({ key }) => key).includes(c)))
.map(([c, { Icon }]) => [c, { Icon }])
)
), [columnsAnalysis, groupingOptional, valueKeys])

useEffect(() => {
if (!group && !groupingOptional) {
Expand All @@ -48,11 +44,11 @@ const DomainControls = () => {
}, [group, groupingOptional, update])

const renderCategory = () => {
const { category } = columnsAnalysis[group ? groupKey : indexKey] || {}
const { category, isNumeric } = columnsAnalysis[group ? groupKey : indexKey] || {}
return (category &&
<Chip
selectable={false}
color={category === 'Numeric' ? 'success' : 'interactive'}
color={isNumeric ? 'success' : 'interactive'}
>
{category}
</Chip >
Expand All @@ -67,10 +63,11 @@ const DomainControls = () => {
renderRow('Column',
<CustomSelect
fullWidth
data={eligibleDomainValues}
data={Object.keys(eligibleDomainValues)}
icons={Object.values(eligibleDomainValues).map(({ Icon }) => Icon)}
value={domain.value}
onSelect={val => {
const willGroup = eligibleGroupKeyValues.includes(val) && !groupingOptional
const willGroup = !columnsAnalysis[val]?.isNumeric && !groupingOptional
userUpdate({
group: willGroup,
...(
Expand Down
2 changes: 2 additions & 0 deletions src/controls/shared/map-domain-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CustomSelect from '../../components/custom-select'
import WidgetControlCard from '../shared/components/widget-control-card'
import { renderRow, renderSection } from './util'
import { MAP_LAYER_VALUE_VIS, MAP_LAYER_GEO_KEYS } from '../../constants/map'
import { Icons } from '@eqworks/lumen-labs'


const MapDomainControls = () => {
Expand Down Expand Up @@ -41,6 +42,7 @@ const MapDomainControls = () => {
<CustomSelect
fullWidth
data={eligibleDomainValues}
icons={eligibleDomainValues.map(() => Icons.AddPin)}
value={domain.value}
onSelect={val => {
// update groupKey with mapGroupKey value to have it available if we switch to a chart widget type
Expand Down
3 changes: 3 additions & 0 deletions src/controls/shared/map-value-controls/map-linked-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Tooltip, Icons, getTailwindConfigColor, makeStyles } from '@eqworks/lum

import PluralLinkedSelect from '../../../components/plural-linked-select'
import types from '../../../constants/type-info'
import { useStoreState } from '../../../store'


const classes = makeStyles({
Expand Down Expand Up @@ -38,6 +39,7 @@ const MapLinkedSelect = ({
disableSubMessage,
callback,
}) => {
const columnsAnalysis = useStoreState((state) => state.columnsAnalysis)
return (
categories.map((mapVis, i) => {
const match = values.findIndex(v => v.mapVis === mapVis)
Expand Down Expand Up @@ -69,6 +71,7 @@ const MapLinkedSelect = ({
]}
titles={titles}
values={values[match] ? [values[match]] : []}
{...(values[match] && { valueIcons: [columnsAnalysis[values[match][PRIMARY_KEY]]?.Icon] })}
callback={(_, v) => callback(
match,
{
Expand Down
Loading