diff --git a/packages/semantic-ui/src/components/ReferenceCodeFormDropdown.js b/packages/semantic-ui/src/components/ReferenceCodeFormDropdown.js new file mode 100644 index 00000000..aff4cc79 --- /dev/null +++ b/packages/semantic-ui/src/components/ReferenceCodeFormDropdown.js @@ -0,0 +1,81 @@ +// @flow + +import { ReferenceTablesService } from '@performant-software/shared-components'; +import React, { type ComponentType, useEffect, useState } from 'react'; +import { Form } from 'semantic-ui-react'; +import EditModal from './EditModal'; +import ReferenceCodeDropdown from './ReferenceCodeDropdown'; +import ReferenceCodeFormLabel from './ReferenceCodeFormLabel'; +import ReferenceTableModal from './ReferenceTableModal'; + +type Props = { + error?: boolean, + label?: string, + required?: boolean, + referenceTable: string +}; + +const ReferenceCodeFormDropdown: ComponentType = (props: Props) => { + const { + error, + label, + required, + referenceTable: key, + ...rest + } = props; + + const [modal, setModal] = useState(false); + const [dropdownKey, setDropdownKey] = useState(0); + const [referenceTable, setReferenceTable] = useState({ key }); + + /** + * Looks up the existing reference table base on the passed key. + */ + useEffect(() => ( + ReferenceTablesService + .fetchByKey(key) + .then(({ data }) => setReferenceTable((prevTable) => ({ + ...prevTable, + ...data.reference_table + }))) + ), [key]); + + return ( + <> + setModal(true)} + referenceTable={referenceTable.key} + /> + )} + required={required} + > + + + { modal && ( + setModal(false)} + onSave={(record) => ( + ReferenceTablesService + .save(record) + .then(({ data }) => data.reference_table) + .then(() => setDropdownKey((prevKey) => prevKey + 1)) + .finally(() => setModal(false)) + )} + /> + )} + + ); +}; + +export default ReferenceCodeFormDropdown; diff --git a/packages/semantic-ui/src/components/ReferenceCodeFormLabel.js b/packages/semantic-ui/src/components/ReferenceCodeFormLabel.js new file mode 100644 index 00000000..9f7917b5 --- /dev/null +++ b/packages/semantic-ui/src/components/ReferenceCodeFormLabel.js @@ -0,0 +1,51 @@ +// @flow + +import React, { type ComponentType } from 'react'; +import { withTranslation } from 'react-i18next'; +import { + Button, + Header, + Icon, + Popup +} from 'semantic-ui-react'; +import i18n from '../i18n/i18n'; + +type Props = { + label: string, + onClick: () => void, + referenceTable: string +}; + +const ReferenceCodeFormLabel: ComponentType = withTranslation()((props: Props) => ( +
+ + + )} + > +
+

{ i18n.t('ReferenceCodeFormLabel.content', { name: props.label })}

+
+)); + +export default ReferenceCodeFormLabel; diff --git a/packages/semantic-ui/src/i18n/en.json b/packages/semantic-ui/src/i18n/en.json index eb4d3b25..1d708842 100644 --- a/packages/semantic-ui/src/i18n/en.json +++ b/packages/semantic-ui/src/i18n/en.json @@ -193,6 +193,9 @@ "loginErrorHeader": "Invalid Credentials", "password": "Password" }, + "ReferenceCodeFormLabel": { + "content": "The values in this list can be edited via the {{name}} reference table." + }, "ReferenceCodeModal": { "labels": { "name": "Name" diff --git a/packages/semantic-ui/src/index.js b/packages/semantic-ui/src/index.js index 5f534dc8..b1901635 100644 --- a/packages/semantic-ui/src/index.js +++ b/packages/semantic-ui/src/index.js @@ -60,6 +60,8 @@ export { default as NestedAccordion } from './components/NestedAccordion'; export { default as PlayButton } from './components/PlayButton'; export { default as PhotoViewer } from './components/PhotoViewer'; export { default as ReferenceCodeDropdown } from './components/ReferenceCodeDropdown'; +export { default as ReferenceCodeFormDropdown } from './components/ReferenceCodeFormDropdown'; +export { default as ReferenceCodeFormLabel } from './components/ReferenceCodeFormLabel'; export { default as ReferenceCodeModal } from './components/ReferenceCodeModal'; export { default as ReferenceTableModal } from './components/ReferenceTableModal'; export { default as ReferenceTablesList } from './components/ReferenceTablesList'; diff --git a/packages/shared/src/index.js b/packages/shared/src/index.js index 0e2fad90..6d1ef5d5 100644 --- a/packages/shared/src/index.js +++ b/packages/shared/src/index.js @@ -20,6 +20,7 @@ export { default as Attachments } from './transforms/Attachments'; export { default as BaseTransform } from './transforms/BaseTransform'; export { default as FormDataTransform } from './transforms/FormDataTransform'; export { default as NestedAttributesTransform } from './transforms/NestedAttributesTransform'; +export { default as References } from './transforms/References'; // Utils export { default as Browser } from './utils/Browser'; diff --git a/packages/shared/src/services/ReferenceTables.js b/packages/shared/src/services/ReferenceTables.js index 232bc9b2..567f1a42 100644 --- a/packages/shared/src/services/ReferenceTables.js +++ b/packages/shared/src/services/ReferenceTables.js @@ -7,6 +7,17 @@ import ReferenceTable from '../transforms/ReferenceTable'; * Class responsible for handling all reference table API requests. */ class ReferenceTables extends BaseService { + /** + * Calls the find_by_key API end point for reference tables. + * + * @param key + * + * @returns {Promise>} + */ + fetchByKey(key) { + return this.getAxios().get(`${this.getBaseUrl()}/find_by_key`, { params: { key } }); + } + /** * Returns the reference tables base URL. * diff --git a/packages/shared/src/transforms/References.js b/packages/shared/src/transforms/References.js new file mode 100644 index 00000000..a683fc94 --- /dev/null +++ b/packages/shared/src/transforms/References.js @@ -0,0 +1,14 @@ +// @flow + +import NestedAttributesTransform from './NestedAttributesTransform'; + +class References extends NestedAttributesTransform { + getPayloadKeys(): Array { + return [ + 'reference_code_id' + ]; + } +} + +const ReferencesTransform: References = new References(); +export default ReferencesTransform; diff --git a/packages/storybook/.storybook/middleware.js b/packages/storybook/.storybook/middleware.js new file mode 100644 index 00000000..e7db42e9 --- /dev/null +++ b/packages/storybook/.storybook/middleware.js @@ -0,0 +1,13 @@ +// @flow + +const bodyParser = require('body-parser'); +const ControlledVocabulary = require('./routes/ControlledVocabulary'); + +const expressMiddleWare = (router) => { + router.use(bodyParser.urlencoded({ extended: false })); + router.use(bodyParser.json()); + + ControlledVocabulary.addRoutes(router); +}; + +module.exports = expressMiddleWare; diff --git a/packages/storybook/.storybook/routes/ControlledVocabulary.js b/packages/storybook/.storybook/routes/ControlledVocabulary.js new file mode 100644 index 00000000..22c90d26 --- /dev/null +++ b/packages/storybook/.storybook/routes/ControlledVocabulary.js @@ -0,0 +1,51 @@ +// @flow + +const addRoutes = (router) => { + const codes = [{ + id: 1, + reference_table_id: 1, + name: 'Boston, MA' + }, { + id: 2, + reference_table_id: 1, + name: 'New York, NY' + }, { + id: 3, + reference_table_id: 1, + name: 'Los Angeles, CA' + }]; + + router.get('/controlled_vocabulary/reference_codes', (request, response) => { + response.send({ + list: { + count: codes.length, + page: 1, + pages: 1 + }, + reference_codes: codes + }); + + response.end(); + }); + + router.get('/controlled_vocabulary/reference_tables/:id', (request, response) => { + response.send({ + reference_table: { + id: 1, + name: 'Locations', + key: 'locations', + reference_codes: codes + } + }) + + response.end(); + }); + + router.put('/controlled_vocabulary/reference_tables/:id', (request, response) => { + response.end(); + }); +}; + +module.exports = { + addRoutes +}; diff --git a/packages/storybook/src/semantic-ui/ReferenceCodeDropdown.stories.js b/packages/storybook/src/semantic-ui/ReferenceCodeDropdown.stories.js new file mode 100644 index 00000000..0e01dcf9 --- /dev/null +++ b/packages/storybook/src/semantic-ui/ReferenceCodeDropdown.stories.js @@ -0,0 +1,39 @@ +// @flow + +import React, { useState } from 'react'; +import { action } from '@storybook/addon-actions'; +import { withA11y } from '@storybook/addon-a11y'; +import { withKnobs } from '@storybook/addon-knobs'; +import ReferenceCodeDropdown from '../../../semantic-ui/src/components/ReferenceCodeDropdown'; + +export default { + title: 'Components/Semantic UI/ReferenceCodeDropdown', + decorators: [withA11y, withKnobs] +}; + +export const Default = () => ( + +); + +export const Multiple = () => { + const [value, setValue] = useState([]); + + return ( + setValue(selected)} + referenceTable='locations' + value={value} + /> + ); +}; diff --git a/packages/storybook/src/semantic-ui/ReferenceCodeFormDropdown.stories.js b/packages/storybook/src/semantic-ui/ReferenceCodeFormDropdown.stories.js new file mode 100644 index 00000000..f1c4189d --- /dev/null +++ b/packages/storybook/src/semantic-ui/ReferenceCodeFormDropdown.stories.js @@ -0,0 +1,27 @@ +// @flow + +import React from 'react'; +import { withA11y } from '@storybook/addon-a11y'; +import { withKnobs } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { Form } from 'semantic-ui-react'; +import ReferenceCodeFormDropdown from '../../../semantic-ui/src/components/ReferenceCodeFormDropdown'; +import useDragDrop from '../../../shared/src/utils/DragDrop'; + +export default { + title: 'Components/Semantic UI/ReferenceCodeFormDropdown', + decorators: [withA11y, withKnobs] +}; + +export const Default = useDragDrop(() => ( +
+ + +)); diff --git a/packages/storybook/src/semantic-ui/ReferenceCodeFormLabel.stories.js b/packages/storybook/src/semantic-ui/ReferenceCodeFormLabel.stories.js new file mode 100644 index 00000000..93549ed9 --- /dev/null +++ b/packages/storybook/src/semantic-ui/ReferenceCodeFormLabel.stories.js @@ -0,0 +1,20 @@ +// @flow + +import React from 'react'; +import { withA11y } from '@storybook/addon-a11y'; +import { withKnobs } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import ReferenceCodeFormLabel from '../../../semantic-ui/src/components/ReferenceCodeFormLabel'; + +export default { + title: 'Components/Semantic UI/ReferenceCodeFormLabel', + decorators: [withA11y, withKnobs] +}; + +export const Default = () => ( + +);