diff --git a/app/src/api/controllers/autofillController.ts b/app/src/api/controllers/autofillController.ts index 527979d1..2514a17d 100644 --- a/app/src/api/controllers/autofillController.ts +++ b/app/src/api/controllers/autofillController.ts @@ -6,7 +6,7 @@ const getAutofillOptions = asyncController(async (req, res) => { const options = await autofillService.getAutofillOptions(field_name, field_value); - res.send(options); + res.send({ options: options }); }); export default { diff --git a/app/src/api/services/autofill.ts b/app/src/api/services/autofill.ts index 8c296a27..41e78174 100644 --- a/app/src/api/services/autofill.ts +++ b/app/src/api/services/autofill.ts @@ -7,7 +7,7 @@ const autofillFunctionMap = { function getLanduseClassOptions(value: string) { return db.manyOrNone(` SELECT landuse_id AS id, description as value, similarity(description, $1) AS similarity - FROM reference_tables.building_landuse_class + FROM reference_tables.buildings_landuse_class WHERE description % $1 ORDER BY similarity DESC, description `, [value] diff --git a/app/src/frontend/building/data-components/autofill/autofillDropdown.css b/app/src/frontend/building/data-components/autofill/autofillDropdown.css new file mode 100644 index 00000000..4257d37c --- /dev/null +++ b/app/src/frontend/building/data-components/autofill/autofillDropdown.css @@ -0,0 +1,9 @@ +.autofill-dropdown { + position: absolute; + z-index: 1000; + background-color: white; + max-height: 5em; + width: 100%; + overflow-y: scroll; + overflow-x: hidden; +} \ No newline at end of file diff --git a/app/src/frontend/building/data-components/autofill/autofillDropdown.tsx b/app/src/frontend/building/data-components/autofill/autofillDropdown.tsx new file mode 100644 index 00000000..ac0d500c --- /dev/null +++ b/app/src/frontend/building/data-components/autofill/autofillDropdown.tsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState } from 'react'; + +import './autofillDropdown.css'; + +import { apiGet } from '../../../apiHelpers'; + +interface AutofillDropdownProps { + fieldName: string; + fieldValue: string; + editing: boolean; + onSelect: (val: string) => void; + onClose: () => void; +} + +interface AutofillOption { + id: string; + value: string; + similarity: number; +} + +export const AutofillDropdown: React.FC = props => { + const [options, setOptions] = useState(null); + + useEffect(() => { + const doAsync = async () => { + if (!props.editing || props.fieldValue === '') return setOptions(null); + + const url = `/api/autofill?field_name=${props.fieldName}&field_value=${props.fieldValue}`; + const { options } = await apiGet(url); + + if (!props.editing) return; + + setOptions(options); + }; + + doAsync(); + }, [props.editing, props.fieldName, props.fieldValue]); + + if (!props.editing || options == undefined) return null; + + return ( +
+ { + options.map(option => +
/* prevent input blur */ e.preventDefault()} + onClick={e => { + props.onSelect(option.value); + // close dropdown manually rather than through input blur + props.onClose(); + }} + > + {option.value} ({option.id}) +
) + } +
+ ); + +}; diff --git a/app/src/frontend/building/data-components/data-entry-input.tsx b/app/src/frontend/building/data-components/data-entry-input.tsx index 09c02b90..56a0a8e6 100644 --- a/app/src/frontend/building/data-components/data-entry-input.tsx +++ b/app/src/frontend/building/data-components/data-entry-input.tsx @@ -1,30 +1,54 @@ -import React from 'react'; +import React, { useState } from 'react'; + +import { AutofillDropdown } from './autofill/autofillDropdown'; export interface TextDataEntryInputProps { slug: string; + name?: string; + onChange?: (key: string, val: any) => void; + maxLength?: number; disabled?: boolean; placeholder?: string; valueTransform?: (val: string) => string; - onChange?: (key: string, val: any) => void; + autofill?: boolean; } -export const TextDataEntryInput: React.FC = props => { +export const DataEntryInput: React.FC = props => { + const [isEditing, setEditing] = useState(false); + + const handleChange = (value: string) => { + console.log(value); + const transform = props.valueTransform || (x => x); + const transformedValue = value === '' ? + null : + transform(value); + props.onChange(props.slug, transformedValue); + }; + return ( - { - const transform = props.valueTransform || (x => x); - const val = e.target.value === '' ? - null : - transform(e.target.value); - props.onChange(props.slug, val); - }} - /> +
setEditing(true)} + onBlur={e => setEditing(false)} + > + handleChange(e.target.value)} + /> + { + props.autofill && + handleChange(value)} + onClose={() => setEditing(false)} + fieldName={props.slug} + fieldValue={props.value} + /> + } +
); }; diff --git a/app/src/frontend/building/data-components/data-entry.tsx b/app/src/frontend/building/data-components/data-entry.tsx index 071d17c6..7c45bca0 100644 --- a/app/src/frontend/building/data-components/data-entry.tsx +++ b/app/src/frontend/building/data-components/data-entry.tsx @@ -2,7 +2,7 @@ import React, { Fragment } from 'react'; import { CopyProps } from '../data-containers/category-view-props'; -import { TextDataEntryInput, TextDataEntryInputProps } from './data-entry-input'; +import { DataEntryInput, TextDataEntryInputProps } from './data-entry-input'; import { DataTitleCopyable } from './data-title'; interface BaseDataEntryProps { @@ -29,7 +29,7 @@ const DataEntry: React.FC = (props) => { disabled={props.disabled || props.value == undefined || props.value == ''} copy={props.copy} /> - { values.map((val, i) => (
  • - this.edit(i, val)} @@ -84,6 +82,7 @@ class MultiDataEntry extends Component maxLength={props.maxLength} placeholder={props.placeholder} valueTransform={props.valueTransform} + autofill={props.autofill} /> { !isDisabled && @@ -100,8 +99,8 @@ class MultiDataEntry extends Component { !isDisabled &&
    - this.setNewValue(val)} @@ -109,6 +108,7 @@ class MultiDataEntry extends Component maxLength={props.maxLength} placeholder={props.placeholder} valueTransform={props.valueTransform} + autofill={props.autofill} />