diff --git a/bun.lockb b/bun.lockb deleted file mode 100644 index 2a890bb..0000000 Binary files a/bun.lockb and /dev/null differ diff --git a/src/app.tsx b/src/app.tsx index 66a8e95..f4b0678 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -12,7 +12,7 @@ import Stepper from './components/stepper' import SavedCalculation from './components/saved-calculation' // import Notification from './components/Notification'; -import vars from './helpers/vars' +import { useGlobals } from './hooks/use-globals' const INPUT_CODES = [ [ @@ -33,8 +33,7 @@ const INPUT_CODES = [ ] export default function App() { - vars.formValues = useState({}) - vars.activeStep = useState(0) + const { activeStep, setActiveStep, formValues } = useGlobals() // const [notifications, setNotifications] = useState([]) @@ -56,10 +55,8 @@ export default function App() { const [isTextFieldErrors, setIsTextFieldErrors] = useState({}) const getInvalidTextFieldIds = () => { - return INPUT_CODES[vars.activeStep[0]].filter( - inputId => - vars.formValues[0][inputId] === '' || - isNaN(vars.formValues[0][inputId]), + return INPUT_CODES[activeStep].filter( + inputId => formValues[inputId] === '' || isNaN(formValues[inputId]), ) } @@ -86,11 +83,11 @@ export default function App() { const handleNext = () => { if (isFormValuesValid()) { - return vars.activeStep[1](vars.activeStep[0] + 1) + return setActiveStep(prev => prev + 1) } } - const handlePrev = () => vars.activeStep[1]((prev: number) => prev - 1) + const handlePrev = () => setActiveStep((prev: number) => prev - 1) return ( @@ -110,7 +107,7 @@ export default function App() { diff --git a/src/components/result-box/result-box.tsx b/src/components/result-box/result-box.tsx index c9aed9a..e21138e 100644 --- a/src/components/result-box/result-box.tsx +++ b/src/components/result-box/result-box.tsx @@ -1,70 +1,27 @@ +// vendors import { useRef, useState, useEffect, useMemo } from 'react' - -import Box from '@mui/system/Box' -import Button from '@mui/material/Button' -import IconButton from '@mui/material/IconButton' -import Tooltip from '@mui/material/Tooltip' - +import { Box, Button, IconButton, Tooltip } from '@mui/material' +import moment from 'moment' +// icons +import { Save as SaveIcon } from '@mui/icons-material' +// helpers +import { currencyFormat, numberFormat } from '../../helpers' import calculatePalmGrade from '../../helpers/calculate-palm-grade' +// import { GALog } from "../helpers/firebaseClient"; +// components import DetailDialog from './components/detail-dialog' import SummaryTable from './components/summary-table' import TextField from '../text-field' - -// import { GALog } from "../helpers/firebaseClient"; -import { - currencyFormat, - getSavedDatasets, - numberFormat, -} from '../../helpers/index' -import vars from '../../helpers/vars' -import moment from 'moment' - -import SaveIcon from '@mui/icons-material/Save' - -const getSummaryData = (detailsCalculation: AnuType[]) => { - const dataset = vars.formValues[0] - - const summaryDataset: TypeB[] = [ - { - name: 'Tandan Buah Segar', - weight: dataset.totalWeight, - worth: dataset.totalWeight * dataset.pricePerKg, - tooltip: `${numberFormat(dataset.totalWeight)} × ${currencyFormat( - dataset.pricePerKg, - )}`, - }, - ] - - const { totalCutWorth, totalAddWorth } = detailsCalculation.reduce( - (acc, detail) => ({ - totalCutWorth: acc.totalCutWorth + detail.cutWorth, - totalAddWorth: acc.totalAddWorth + detail.addWorth, - }), - { - totalCutWorth: 0, - totalAddWorth: 0, - }, - ) - - summaryDataset.push({ - name: 'Potongan', - weight: totalCutWorth / dataset.pricePerKg, - worth: totalCutWorth, - }) - - summaryDataset.push({ - name: 'Insentif', - weight: totalAddWorth / dataset.pricePerKg, - worth: totalAddWorth, - }) - - return summaryDataset -} +// hooks +import { useGlobals } from '../../hooks/use-globals' +// functions +import { getSavedDatasets } from '../../functions/get-saved-datasets' let calculationResults: AnuType[] = [] -const ResultBox = () => { - const temp = vars.formValues[0] +export default function ResultBox() { + const { formValues: temp, setFormValues, setActiveStep } = useGlobals() + const dataset = useMemo(() => { return { ...temp } }, [temp]) @@ -76,18 +33,52 @@ const ResultBox = () => { const detailBtnRef = useRef(null) useEffect(() => { - vars.formValues[0].pricePerKg = pricePerKg - vars.formValues[1]({ ...vars.formValues[0] }) + setFormValues({ ...temp, pricePerKg }) - calculationResults = calculatePalmGrade(vars.formValues[0]) + calculationResults = calculatePalmGrade(temp) - setSummaryData(getSummaryData(calculationResults)) + setSummaryData(getSummaryData(calculationResults, temp)) }, [pricePerKg]) useEffect(() => { setPricePerKg(dataset.pricePerKg || '') }, [dataset.savedAt, dataset.pricePerKg]) + const isSaveDisabled = + !pricePerKg || + JSON.stringify(dataset) === + JSON.stringify( + getSavedDatasets().find( + data => data.savedAt === dataset.savedAt, + ), + ) + + function handleSave() { + const savedDatasets = getSavedDatasets() + + if (dataset.savedAt) { + const delIndex = savedDatasets.findIndex( + data => data.savedAt === dataset.savedAt, + ) + savedDatasets.splice(delIndex, 1) + } + + dataset.savedAt = moment().format() + dataset.finalWeight = + summaryData[0]?.weight - + summaryData[1]?.weight + + summaryData[2]?.weight + dataset.finalWorth = + summaryData[0]?.worth - + summaryData[1]?.worth + + summaryData[2]?.worth + + setFormValues({ ...dataset }) + savedDatasets.push({ ...dataset } as any) + + localStorage.setItem('savedDatasets', JSON.stringify(savedDatasets)) + } + return ( <> { variant="outlined" onClick={() => { // GALog('click_reset'); - vars.activeStep[1](0) - vars.formValues[1]({}) + setActiveStep(0) + setFormValues({}) }}> Ulangi @@ -126,48 +117,8 @@ const ResultBox = () => { - data.savedAt === - dataset.savedAt, - ), - ) - } - onClick={() => { - const savedDatasets = getSavedDatasets() - - if (dataset.savedAt) { - const delIndex = - savedDatasets.findIndex( - data => - data.savedAt === - dataset.savedAt, - ) - savedDatasets.splice(delIndex, 1) - } - - dataset.savedAt = moment().format() - dataset.finalWeight = - summaryData[0]?.weight - - summaryData[1]?.weight + - summaryData[2]?.weight - dataset.finalWorth = - summaryData[0]?.worth - - summaryData[1]?.worth + - summaryData[2]?.worth - - vars.formValues[1]({ ...dataset }) - savedDatasets.push({ ...dataset }) - - window.localStorage.setItem( - 'savedDatasets', - JSON.stringify(savedDatasets), - ) - }}> + disabled={isSaveDisabled} + onClick={handleSave}> @@ -195,7 +146,43 @@ const ResultBox = () => { ) } -export default ResultBox +function getSummaryData(detailsCalculation: AnuType[], dataset: any) { + const summaryDataset: TypeB[] = [ + { + name: 'Tandan Buah Segar', + weight: dataset.totalWeight, + worth: dataset.totalWeight * dataset.pricePerKg, + tooltip: `${numberFormat(dataset.totalWeight)} × ${currencyFormat( + dataset.pricePerKg, + )}`, + }, + ] + + const { totalCutWorth, totalAddWorth } = detailsCalculation.reduce( + (acc, detail) => ({ + totalCutWorth: acc.totalCutWorth + detail.cutWorth, + totalAddWorth: acc.totalAddWorth + detail.addWorth, + }), + { + totalCutWorth: 0, + totalAddWorth: 0, + }, + ) + + summaryDataset.push({ + name: 'Potongan', + weight: totalCutWorth / dataset.pricePerKg, + worth: totalCutWorth, + }) + + summaryDataset.push({ + name: 'Insentif', + weight: totalAddWorth / dataset.pricePerKg, + worth: totalAddWorth, + }) + + return summaryDataset +} interface AnuType { qty: number diff --git a/src/components/saved-calculation.tsx b/src/components/saved-calculation.tsx index 437dc65..80d1919 100644 --- a/src/components/saved-calculation.tsx +++ b/src/components/saved-calculation.tsx @@ -1,57 +1,41 @@ -import * as React from 'react' -import Container from '@mui/material/Container' -import Dialog from '@mui/material/Dialog' -import ListItemText from '@mui/material/ListItemText' -import ListItem from '@mui/material/ListItem' -import List from '@mui/material/List' -import Divider from '@mui/material/Divider' -import AppBar from '@mui/material/AppBar' -import Menu from '@mui/material/Menu' -import MenuItem from '@mui/material/MenuItem' -import IconButton from '@mui/material/IconButton' -import Typography from '@mui/material/Typography' -import CloseIcon from '@mui/icons-material/Close' -import Slide from '@mui/material/Slide' -import { TransitionProps } from '@mui/material/transitions' -import VisibilityIcon from '@mui/icons-material/Visibility' -import MoreVert from '@mui/icons-material/MoreVert' -import moment from 'moment' +// types +import type { TransitionProps } from '@mui/material/transitions' +// vendors +import { forwardRef, MouseEvent, ReactElement, Ref, useState } from 'react' + import { - currencyFormat, - getSavedDatasets, - numberFormat, -} from '../helpers' -import vars from '../helpers/vars' -import Tooltip from '@mui/material/Tooltip' -import ListIcon from '@mui/icons-material/List' + AppBar, + Container, + Dialog, + Divider, + IconButton, + List, + ListItem, + ListItemText, + Menu, + MenuItem, + Slide, + Tooltip, + Typography, +} from '@mui/material' -const Transition = React.forwardRef(function Transition( - props: TransitionProps & { - children: React.ReactElement - }, - ref: React.Ref, -) { - return -}) +import { + Close as CloseIcon, + List as ListIcon, + MoreVert as MoreVertIcon, + Visibility as VisibilityIcon, +} from '@mui/icons-material' +import moment from 'moment' +// helpers +import { currencyFormat, numberFormat } from '../helpers' +import { useGlobals } from '../hooks/use-globals' +import { getSavedDatasets } from '../functions/get-saved-datasets' export default function SavedCalculationDialog() { - const [open, setOpen] = React.useState(false) - - const handleClose = () => { - setOpen(false) - } + const { setFormValues, setActiveStep } = useGlobals() + const [open, setOpen] = useState(false) - const [anchorEl, setAnchorEl] = React.useState(null) - const isMoreVertOpen = Boolean(anchorEl) - const handleMoreVertClick = ( - event: React.MouseEvent, - ) => { - setAnchorEl(event.currentTarget) - } - - const handleMoreVertClose = () => { - setAnchorEl(null) - } + const handleDialogClose = () => setOpen(false) return ( <> @@ -64,7 +48,7 @@ export default function SavedCalculationDialog() { - - {getSavedDatasets() - .reverse() - .map((item: any | object, i: number) => ( -
- - { - vars.formValues[1]({ - ...item, - }) - vars.activeStep[1](2) - handleClose() - }}> - - - - - - - { - const savedCalculations = - getSavedDatasets() - savedCalculations.splice( - i, - 1, - ) - window.localStorage.setItem( - 'savedDatasets', - JSON.stringify( - savedCalculations, - ), - ) - handleMoreVertClose() - }}> - Hapus - - - - }> - - - - {i !== getSavedDatasets().length - 1 && ( - - )} -
- ))} - - {getSavedDatasets().length === 0 && ( - - Belum ada data tersimpan - - )} -
+ { + setFormValues({ + ...dataset, + }) + setActiveStep(2) + handleDialogClose() + }} + />
) } + +const Transition = forwardRef(function Transition( + props: TransitionProps & { + children: ReactElement + }, + ref: Ref, +) { + return +}) + +function DatasetsList({ onSelected }: { onSelected: (item: any) => void }) { + const savedDatasets = getSavedDatasets() + const [anchorEl, setAnchorEl] = useState(null) + + const isMoreVertOpen = Boolean(anchorEl) + const handleMoreVertClick = ({ + currentTarget, + }: MouseEvent) => { + setAnchorEl(currentTarget) + } + + const handleMoreVertClose = () => { + setAnchorEl(null) + } + + return ( + + {savedDatasets.reverse().map((item: any | object, i: number) => ( +
+ + onSelected({ ...item })}> + + + + + + + { + const savedCalculations = + getSavedDatasets() + savedCalculations.splice(i, 1) + window.localStorage.setItem( + 'savedDatasets', + JSON.stringify( + savedCalculations, + ), + ) + handleMoreVertClose() + }}> + Hapus + + + + }> + + + + {i + 1 !== savedDatasets.length && } +
+ ))} + + {savedDatasets.length === 0 && ( + + Belum ada data tersimpan + + )} +
+ ) +} diff --git a/src/functions/get-saved-datasets.ts b/src/functions/get-saved-datasets.ts new file mode 100644 index 0000000..0484105 --- /dev/null +++ b/src/functions/get-saved-datasets.ts @@ -0,0 +1,5 @@ +export function getSavedDatasets() { + const savedDatasetsString = localStorage.getItem('savedDatasets') + + return (savedDatasetsString ? JSON.parse(savedDatasetsString) : []) as any[] +} diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 308fb44..77d4606 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -24,21 +24,3 @@ export const isProduction = Boolean( process.env.NODE_ENV === 'production' && process.env.REACT_APP_ENV === 'production', ) - -export const getSavedDatasets = () => { - let savedDatasets: { - finalWorth: number - totalWeight: number - savedAt: string - }[] = [] - - const savedDatasetsString = localStorage.getItem('savedDatasets') - - try { - savedDatasets = savedDatasetsString - ? JSON.parse(savedDatasetsString) - : [] - } catch (error) {} - - return savedDatasets -} diff --git a/src/helpers/vars.ts b/src/helpers/vars.ts deleted file mode 100644 index 9d658ac..0000000 --- a/src/helpers/vars.ts +++ /dev/null @@ -1,11 +0,0 @@ -const vars: { - summaryData: any[] - formValues: any[] - activeStep: any[] -} = { - summaryData: [], - formValues: [], - activeStep: [], -} - -export default vars diff --git a/src/hooks/use-globals.ts b/src/hooks/use-globals.ts new file mode 100644 index 0000000..591efd3 --- /dev/null +++ b/src/hooks/use-globals.ts @@ -0,0 +1 @@ +export { useGlobals } from '../providers/components/globals-provider' diff --git a/src/main.tsx b/src/main.tsx index 27aa2fb..4f61b9d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,30 +1,17 @@ -/* eslint-disable react-refresh/only-export-components */ - import '@fontsource/roboto/300.css' import '@fontsource/roboto/400.css' import '@fontsource/roboto/500.css' import '@fontsource/roboto/700.css' -import createTheme from '@mui/material/styles/createTheme' -import ThemeProvider from '@mui/material/styles/ThemeProvider' -import red from '@mui/material/colors/red' - import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' +import { Providers } from './providers' import App from './app' -const THEME = createTheme({ - palette: { - primary: { - main: red.A700, - }, - }, -}) - createRoot(document.getElementById('root')!).render( - + - + , ) diff --git a/src/providers/components/globals-provider.tsx b/src/providers/components/globals-provider.tsx new file mode 100644 index 0000000..7f8fde3 --- /dev/null +++ b/src/providers/components/globals-provider.tsx @@ -0,0 +1,46 @@ +import { + createContext, + Dispatch, + ReactNode, + SetStateAction, + useContext, + useState, +} from 'react' + +const DEFAULT_GLOBAL_VARIABLES: { + activeStep: number + setActiveStep: Dispatch> + + formValues: Record + setFormValues: Dispatch>> +} = { + activeStep: 0, + setActiveStep: () => {}, + + formValues: {}, + setFormValues: () => {}, +} + +const CONTEXT = createContext(DEFAULT_GLOBAL_VARIABLES) + +export function GlobalsProvider({ children }: { children: ReactNode }) { + const [activeStep, setActiveStep] = useState(0) + const [formValues, setFormValues] = useState({}) + + return ( + + {children} + + ) +} + +export function useGlobals() { + return useContext(CONTEXT) +} diff --git a/src/providers/index.tsx b/src/providers/index.tsx new file mode 100644 index 0000000..27a2b46 --- /dev/null +++ b/src/providers/index.tsx @@ -0,0 +1,20 @@ +import type { ReactNode } from 'react' +import { createTheme, ThemeProvider } from '@mui/material' +import { red } from '@mui/material/colors' +import { GlobalsProvider } from './components/globals-provider' + +const THEME = createTheme({ + palette: { + primary: { + main: red[500], + }, + }, +}) + +export function Providers({ children }: { children: ReactNode }) { + return ( + + {children} + + ) +}