diff --git a/src/hooks/useDb.ts b/src/hooks/useDb.ts index 4e12d5b..7d079c9 100644 --- a/src/hooks/useDb.ts +++ b/src/hooks/useDb.ts @@ -110,12 +110,32 @@ const useDb = () => { } }; + const autocomplete = async (attribute: string, search: string) => { + try { + const items = await client.records.getFullList(Collections.Tunes, 10, { + filter: `${attribute} ~ "${search}"`, + }); + + return Promise.resolve(items as TunesRecordFull[]); + } catch (error) { + if ((error as ClientResponseError).isAbort) { + return Promise.reject(new Error('Cancelled')); + } + + Sentry.captureException(error); + databaseGenericError(new Error(formatError(error))); + + return Promise.reject(error); + } + }; + return { updateTune: (tuneId: string, data: TunesRecordPartial): Promise => updateTune(tuneId, data), createTune: (data: TunesRecord): Promise => createTune(data), getTune: (tuneId: string): Promise => getTune(tuneId), getIni: (tuneId: string): Promise => getIni(tuneId), searchTunes: (search: string, page: number, perPage: number): Promise<{ items: TunesRecordFull[]; totalItems: number }> => searchTunes(search, page, perPage), + autocomplete: (attribute: string, search: string): Promise => autocomplete(attribute, search), }; }; diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index b7f15a1..4706ea6 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -18,6 +18,7 @@ import { Typography, Upload, Form, + AutoComplete, } from 'antd'; import { PlusOutlined, @@ -33,6 +34,7 @@ import { GlobalOutlined, EyeOutlined, } from '@ant-design/icons'; +import debounce from 'lodash.debounce'; import { INI } from '@hyper-tuner/ini'; import { UploadRequestOption } from 'rc-upload/lib/interface'; import { UploadFile } from 'antd/lib/upload/interface'; @@ -138,7 +140,24 @@ const UploadPage = () => { const { currentUser, refreshUser } = useAuth(); const navigate = useNavigate(); const { fetchTuneFile } = useServerStorage(); - const { createTune, updateTune, getTune } = useDb(); + const { createTune, updateTune, getTune, autocomplete } = useDb(); + + const [autocompleteOptions, setAutocompleteOptions] = useState<{ [attribute: string]: { value: string }[] }>({}); + + const searchAutocomplete = debounce(async (attribute: string, search: string) => { + if (search.length === 0) { + setAutocompleteOptions((prev) => ({ ...prev, [attribute]: [] })); + return; + } + + const options = (await autocomplete(attribute, search)) + .map((record) => record[attribute]); + + // TODO: order by occurrence (more common - higher in the list) + const unique = [...new Set(options)].map((value) => ({ value })); + + setAutocompleteOptions((prev) => ({ ...prev, [attribute]: unique })); + }, 300); const fetchFile = async (tuneId: string, fileName: string) => bufferToFile(await fetchTuneFile(tuneId, fileName), fileName); @@ -603,19 +622,37 @@ const UploadPage = () => { - + searchAutocomplete('vehicleName', search)} + backfill + > + + - + searchAutocomplete('engineMake', search)} + backfill + > + + - + searchAutocomplete('engineCode', search)} + backfill + > + + @@ -650,12 +687,24 @@ const UploadPage = () => { - + searchAutocomplete('fuel', search)} + backfill + > + + - + searchAutocomplete('ignition', search)} + backfill + > + +