Skip to content

Commit

Permalink
Merge pull request #168 from BoostV/feature/163-implement-expandable-…
Browse files Browse the repository at this point in the history
…table-rows-for-data-points

Feature/163 implement expandable table rows for data points
  • Loading branch information
langdal committed Jun 24, 2022
2 parents 916d097 + f41218e commit d8fec17
Show file tree
Hide file tree
Showing 16 changed files with 669 additions and 709 deletions.
111 changes: 41 additions & 70 deletions components/data-points/data-points.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { CircularProgress, IconButton, Box, Tooltip } from '@material-ui/core'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
import { useCallback, useEffect, useMemo, useReducer } from 'react'
import { useGlobal } from '../../context/global-context'
import {
dataPointsReducer,
DataPointsState,
} from '../../reducers/data-points-reducer'
import {
DataPointType,
TableDataPoint,
Expand All @@ -21,6 +17,10 @@ import { TitleCard } from '../title-card/title-card'
import useStyles from './data-points.style'
import DownloadCSVButton from '../download-csv-button'
import UploadCSVButton from '../upload-csv-button'
import {
dataPointsReducer,
DataPointsState,
} from '../../reducers/data-points-reducer'

type DataPointProps = {
valueVariables: ValueVariableType[]
Expand All @@ -30,8 +30,6 @@ type DataPointProps = {
onUpdateDataPoints: (dataPoints: DataPointType[][]) => void
}

type UpdateFnType = (rowIndex: number, ...args: any[]) => void

export default function DataPoints(props: DataPointProps) {
const {
valueVariables,
Expand All @@ -43,9 +41,7 @@ export default function DataPoints(props: DataPointProps) {
const classes = useStyles()
const [state, dispatch] = useReducer(dataPointsReducer, {
rows: [],
prevRows: [],
changed: false,
hasTempChange: false,
})
const isLoadingState = state.rows.length === 0
const global = useGlobal()
Expand All @@ -57,8 +53,16 @@ export default function DataPoints(props: DataPointProps) {
)

const buildCombinedVariables = useCallback((): CombinedVariableType[] => {
return (valueVariables as CombinedVariableType[]).concat(
categoricalVariables as CombinedVariableType[]
return (
valueVariables.map(v => ({
...v,
tooltip: `[${v.min}, ${v.max}]`,
})) as CombinedVariableType[]
).concat(
categoricalVariables.map(v => ({
...v,
tooltip: `${v.options.length} options`,
})) as CombinedVariableType[]
)
}, [categoricalVariables, valueVariables])

Expand All @@ -70,51 +74,45 @@ export default function DataPoints(props: DataPointProps) {
name: variable.name,
value: variable.options ? variable.options[0] : '',
options: variable.options,
tooltip: variable.tooltip,
}
})
.concat(
scoreNames.map(s => ({
name: s,
value: '0',
value: '',
options: undefined,
tooltip: undefined,
}))
),
isEditMode: true,
isNew: true,
}
}, [buildCombinedVariables, scoreNames])

const toggleEditMode = (rowIndex: number) => {
dispatch({ type: 'DATA_POINTS_TABLE_EDIT_TOGGLED', payload: rowIndex })
}
const rowAdded = (row: TableDataRow) =>
dispatch({
type: 'rowAdded',
payload: row,
})

const cancelEdit = (rowIndex: number) => {
dispatch({ type: 'DATA_POINTS_TABLE_EDIT_CANCELLED', payload: rowIndex })
}
const rowDeleted = (rowIndex: number) =>
dispatch({
type: 'rowDeleted',
payload: rowIndex,
})

const edit = (rowIndex: number, editValue: string, itemIndex: number) => {
const rowEdited = (rowIndex: number, row: TableDataRow) =>
dispatch({
type: 'DATA_POINTS_TABLE_EDITED',
type: 'rowEdited',
payload: {
itemIndex,
rowIndex,
value: editValue,
row,
},
})
}

const deleteRow = (rowIndex: number) => {
dispatch({ type: 'DATA_POINTS_TABLE_ROW_DELETED', payload: rowIndex })
}

const addRow = (emptyRow: TableDataRow) => {
dispatch({ type: 'DATA_POINTS_TABLE_ROW_ADDED', payload: emptyRow })
}

const buildState = useCallback(
(dataPoints: DataPointType[][]): DataPointsState => {
const combinedVariables: CombinedVariableType[] = buildCombinedVariables()
const emptyRow: TableDataRow = buildEmptyRow()
const dataPointRows: TableDataRow[] = dataPoints
.map(item => {
const rowData: DataPointType[] = item.filter(
Expand All @@ -127,46 +125,27 @@ export default function DataPoints(props: DataPointProps) {
name: v.name,
value: v.value.toString(),
options: combinedVariables[idx]?.options,
tooltip: combinedVariables[idx]?.tooltip,
}
})
const scores: TableDataPoint[] = item
.filter(dp => scoreNames.includes(dp.name))
.map(score => ({ name: score.name, value: score.value as string }))
return {
dataPoints: vars.concat(scores),
isEditMode: false,
isNew: false,
}
})
.concat(emptyRow as any)
.concat(buildEmptyRow())

return {
rows: dataPointRows,
prevRows: dataPointRows,
changed: false,
hasTempChange: false,
}
},
[buildCombinedVariables, buildEmptyRow, scoreNames]
[buildCombinedVariables, scoreNames, buildEmptyRow]
)

const onEditConfirm = (row: TableDataRow, rowIndex: number) => {
if (row.isNew) {
addRow(buildEmptyRow())
} else {
updateRow(rowIndex, toggleEditMode)
}
}

const updateRow = (index: number, updateFn: UpdateFnType, ...args: any[]) => {
const rowIndex = newestFirst ? state.rows.length - 1 - index : index
updateFn(rowIndex, ...args)
}

const updateTable = (dataPoints: DataPointType[][]) => {
dispatch({ type: 'setInitialState', payload: buildState(dataPoints) })
}

useEffect(() => {
dispatch({ type: 'setInitialState', payload: buildState(dataPoints) })
}, [valueVariables, categoricalVariables, scoreNames, buildState, dataPoints])
Expand Down Expand Up @@ -203,10 +182,7 @@ export default function DataPoints(props: DataPointProps) {
title={
<>
<Box display="flex" justifyContent="space-between">
<Box>
Data points
<br />
</Box>
Data points
<Box>
<DownloadCSVButton light />
<UploadCSVButton
Expand Down Expand Up @@ -244,22 +220,17 @@ export default function DataPoints(props: DataPointProps) {
{buildCombinedVariables().length > 0 && !isLoadingState && (
<Box className={classes.tableContainer}>
<EditableTable
newestFirst={global.state.dataPointsNewestFirst}
rows={
(newestFirst
? [...state.rows].reverse()
: state.rows) as TableDataRow[]
}
onEdit={(editValue: string, rowIndex: number, itemIndex: number) =>
updateRow(rowIndex, edit, editValue, itemIndex)
}
onEditConfirm={(row: TableDataRow, rowIndex: number) =>
onEditConfirm(row, rowIndex)
: [...state.rows]) as TableDataRow[]
}
onEditCancel={(rowIndex: number) => updateRow(rowIndex, cancelEdit)}
onToggleEditMode={(rowIndex: number) =>
updateRow(rowIndex, toggleEditMode)
onRowAdded={(row: TableDataRow) => rowAdded(row)}
onRowDeleted={(rowIndex: number) => rowDeleted(rowIndex)}
onRowEdited={(rowIndex: number, row: TableDataRow) =>
rowEdited(rowIndex, row)
}
onDelete={(rowIndex: number) => updateRow(rowIndex, deleteRow)}
/>
</Box>
)}
Expand Down
7 changes: 6 additions & 1 deletion components/editable-table/editable-table-cell.style.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { makeStyles } from '@material-ui/core'
import { tableBorder } from '../../theme/theme'

export const useStyles = makeStyles(theme => ({
cell: {
editCell: {
minWidth: 48,
},
cell: {
border: 'none',
borderTop: tableBorder,
},
}))

export default useStyles
51 changes: 36 additions & 15 deletions components/editable-table/editable-table-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,50 @@ import {
Select,
TableCell,
TextField,
Tooltip,
} from '@material-ui/core'
import { ChangeEvent } from 'react'
import { ChangeEvent, CSSProperties } from 'react'
import useStyles from './editable-table-cell.style'

type EditableTableCellProps = {
value: string
isEditMode: boolean
options?: string[]
onChange: (value: string) => void
onChange?: (value: string) => void
tooltip?: string
style?: CSSProperties
}

export function EditableTableCell(props: EditableTableCellProps) {
const { value, isEditMode, options, onChange } = props
export function EditableTableCell({
value,
isEditMode,
options,
onChange,
tooltip,
style,
}: EditableTableCellProps) {
const classes = useStyles()

const textField = (
<TextField
size="small"
value={value}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
onChange('' + e.target.value)
}
/>
)

return (
<>
{isEditMode ? (
<TableCell className={classes.cell}>
<TableCell className={classes.editCell} style={{ ...style }}>
{options && options.length > 0 ? (
<FormControl>
<Select
value={value}
onChange={(e: ChangeEvent<any>) =>
onChange(e.target.value as string)
onChange={(e: ChangeEvent<HTMLInputElement>) =>
onChange(e.target.value)
}
displayEmpty
inputProps={{ 'aria-label': 'select value' }}
Expand All @@ -41,17 +60,19 @@ export function EditableTableCell(props: EditableTableCellProps) {
</Select>
</FormControl>
) : (
<TextField
size="small"
value={value}
onChange={(e: ChangeEvent) =>
onChange('' + (e.target as HTMLInputElement).value)
}
/>
<>
{tooltip !== undefined ? (
<Tooltip title={tooltip}>{textField}</Tooltip>
) : (
<>{textField}</>
)}
</>
)}
</TableCell>
) : (
<TableCell>{value}</TableCell>
<TableCell className={classes.cell} style={{ ...style }}>
{value}
</TableCell>
)}
</>
)
Expand Down
46 changes: 46 additions & 0 deletions components/editable-table/editable-table-collapsed-row.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { makeStyles } from '@material-ui/core'
import { colors, tableBorder } from '../../theme/theme'

export const useStyles = makeStyles(theme => ({
buttonContainer: {
whiteSpace: 'nowrap',
float: 'right',
},
cell: {
color: colors.silver,
paddingRight: '8px',
border: 'none',
borderTop: tableBorder,
},
editCell: {
paddingRight: '0px',
border: 'none',
borderTop: tableBorder,
},
row: {
'&:hover': {
background: '#fdfdfd',
},
'&:hover td': {
background: '#f4f4f4',
},
'&:hover td:first-of-type': {
background: 'white',
},
'&:hover td:last-of-type': {
background: 'white',
},
},
newRow: {
paddingRight: 0,
border: 'none',
borderTop: tableBorder,
},
emptyCell: {
border: 'none',
width: 16,
padding: 0,
},
}))

export default useStyles
Loading

0 comments on commit d8fec17

Please sign in to comment.