Website/db/app/core/components/SearchArea.tsx
2022-04-08 12:00:26 -04:00

208 lines
8.1 KiB
TypeScript

import {ReactElement, SyntheticEvent, useCallback, useEffect, useState} from 'react'
import {Autocomplete, Box, CircularProgress, Theme, Typography,} from '@mui/material'
import {makeStyles} from '@mui/styles'
import TextField from '@mui/material/TextField'
import {getItem, getItemHierarchy, searchItem} from '../dataaccess/ItemBackend';
import {ItemOption} from '../dto/ItemOption'
import {useGlobalState} from '../state/GlobalState'
// import {useNavigate, useParams} from "react-router-dom";
import {useRouter, dynamic } from "blitz";
let ReactJson
if (process.browser){
ReactJson = dynamic(() => import("react-json-view"))
}
interface IItemOption {
id: string
name: string
shortName?: string
}
const useStyles = makeStyles((theme: Theme) => ({
searchAreaHolder: {
display: 'flex',
flexGrow: 1,
flexDirection: 'column',
background: theme.palette.background.paper,
padding: '2vh 2vw 2vh 2vw',
},
jsonHolder: {
display: 'flex',
flexGrow: 1,
alignItems: 'center',
flexDirection: 'column',
background: theme.palette.background.paper,
maxHeight: '80vh',
},
autocomplete: {},
}))
export const SearchArea = () => {
const classes = useStyles();
const router = useRouter()
const params = router.query;
const preferedLocale = useGlobalState((state) => state.preferedLocale)
const preferedJsonViewerTheme = useGlobalState(
useCallback((state) => state.preferedJsonViewerTheme, []),
)
const [searchInputState, setSearchInput] = useGlobalState((state) => [
state.searchInput,
state.setSearchInput,
])
const [setHierarchy, initHierarchy] = useGlobalState((state) => [state.setHierarchy, state.initHierarchy])
const [selectedItem, setSelectedItem] = useGlobalState((state) => [
state.selectedItem,
state.setSelectedItem,
])
const [selectOptions, setSelecteOptions] = useState<ItemOption[]>([])
const [isbusy, setIsBusy] = useState<boolean>(false)
const searchThreshold = 3
const handleNameInput = useCallback(async (input: string) => {
const searchResults = await searchItem(input, preferedLocale)
const options = searchResults?.map((res) => ({
id: res.item._id,
name: res.locale.Name ? res.locale.Name : res.item._name,
shortName: JSON.stringify(res.locale.ShortName)
}))
setSelecteOptions(options ? options : [])
}, [preferedLocale])
const handleIDInput = useCallback(async (input: string) => {
const itemJson = await getItem(input, preferedLocale)
if (!itemJson) {
setSelectedItem(undefined)
setSearchInput('')
return;
}
setSelectedItem(itemJson)
const itemObj = {
id: itemJson.item._id,
name: itemJson.locale.Name ? itemJson.locale.Name : itemJson.item._name,
shortName: itemJson.locale.ShortName
}
setSelecteOptions([itemObj])
setSearchInput(itemObj.name)
// Update hierachy
const itemHierarchy = await getItemHierarchy(
itemJson.item,
preferedLocale,
)
setHierarchy(itemHierarchy ? itemHierarchy : {})
// eslint-disable-next-line
}, []) // Need to only be created on startup
useEffect(() => initHierarchy(), [initHierarchy])
useEffect(()=>{
if (selectedItem){
router.replace(`/search/${selectedItem.item._id}`)
}
},[selectedItem, router])
useEffect(() => {
if (searchInputState && searchInputState.match(/([a-z0-9]{24})/)) {
handleIDInput(searchInputState)
}
}, [handleIDInput, searchInputState])
const handleInput = useCallback(async (input: string) => {
if (!input || input.length < searchThreshold || isbusy) {
setSelectedItem(undefined)
setSelecteOptions([])
setIsBusy(false)
return
}
setIsBusy(true)
if (input.match(/([a-z0-9]{24})/)) await handleIDInput(input)
else await handleNameInput(input)
setIsBusy(false)
}, [handleIDInput, handleNameInput, isbusy, setSelectedItem])
useEffect(() => {
if (!searchInputState && params.id) {
const newId = (params.id as string).trim();
console.log(newId);
setSearchInput(newId);
(async () => await handleInput(newId))();
}
}, [params, searchInputState, setSearchInput, handleInput, router])
const formatDisplayItems = () => {
// If loading
if (isbusy) return <CircularProgress size={100}/>
// If finished loading
console.log(process.browser)
if (selectedItem && ReactJson !== undefined) {
return (<ReactJson
src={selectedItem!}
theme={preferedJsonViewerTheme}
style={{
marginTop: '2vh',
width: '100%',
overflowY: 'auto',
display: 'flex',
}}
/>)
} else return <Typography id='search-no-data'>No data to display</Typography>
// return <Typography id='search-no-data'>No data to display</Typography>
}
const findOptionValue = (option: ItemOption, value: ItemOption): boolean => {
return option.name?.toLocaleLowerCase() === value.name?.toLocaleLowerCase()
|| option.id?.toLocaleLowerCase() === value.id?.toLocaleLowerCase()
|| option.shortName?.toLocaleLowerCase() === value.shortName?.toLocaleLowerCase()
}
return (
<Box className={classes.searchAreaHolder}>
<Autocomplete
id='search-autocomplete'
options={selectOptions.map((elt) => ({name: elt.name, shortName: elt.shortName, id: elt.id}))}
getOptionLabel={(option) => (option.name ? option.name : option.id) as string}
isOptionEqualToValue={(option, value) => findOptionValue(option, value)}
open={!isbusy && searchInputState.length >= searchThreshold && (searchInputState !== selectedItem?.locale.Name && searchInputState !== selectedItem?.item._name)}
className={classes.autocomplete}
inputValue={searchInputState ? searchInputState : ''}
onInputChange={async (evt: SyntheticEvent, newValue: string) => {
if (!evt) return
setSelectedItem(undefined)
setSearchInput(newValue)
await handleInput(newValue.trim())
}}
value={(() => {
const selectedOption = selectOptions.find(elt => elt.id === searchInputState || elt.name === searchInputState);
return selectedOption ? selectedOption : null;
})()}
onChange={async (event: SyntheticEvent, newValue: IItemOption | null) => {
if (newValue) {
const selectedOption = selectOptions.find(
(elt) => elt.name === newValue.name,
)
if (selectedOption) await handleIDInput(selectedOption.id)
}
}}
renderInput={(params) => (
<TextField {...params} label="Search by name or ID"/>
)}
renderOption={(props, option ) => (
<li {...props} key={option.id}><Typography>{option.name}</Typography></li>
)}
filterOptions={(options, state) => options.filter(elt => {
return (elt.name?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase())
|| elt.id?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase())
|| elt.shortName?.toLocaleLowerCase().includes(state.inputValue.toLocaleLowerCase()))
})}
filterSelectedOptions
/>
<Box className={classes.jsonHolder}>{formatDisplayItems()}</Box>
</Box>
)
}