Skip to content

Commit

Permalink
Merge pull request #12 from spiltbeans/components
Browse files Browse the repository at this point in the history
Turn project index file into components
  • Loading branch information
spiltbeans authored Jun 6, 2023
2 parents 826e22e + b0fd53c commit 684c907
Show file tree
Hide file tree
Showing 14 changed files with 678 additions and 440 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ touch .env && nano .env
The following environment variables are required:
```
# config for server
SERVER_MODE=<prod | test>
BYPASS_AUTH=<Bool>
TEST_ORIGIN=<TESTING_URL>
PROD_ORIGIN=<PRODUCTION_URL>
DATA_ORIGIN=<RELATIVE PATH_DATA_PATH>
BASIC_AUTH_USERNAME=<AUTH_USERNAME>
BASIC_AUTH_PASSWORD=<AUTH_PASSWORD
BASIC_AUTH_PASSWORD=<AUTH_PASSWORD>
NEXTAUTH_SECRET=<SECRET_FOR_JWT>
NEXTAUTH_URL=<APPLICATION_URL>
```
3. Build the Next.js project
```
Expand Down
16 changes: 13 additions & 3 deletions src/shared/components/Chart.tsx → src/components/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Label, Text, ResponsiveContainer } from 'recharts'
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'

// https://github.com/recharts/recharts/issues/397

Expand All @@ -14,8 +14,18 @@ const CustomizedAxisTick = (props: any) => {
)
}

export default function Chart({ data, maxValue, autoSort=true }: { data: Array<DataItem>, maxValue?: number, autoSort ?: boolean }) {
const sortedData = autoSort ? data.sort((a, b) => a.value - b.value) : data
export default function Chart(
{
data,
maxValue,
autoSort = true
}: {
data: Array<DataElement>,
maxValue?: number,
autoSort?: boolean
}
) {
const sortedData = autoSort ? data.sort((a, b) => a.value - b.value) : data
return (
<ResponsiveContainer width={'100%'} height={300}>
<BarChart
Expand Down
39 changes: 39 additions & 0 deletions src/components/ChartSpace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import dynamic from 'next/dynamic'
import CloseIcon from '@mui/icons-material/Close'

import Fab from '@mui/material/Fab'

const SimpleBarChartWithoutSSR = dynamic(import('@/components/Chart'), { ssr: false })


export default function ChartSpace(
{
graphs,
autoSort,
onRemoveGraph,
maxValue
}: {
graphs: FormattedData,
autoSort: boolean,
onRemoveGraph: (g: string) => void,
maxValue?: number
}
) {
return (
<>
{Object.keys(graphs ?? {})?.map((label, idx) => {
return (
<div id='data-chart' className='w-full' key={idx}>
<div className='flex items-center gap-5 ml-8 my-4'>
<Fab size='small' onClick={() => onRemoveGraph(label)}>
<CloseIcon />
</Fab>
{label}
</div>
<SimpleBarChartWithoutSSR autoSort={autoSort} data={graphs?.[label] ?? []} maxValue={maxValue} />
</div>
)
})}
</>
)
}
241 changes: 241 additions & 0 deletions src/components/Controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import { useState, useEffect } from 'react'
import axios from 'axios'
import { useRouter } from 'next/router'
import type { SelectChangeEvent } from '@mui/material/Select'

import ToggleButton from '@mui/material/ToggleButton'
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup'
import TextField from '@mui/material/TextField'
import Tooltip from '@mui/material/Tooltip'
import Autocomplete from '@mui/material/Autocomplete'
import Select from '@mui/material/Select'
import MenuItem from '@mui/material/MenuItem'
import Fab from '@mui/material/Fab'

import AddIcon from '@mui/icons-material/Add'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'

// enum DisplayTypes{
// clients,
// employees,
// individual,
// individual_employees,
// individual_clients,
// client_trends,
// employee_trends
// }

export default function Controller(
{
isDefaultCollapsed = false,
documentOptions,
graphTypeOptions,
graphOptions,
onWorkbookChange,
onYRangeChange,
onGraphTypeChange,
onAddGraph,
onWarning

}: {
isDefaultCollapsed: boolean,
documentOptions: Array<string>,
graphTypeOptions: Array<string>,
graphOptions: Array<string>,
onWorkbookChange: (d: string) => void,
onYRangeChange: (y: boolean) => void,
onGraphTypeChange: (p: string) => void,
onAddGraph: (g: string) => void
onWarning: (w: string) => void,
}
) {
useEffect(()=>{
// this is a limited solution. if the workbook is changed and the graph type is clients
// or employees, the graph is not selected
changeGraphType('clients')
// eslint-disable-next-line
}, [])
const SPECIAL_DISPLAY = ['client_trends', 'employee_trends']

const [collapsed, setCollapsed] = useState(isDefaultCollapsed)
const [workbook, setWorkbook] = useState(documentOptions[0])

const [searchInp, setSearchInp] = useState('')
const [searchValue, setSearchValue] = useState<string | null>(null)

// display options
const [graphType, setGraphType] = useState('clients')
const [yRelative, setYRelative] = useState(true)

const router = useRouter()

// References
// https://gist.github.com/ndpniraj/2735c3af00a7c4cbe50602ffe6209fc3
// https://stackoverflow.com/questions/59233036/react-typescript-get-files-from-file-input
const handleFileUpload = (e: React.FormEvent<HTMLInputElement>) => {
try {
if (e.currentTarget?.files) {
const file = e.currentTarget.files[0]

if (!file) return

const formData = new FormData()
formData.append('newFile', file)
axios.post('/api/xlsx', formData)
.then(({ data }) => {
if (data.status) return router.replace(router.asPath)

onWarning(data.message)
})
}
} catch (err: any) {
onWarning(JSON.stringify(err))
}

}

const handleWorkbookChange = (e: SelectChangeEvent) => {
clearSearch()

if (e.target.value === 'trends' && !SPECIAL_DISPLAY.includes(graphType)) {
changeGraphType('client_trends')
}

if (e.target.value !== 'trends' && SPECIAL_DISPLAY.includes(graphType)) {
changeGraphType('clients')
}

setWorkbook(e.target.value as string)
onWorkbookChange(e.target.value as string)
}

const handleChangeYRange = (_event: React.MouseEvent<HTMLElement>, yRange: boolean) => {
if (yRange !== null) {
setYRelative(yRange)
onYRangeChange(yRange)
}
}
const handleGraphTypeChange = (_e: React.MouseEvent<HTMLElement>, type: string) => {
changeGraphType(type)
}

const changeGraphType = (type: string) => {
if (type !== null) {
setGraphType(type)
clearSearch()
onGraphTypeChange(type)
}
}

const handleSearchChange = (_e: React.SyntheticEvent<Element, Event>, value: string) => setSearchInp(value)

const handleSearchValChange = (_e: any, newVal: string | null) => setSearchValue(newVal)

const handleAddGraph = () => {
clearSearch()
onAddGraph(searchInp)
}

const clearSearch = () => {
setSearchInp('')
setSearchValue(null)
}

return (
<div>
{collapsed && <ArrowBackIcon className='hover:cursor-pointer' onClick={() => setCollapsed(false)} />}
{!collapsed &&
<div className='flex flex-col gap-4 px-5'>
<ArrowForwardIcon className='hover:cursor-pointer' onClick={() => setCollapsed(true)} />
<section className='flex flex-col gap-4'>
<h2 className='text-base font-bold'>
Select from already existing data
<hr />
</h2>
<div>
<Select
value={workbook}
onChange={handleWorkbookChange}
>
{documentOptions.map((document, idx) => {
return <MenuItem key={idx} value={document}>{document}</MenuItem>
})}
</Select>
</div>

</section>

<section className='flex flex-col gap-4'>
<h2 className='text-base font-bold'>
Upload your own data
<hr />
</h2>
<Tooltip title="replace data by uploading a sheet with the same name">
<input type='file' onChange={handleFileUpload}></input>
</Tooltip>
<div className='flex flex-col w-96'>
<em>
{'Note on "trend" feature: files are sorted by date in format of [text]-[DD]-[MM]-[YY].xlsx.'}
</em>
<em>
{'The system will attempt to sort even if some date formatting is missing (i.e., [text]-[MM]-[YY].xlsx), but if the system cannot recognize a valid date formatting, the bar will be positioned left-wise.'}
</em>
</div>

</section>
<section className='flex flex-col gap-4'>
<h2 className='text-base font-bold'>
Display Options
<hr />
</h2>
<div className='flex flex-col w-full items-center gap-4'>
<ToggleButtonGroup value={yRelative} exclusive onChange={handleChangeYRange}>
<ToggleButton className='text-xs' value={true}>Set Y Range Relative</ToggleButton>
<ToggleButton className='text-xs' value={false}>Set Y Range Global</ToggleButton>
</ToggleButtonGroup>


<ToggleButtonGroup value={graphType} exclusive onChange={handleGraphTypeChange}>
<ToggleButton className='text-xs' value='clients' disabled={!graphTypeOptions.includes('clients')}>Clients</ToggleButton>
<ToggleButton className='text-xs' value='employees' disabled={!graphTypeOptions.includes('employees')}>Employees</ToggleButton>
<ToggleButton className='text-xs' value='individual_employees' disabled={!graphTypeOptions.includes('individual_employees')}>Individual Employees</ToggleButton>
<ToggleButton className='text-xs' value='individual_clients' disabled={!graphTypeOptions.includes('individual_clients')}>Individual Clients</ToggleButton>
</ToggleButtonGroup>
<ToggleButtonGroup value={graphType} exclusive onChange={handleGraphTypeChange}>
<ToggleButton className='text-xs' value='client_trends' disabled={!graphTypeOptions.includes('client_trends')}>Client Trends</ToggleButton>
<ToggleButton className='text-xs' value='employee_trends' disabled={!graphTypeOptions.includes('employee_trends')}>Employee Trends</ToggleButton>
</ToggleButtonGroup>
</div>

</section>

<section className='flex flex-col gap-4'>
<h2 className='text-base font-bold'>
Add a Graph
<hr />
</h2>
<div className='flex justify-evenly items-center'>
<Autocomplete
disablePortal
freeSolo
id={'graph_input'}
options={graphOptions}
value={searchValue} // option chosen
onChange={handleSearchValChange}
inputValue={searchInp} // what is typed
onInputChange={handleSearchChange}
className={'w-1/2'}
renderInput={(params) => <TextField {...params} label={'Graph Name'} />}
/>
<Fab size='small' className='bg-gray-400 hover:bg-gray-300' onClick={handleAddGraph}>
<AddIcon />
</Fab>
</div>
</section>

</div>
}
</div>
)
}
45 changes: 45 additions & 0 deletions src/components/Errors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// import { useRef } from 'react'
// import Accordion from '@mui/material/Accordion'
// import AccordionSummary from '@mui/material/AccordionSummary'
// import AccordionDetails from '@mui/material/AccordionDetails'
// import Badge from '@mui/material/Badge'
// import ExpandMoreIcon from '@mui/icons-material/ExpandMore'

export default function Errors(
{ errors }: { errors: { [sheet: string]: SheetErrors } | string }
) {

return (
<>
{
typeof errors === 'string' ? (
<ul className='list-disc ml-8'>
<li >
{errors}
</li>
</ul>
) : (
Object.keys(errors ?? {}).map((sheet, idx) => {
return (
<div key={idx}>
{sheet}
<ul className='list-disc ml-8'>
{
(Object.keys(errors?.[sheet])?.map((source, sub_idx) => {
return (
<li key={`${idx}_${sub_idx}`}>
{errors[sheet][source]}
</li>
)
}))
}
</ul>
</div>
)
})
)
}
</>
)

}
Loading

0 comments on commit 684c907

Please sign in to comment.