Add autofill dropdown to data entry
This commit is contained in:
parent
cf5906dc89
commit
4a098ad57c
@ -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 {
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
@ -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<AutofillDropdownProps> = props => {
|
||||
const [options, setOptions] = useState<AutofillOption[]>(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 (
|
||||
<div className="autofill-dropdown">
|
||||
{
|
||||
options.map(option =>
|
||||
<div
|
||||
onMouseDown={e => /* 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})
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
@ -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<TextDataEntryInputProps & {value?: string}> = props => {
|
||||
export const DataEntryInput: React.FC<TextDataEntryInputProps & {value?: string}> = 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 (
|
||||
<input className="form-control" type="text"
|
||||
id={props.slug}
|
||||
name={props.slug}
|
||||
value={props.value || ''}
|
||||
maxLength={props.maxLength}
|
||||
disabled={props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
onChange={e => {
|
||||
const transform = props.valueTransform || (x => x);
|
||||
const val = e.target.value === '' ?
|
||||
null :
|
||||
transform(e.target.value);
|
||||
props.onChange(props.slug, val);
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
onFocus={e => setEditing(true)}
|
||||
onBlur={e => setEditing(false)}
|
||||
>
|
||||
<input className="form-control" type="text"
|
||||
name={props.slug}
|
||||
value={props.value || ''}
|
||||
maxLength={props.maxLength}
|
||||
disabled={props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
/>
|
||||
{
|
||||
props.autofill &&
|
||||
<AutofillDropdown
|
||||
editing={isEditing}
|
||||
onSelect={value => handleChange(value)}
|
||||
onClose={() => setEditing(false)}
|
||||
fieldName={props.slug}
|
||||
fieldValue={props.value}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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<DataEntryProps> = (props) => {
|
||||
disabled={props.disabled || props.value == undefined || props.value == ''}
|
||||
copy={props.copy}
|
||||
/>
|
||||
<TextDataEntryInput
|
||||
<DataEntryInput
|
||||
slug={props.slug}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { sanitiseURL } from '../../helpers';
|
||||
|
||||
import { BaseDataEntryProps } from './data-entry';
|
||||
import { TextDataEntryInput, TextDataEntryInputProps } from './data-entry-input';
|
||||
import { DataEntryInput, TextDataEntryInputProps } from './data-entry-input';
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
|
||||
|
||||
@ -75,8 +73,8 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
|
||||
{
|
||||
values.map((val, i) => (
|
||||
<li className="input-group" key={i}>
|
||||
<TextDataEntryInput
|
||||
slug={`${props.slug}-${i}`}
|
||||
<DataEntryInput
|
||||
slug={props.slug}
|
||||
value={val}
|
||||
disabled={isDisabled}
|
||||
onChange={(key, val) => this.edit(i, val)}
|
||||
@ -84,6 +82,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
|
||||
maxLength={props.maxLength}
|
||||
placeholder={props.placeholder}
|
||||
valueTransform={props.valueTransform}
|
||||
autofill={props.autofill}
|
||||
/>
|
||||
{
|
||||
!isDisabled &&
|
||||
@ -100,8 +99,8 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
|
||||
{
|
||||
!isDisabled &&
|
||||
<div className="input-group">
|
||||
<TextDataEntryInput
|
||||
slug='new'
|
||||
<DataEntryInput
|
||||
slug={props.slug}
|
||||
value={this.state.newValue}
|
||||
disabled={props.disabled}
|
||||
onChange={(key, val) => this.setNewValue(val)}
|
||||
@ -109,6 +108,7 @@ class MultiDataEntry extends Component<MultiDataEntryProps, MultiDataEntryState>
|
||||
maxLength={props.maxLength}
|
||||
placeholder={props.placeholder}
|
||||
valueTransform={props.valueTransform}
|
||||
autofill={props.autofill}
|
||||
/>
|
||||
<div className="input-group-append">
|
||||
<button type="button" onClick={this.addNew}
|
||||
|
@ -13,22 +13,22 @@ import { CategoryViewProps } from './category-view-props';
|
||||
*/
|
||||
const UseView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
<Fragment>
|
||||
<InfoBox msg="This category is currently read-only. We are working on enabling its editing soon." />
|
||||
<MultiDataEntry
|
||||
title={dataFields.current_landuse_class.title}
|
||||
slug="current_landuse_class"
|
||||
value={props.building.current_landuse_class}
|
||||
mode="view"
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
// tooltip={dataFields.current_landuse_class.tooltip}
|
||||
placeholder="New land use class..."
|
||||
autofill={true}
|
||||
/>
|
||||
<MultiDataEntry
|
||||
title={dataFields.current_landuse_group.title}
|
||||
slug="current_landuse_group"
|
||||
value={props.building.current_landuse_group}
|
||||
mode="view"
|
||||
mode={props.mode}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
// tooltip={dataFields.current_landuse_class.tooltip}
|
||||
@ -38,7 +38,8 @@ const UseView: React.FunctionComponent<CategoryViewProps> = (props) => (
|
||||
title={dataFields.current_landuse_order.title}
|
||||
slug="current_landuse_order"
|
||||
value={props.building.current_landuse_order}
|
||||
mode="view"
|
||||
mode={props.mode}
|
||||
disabled={true}
|
||||
copy={props.copy}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user