Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SALAD-23959] WebApp: Reusable Table component - added #1263

Merged
merged 11 commits into from
Jan 29, 2025
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 = () => {
Maks19 marked this conversation as resolved.
Show resolved Hide resolved
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