Skip to content

Commit

Permalink
[SALAD-23959] WebApp: Reusable Table component - added (#1263)
Browse files Browse the repository at this point in the history
* Reusable Table component - added

* Table: refactor

* _AllMachines: getRows - refactor

* _AllMachines: checkboxes styles - added

* logs - removed

* _AllMachines: machines are made selectable

* _AllMachines: styles - improved

* _AllMachines: warningPill styles - changed

* _AllMachines: Machine ID question icon - added

* AllMachines - temporary commented

* _AllMachines: selectedMachineIds state - refactored
  • Loading branch information
vitto-moz authored Jan 29, 2025
1 parent cbfe55d commit 2c840f3
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 0 deletions.
77 changes: 77 additions & 0 deletions packages/web-app/src/components/Table/Table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Text } from '@saladtechnologies/garden-components'
import type CSS from 'csstype'
import { type FunctionComponent } from 'react'
import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import type { SaladTheme } from '../../SaladTheme'
import type { TableRow, TableTitles } from './types'

const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: SaladTheme) => ({
tableWrapper: {
fontFamily: theme.fontMallory,
color: theme.lightGreen,
fontSize: '14px',
width: '100%',
'@media (max-width: 900px)': {
overflow: 'scroll',
},
},
tableContent: {
overflow: 'hidden',
boxSizing: 'border-box',
},
table: {
width: '100%',
borderCollapse: 'collapse',
},
tableHeaderRow: {
borderBottom: `1px #3B4D5C solid`,
},
tableRow: {
borderBottom: `1px #3B4D5C solid`,
},
tableCell: {},
})

interface Props extends WithStyles<typeof styles> {
titles: TableTitles
rows: Array<TableRow>
}

const _Table: FunctionComponent<Props> = ({ classes, titles, rows }) => {
return (
<div className={classes.tableWrapper}>
<div className={classes.tableContent}>
<table className={classes.table}>
<thead>
<tr className={classes.tableHeaderRow}>
{titles.map((title) => {
const titleJSX = typeof title === 'object' ? title : <Text variant="baseXS">{title}</Text>
return (
<th className={classes.tableCell} key={title.toString()}>
{titleJSX}
</th>
)
})}
</tr>
</thead>
<tbody>
{rows.map((row, index) => {
return (
<tr key={index} className={classes.tableRow}>
{row.map((rowItem) => {
const rowItemJSX =
typeof rowItem === 'object' ? rowItem : <Text variant="baseXS">{rowItem.toString()}</Text>
return <td className={classes.tableCell}>{rowItemJSX}</td>
})}
</tr>
)
})}
</tbody>
</table>
</div>
</div>
)
}

export const Table = withStyles(styles)(_Table)
1 change: 1 addition & 0 deletions packages/web-app/src/components/Table/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Table'
5 changes: 5 additions & 0 deletions packages/web-app/src/components/Table/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type TableRowItem = string | number | JSX.Element

export type TableRow = Array<TableRowItem>

export type TableTitles = Array<JSX.Element | string>
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { faCircleQuestion, faList } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Checkbox, Text } from '@saladtechnologies/garden-components'
import classNames from 'classnames'
import type CSS from 'csstype'
import { DateTime } from 'luxon'
import { useState } from 'react'
import type { WithStyles } from 'react-jss'
import withStyles from 'react-jss'
import { Table } from '../../../../components/Table'
import type { TableRow } from '../../../../components/Table/types'
import { DefaultTheme, type SaladTheme } from '../../../../SaladTheme'
import { EarnSectionHeader } from '../EarnSectionHeader'
import { generatedMockedMachines } from './mocks'

const styles: (theme: SaladTheme) => Record<string, CSS.Properties> = (theme: SaladTheme) => ({
allMachinesWrapper: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'flex-start',
flexDirection: 'column',
width: '100%',
position: 'relative',
},
tableWrapper: {
display: 'flex',
flexDirection: 'column',
flex: 1,
position: 'relative',
height: '200px',
width: '100%',
maxWidth: '700px',
},
tableCell: {
padding: '4px',
fontSize: '14px',
},
tableHeaderCell: {
padding: '10px',
paddingLeft: '0px',
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
flexDirection: 'row',
},
tableCellCentered: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
warningPillWrapper: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
gap: '6px',
},
warningPill: {
height: '23px',
padding: '0px 8px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
borderRadius: '16px',
backgroundColor: '#F6931D',
width: 'auto',
color: theme.darkBlue,
textDecoration: 'underline',
},
checkboxWrapper: {
width: '22px',
height: '22px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
border: `1px ${theme.lightGreen} solid`,
transform: 'scale(0.8)',
},
questionIconWrapper: {
marginLeft: '6px',
backgroundColor: theme.lightGreen,
border: `.5px solid ${theme.lightGreen}`,
borderRadius: '100%',
width: '13px',
height: '13px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
cursor: 'pointer',
},
})

interface Props extends WithStyles<typeof styles> {}

const _AllMachines = ({ classes }: Props) => {
const [selectedMachineIds, setSelectedMachineIds] = useState<Record<string, boolean>>(() =>
Object.fromEntries(generatedMockedMachines.map((machine) => [machine.id, false])),
)

const handleMachineIdQuestionIconClick = () => {
window.location.href = 'https://support.salad.com/article/414-how-to-find-your-salad-machine-id'
}

const getTitles = () => {
return [
<div className={(classes.tableHeaderCell, classes.tableCellCentered)}>
<FontAwesomeIcon icon={faList} />
</div>,
<div className={classes.tableHeaderCell}>
<Text variant="baseXS">Machine ID</Text>
<div className={classes.questionIconWrapper} onClick={handleMachineIdQuestionIconClick}>
<FontAwesomeIcon
size="sm"
icon={faCircleQuestion}
fill={DefaultTheme.darkBlue}
color={DefaultTheme.darkBlue}
/>
</div>
</div>,
<div className={classes.tableHeaderCell}>
<Text variant="baseXS">Running Status</Text>
</div>,
<div className={classes.tableHeaderCell}>
<Text variant="baseXS">Last Seen</Text>
</div>,
<div className={classNames(classes.tableHeaderCell, classes.tableCellCentered)}>
<Text variant="baseXS">Current Earning Rate</Text>
</div>,
<div className={classNames(classes.tableHeaderCell, classes.tableCellCentered)}>
<Text variant="baseXS">Warnings</Text>
</div>,
]
}

const getRows = (): Array<TableRow> => {
return generatedMockedMachines
.map((machine) => {
return {
checkbox: (
<div className={classNames(classes.tableCell, classes.tableCellCentered)}>
<div className={classes.checkboxWrapper}>
<Checkbox
onChange={(checked) =>
setSelectedMachineIds((previousSelectedMachineIds) => ({
...previousSelectedMachineIds,
[machine.id]: checked,
}))
}
checked={selectedMachineIds[machine.id]}
/>
</div>
</div>
),
...machine,
lastSeen: DateTime.fromJSDate(machine.lastSeen).toFormat('MMM d, yyyy'),
currentEarningRate: (
<div className={classNames(classes.tableCell, classes.tableCellCentered)}>{machine.currentEarningRate}</div>
),
warnings: (
<div className={classes.warningPillWrapper}>
{machine.warnings.map((warningText) => (
<div className={classes.warningPill}>
<Text variant="baseXS">{warningText}</Text>
</div>
))}
</div>
),
}
})
.map((machineRow) =>
Object.values(machineRow).map((machineRowItem) => {
return <div className={classes.tableCell}>{machineRowItem}</div>
}),
)
}

return (
<div className={classes.allMachinesWrapper}>
<EarnSectionHeader>All Machines</EarnSectionHeader>
<div className={classes.tableWrapper}>
<Table titles={getTitles()} rows={getRows()} />
</div>
</div>
)
}

export const AllMachines = withStyles(styles)(_AllMachines)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './AllMachines'
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
export interface MachineState {
id: string
status: RunningStatus
lastSeen: Date
currentEarningRate: number
warnings: MachineWarnings[]
}

type RunningStatus = 'Idle' | 'Offline' | 'Downloading Job' | 'Downloading Job' | 'Running Job'
type MachineWarnings = 'Idle' | 'Wsl Update' | 'Network Blocked'

export const getRandomId = (): string => {
let text: string = ''
const possible: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'

for (let i = 0; i < 6; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length))
}

return text
}

const generateMockedMachines = () => {
return Array(100)
.fill(null)
.map(() => ({
id: getRandomId().toLocaleLowerCase(),
status: 'Idle',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked'],
}))
}

export const generatedMockedMachines = generateMockedMachines()

export const mockedMachines: MachineState[] = [
{
id: '0',
status: 'Idle',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked'],
},
{
id: '1',
status: 'Offline',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: [],
},
{
id: '2',
status: 'Running Job',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked', 'Idle'],
},
{
id: '3',
status: 'Downloading Job',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked', 'Idle'],
},
{
id: '4',
status: 'Downloading Job',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked', 'Idle'],
},
{
id: '5',
status: 'Downloading Job',
lastSeen: new Date(),
currentEarningRate: 0.018,
warnings: ['Wsl Update', 'Network Blocked', 'Idle'],
},
]
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const _EarningSummaryPage: FC<Props> = ({
viewLast7Days={viewLast7Days}
viewLast30Days={viewLast30Days}
/>
{/* <AllMachines /> */}
<LatestRewardsRedeemed
latestCompletedRedeemedRewards={latestCompletedRedeemedRewardsArray}
navigateToRewardVaultPage={trackAndNavigateToRewardVaultPage}
Expand Down

0 comments on commit 2c840f3

Please sign in to comment.