From 156dba44b62b0a746d75c2f223a37ca6de166db5 Mon Sep 17 00:00:00 2001 From: John DeBovis Date: Fri, 16 Feb 2018 14:48:05 -0500 Subject: [PATCH 001/441] add Dataset Explorer --- src/components/DatasetExplorer.jsx | 25 ++++++++++++++ src/components/FilterComponent.jsx | 25 ++++++++++++++ src/components/Nav.jsx | 7 +++- src/pages/tools/dataexplorer/_datasets.yaml | 4 +++ src/pages/tools/dataexplorer/_meta.yaml | 6 ++++ .../dataexplorer/animaldrug/filters.yaml | 33 +++++++++++++++++++ .../tools/dataexplorer/animaldrug/index.jsx | 29 ++++++++++++++++ src/pages/tools/dataexplorer/index.jsx | 32 ++++++++++++++++++ 8 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/components/DatasetExplorer.jsx create mode 100644 src/components/FilterComponent.jsx create mode 100644 src/pages/tools/dataexplorer/_datasets.yaml create mode 100644 src/pages/tools/dataexplorer/_meta.yaml create mode 100644 src/pages/tools/dataexplorer/animaldrug/filters.yaml create mode 100644 src/pages/tools/dataexplorer/animaldrug/index.jsx create mode 100644 src/pages/tools/dataexplorer/index.jsx diff --git a/src/components/DatasetExplorer.jsx b/src/components/DatasetExplorer.jsx new file mode 100644 index 00000000..602daf35 --- /dev/null +++ b/src/components/DatasetExplorer.jsx @@ -0,0 +1,25 @@ +/* @flow */ + +import React from 'react' +import _ from 'lodash' +import Hero from './Hero' + + +class DatasetExplorerComponent extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + } + } + + componentDidMount () { + } + + render (): ?React.Element { + return () + } +} + +export default DatasetExplorerComponent \ No newline at end of file diff --git a/src/components/FilterComponent.jsx b/src/components/FilterComponent.jsx new file mode 100644 index 00000000..8c5290c9 --- /dev/null +++ b/src/components/FilterComponent.jsx @@ -0,0 +1,25 @@ +/* @flow */ + +import React from 'react' +import _ from 'lodash'; + + +class FilterComponent extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + } + } + + componentDidMount () { + } + + render (): ?React.Element { + if (!this.state.data) return + return () + } +} + +export default FilterComponent \ No newline at end of file diff --git a/src/components/Nav.jsx b/src/components/Nav.jsx index 657b3295..29fcc150 100644 --- a/src/components/Nav.jsx +++ b/src/components/Nav.jsx @@ -172,6 +172,7 @@ const Nav = (props: tPROPS) => {
Research tools Downloads + Dataset Explorer
@@ -180,7 +181,9 @@ const Nav = (props: tPROPS) => { title='Community' className={activeDropdown==='Community' ? 'menu-header emphasis': path.includes('community') ? 'menu-header emphasis': 'menu-header'} onTouchStart={toggleDropdownContent} - >Community
+ > + Community +
@@ -229,3 +232,5 @@ const Nav = (props: tPROPS) => { Nav.displayName = 'components/Nav' export default NavContainer(Nav) + + diff --git a/src/pages/tools/dataexplorer/_datasets.yaml b/src/pages/tools/dataexplorer/_datasets.yaml new file mode 100644 index 00000000..30fb06b1 --- /dev/null +++ b/src/pages/tools/dataexplorer/_datasets.yaml @@ -0,0 +1,4 @@ +names: + animaldrugs: + name: Animal Drug Labeling + path: animaldrug \ No newline at end of file diff --git a/src/pages/tools/dataexplorer/_meta.yaml b/src/pages/tools/dataexplorer/_meta.yaml new file mode 100644 index 00000000..fbf5774e --- /dev/null +++ b/src/pages/tools/dataexplorer/_meta.yaml @@ -0,0 +1,6 @@ +documentTitle: openFDA › Dataset Explorer +label: Tools +title: Dataset Explorer +description: "" +path: /tools/dataexplorer/ +type: noun \ No newline at end of file diff --git a/src/pages/tools/dataexplorer/animaldrug/filters.yaml b/src/pages/tools/dataexplorer/animaldrug/filters.yaml new file mode 100644 index 00000000..1c008f3c --- /dev/null +++ b/src/pages/tools/dataexplorer/animaldrug/filters.yaml @@ -0,0 +1,33 @@ +dataset: animaldrug + filters: + name: Time Period + type: select_time + option: + - Last 7 Days + - Last 30 Days + - YTD + - Last Year + - Last 5 Years + - All Available + name: Ingredients + type: autocomplete + name: Product type + type: select + options: + - HUMAN OTC DRUG + - HUMAN PRESCRIPTION DRUG + name: Brand Name + type: autocomplete + name: Generic Name + type: autocomplete + name: Labler Name + type: autocomplete + name: NDC Code + type: autocomplete + name: Application Number + type: autocomplete + name: Drug Approval Status + type: checkbox + options: + - Approved + - Unapproved \ No newline at end of file diff --git a/src/pages/tools/dataexplorer/animaldrug/index.jsx b/src/pages/tools/dataexplorer/animaldrug/index.jsx new file mode 100644 index 00000000..cd8df0f3 --- /dev/null +++ b/src/pages/tools/dataexplorer/animaldrug/index.jsx @@ -0,0 +1,29 @@ +/* @flow */ + +import React from 'react' + + +class Explorer extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + dataset: "animaldrugs" + } + } + componentWillReceiveProps(){ + + } + componentDidMount () { + + } + + render (): ?React.Element { + + return () + } +} + + +export default Explorer \ No newline at end of file diff --git a/src/pages/tools/dataexplorer/index.jsx b/src/pages/tools/dataexplorer/index.jsx new file mode 100644 index 00000000..51b05813 --- /dev/null +++ b/src/pages/tools/dataexplorer/index.jsx @@ -0,0 +1,32 @@ +/* @flow */ + +import React from 'react' +import Hero from '../../../components/Hero/index' +import meta from './_meta.yaml' +import datasets from './_datasets.yaml' +import Select from 'react-select' + + +export default () => ( +
+ +
+
+
+
+ {"I'm Interested In: "} + + constructor (props: Object) { + super(props) + + const options = Object.keys(datasets.names).map(name => { + return datasets.names[name] + }) + + this.state = { + options: options, + choosenDataset: options[0] + } + } + componentWillReceiveProps(){ + + } + componentDidMount () { + + } + + render (): ?React.Element { + + return ( +
+ +
+
+
+
+ I'm Interested In: + +
+ ) + } +} + +class SelectFilterComponent extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + } + } + + componentDidMount () { + } + + render (): ?React.Element { + return ( +
+
+ ) + } +} + +class TimeSelectFilterComponent extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + options: [] + } + this.getDateLabels = this.getDateLabels.bind(this) + } + + componentDidMount () { + this.getDateLabels() + } + + getFormattedDate(d, style) { + let formatStr = "" + if(style === "year"){ + formatStr = "YYYY" + } else if (style === "full"){ + formatStr = 'MM/DD/YYYY' + } else if (style === "value"){ + formatStr = 'YYYYMMDD' + } + return d.format(formatStr) + } + getTimeValue(range){ + + } + + // [${year}0101+TO+${year}1231] + getDateLabels() { + const options = this.props.option.options.map(option => { + let label = "" + let value = "" + if(!true){} + else if(option === "Last 7 Days"){ + const startdate = Moment().subtract(7, "days") + const enddate = Moment() + const startdateLabel = this.getFormattedDate(startdate, "full") + const enddateLabel = this.getFormattedDate(enddate, "full") + const startdateValue = this.getFormattedDate(startdate, "value") + const enddateValue = this.getFormattedDate(enddate, "value") + + value = `[${startdateValue}+TO+${enddateValue}]` + label = `${option} (${startdateLabel} - ${enddateLabel})` + } else if(option === "Last 30 Days"){ + const enddate = Moment() + const startdate = Moment().subtract(30, "days") + const enddateLabel = this.getFormattedDate(enddate, "full") + const startdateLabel = this.getFormattedDate(startdate, "full") + const startdateValue = this.getFormattedDate(startdate, "value") + const enddateValue = this.getFormattedDate(enddate, "value") + + label = `${option} (${startdateLabel} - ${enddateLabel})` + value = `[${startdateValue}+TO+${enddateValue}]` + } else if(option === "YTD"){ + const startdate = Moment().startOf('year') + const enddate = Moment() + + const year = this.getFormattedDate(startdate, "year") + const startdateValue = this.getFormattedDate(startdate, "value") + const enddateValue = this.getFormattedDate(enddate, "value") + + value = `[${startdateValue}+TO+${enddateValue}]` + label = `${option} (${year})` + } else if(option === "Last Year"){ + const startdate = Moment().startOf('year').subtract(1, "years") + const enddate = Moment().endOf('year').subtract(1, "years") + + const year = this.getFormattedDate(startdate, "year") + const startdateValue = this.getFormattedDate(startdate, "value") + const enddateValue = this.getFormattedDate(enddate, "value") + + value = `[${startdateValue}+TO+${enddateValue}]` + label = `${option} (${year})` + } else if(option === "Last 5 Years"){ + const enddate = Moment().endOf('year') + const startdate = Moment().startOf('year').subtract(5, "years") + const startdateLabel = this.getFormattedDate(startdate, "year") + const enddateLabel = this.getFormattedDate(enddate, "year") + const startdateValue = this.getFormattedDate(startdate, "value") + const enddateValue = this.getFormattedDate(enddate, "value") + + + label = `${option} (${startdateLabel} - ${enddateLabel})` + value = `[${startdateValue}+TO+${enddateValue}]` + } else if(option === "All Available"){ + label = `${option}` + } + return { + label: label, + value: value + } + }) + + this.setState({ + options: options + }) + + } + + render (): ?React.Element { + return ( +
+
+

{this.props.option.label}

+
+ -
- ) - } + return ( +
+
+

{this.props.option.label}

+
+ + I'm Interested In: +
Date: Wed, 21 Feb 2018 17:39:11 -0500 Subject: [PATCH 006/441] merge autocomplete components --- package.json | 3 +- src/components/AutoComplete.jsx | 105 ++++++++++++++++ src/components/AutoCompleteComponent.jsx | 97 --------------- src/components/FilterComponent.jsx | 125 +++----------------- src/pages/tools/dataexplorer/_datasets.yaml | 27 +++-- 5 files changed, 144 insertions(+), 213 deletions(-) create mode 100644 src/components/AutoComplete.jsx delete mode 100644 src/components/AutoCompleteComponent.jsx diff --git a/package.json b/package.json index bf8de898..bbdca906 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "matchmedia": "^0.1.2", "moment": "2.20.1", "nib": "^1.1.2", - "nivo": "0.31.0", + "nivo": "git+ssh://git@github.com:debovis/nivo.git#add_template_label_heatmap1", "pandas-js": "0.2.4", "pondjs": "0.8.7", "rc-slider": "8.3.1", @@ -68,6 +68,7 @@ "recharts": "1.0.0-beta.0", "scroll-into-view": "^1.9.1", "stylus": "^0.54.5", + "with-query": "1.0.2", "whatwg-fetch": "2.0.3" }, "keywords": [ diff --git a/src/components/AutoComplete.jsx b/src/components/AutoComplete.jsx new file mode 100644 index 00000000..2cd636db --- /dev/null +++ b/src/components/AutoComplete.jsx @@ -0,0 +1,105 @@ +import Autosuggest from 'react-autosuggest' +import withQuery from 'with-query' +import 'whatwg-fetch' +import React from 'react' + +class AutoCompleteComponent extends React.Component { + + constructor (props: Object) { + super(props) + + this.state = { + value: '', + suggestions: [] + } + this.getSuggestions = this.getSuggestions.bind(this) + this.onChange = this.onChange.bind(this) + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this) + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this) + + } + + // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters + escapeRegexCharacters(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + renderSuggestion(suggestion) { + return ( + {suggestion} + ); + } + + getSuggestions(value) { + return fetch( + withQuery(`${this.props.url}/${this.props.endpoint}`,{ + searchField: this.props.field, + searchText: value.value, + searchType: 'autocomplete', + limit: this.props.limit + }) + ) + .then(res => res.json()) + .then((json) => { + return json.results + }) + .catch((err) => { + return [] + }) + } + + onSuggestionsFetchRequested(value){ + var that = this; + this.getSuggestions(value).then(result => { + if(!result){ + return + } + that.setState({ + suggestions: result, + value: value.value + }) + console.log("suggestions are:", result); + }) + } + + getSuggestionValue(suggestion) { + return suggestion + } + + onChange (event, { newValue, method }) { + const value = this.escapeRegexCharacters(newValue.trim()) + if(!value.length){ + this.setState({ + suggestions: [], + value + }) + } + } + + onSuggestionsClearRequested (){ + this.setState({ + suggestions: [] + }) + } + + render() { + return ( + + ); + } +} + + +AutoCompleteComponent.displayName = 'component/AutoCompleteComponent' +export default AutoCompleteComponent \ No newline at end of file diff --git a/src/components/AutoCompleteComponent.jsx b/src/components/AutoCompleteComponent.jsx deleted file mode 100644 index 7570864a..00000000 --- a/src/components/AutoCompleteComponent.jsx +++ /dev/null @@ -1,97 +0,0 @@ -import Autosuggest from 'react-autosuggest'; -import { defaultTheme } from 'react-autosuggest/dist/theme'; -import {default as $} from "jquery"; - -// https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters -function escapeRegexCharacters(str) { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -function getSuggestions(value) { - const escapedValue = escapeRegexCharacters(value.trim()); - - if (escapedValue === '') { - return []; - } - - const regex = new RegExp('^' + escapedValue, 'i'); - - const generic_name_url = "http://ec2-34-238-202-4.compute-1.amazonaws.com:8000/drug/label.json?searchField=openfda.generic_name&searchText=" + escapedValue + - "&searchType=autocomplete&limit=100"; - - return fetchJSON(generic_name_url); - -} - -function fetchJSON (url: string): Object { - var promise = new Promise((resolve, reject) => { - resolve($.getJSON(url)); - }); - - return promise; -}; - -function getSuggestionValue(suggestion) { - return suggestion; -} - -function renderSuggestion(suggestion) { - return ( - {suggestion} - ); -} - -class AutoCompleteComponent extends React.Component { - constructor() { - super(); - - this.state = { - value: '', - suggestions: [] - }; - } - - onChange = (event, { newValue, method }) => { - this.setState({ - value: newValue - }); - }; - - onSuggestionsFetchRequested = ({ value }) => { - var that = this; - getSuggestions(value).then(function (result) { - that.setState({ suggestions: result.results}) - }); - - console.log("suggestions are:" + this.state.suggestions); - }; - - onSuggestionsClearRequested = () => { - this.setState({ - suggestions: [] - }); - }; - - render() { - const { value, suggestions } = this.state; - const inputProps = { - placeholder: "Start typing " + this.props.fieldName + "...", - value, - onChange: this.onChange, - }; - - return ( - - ); - } -} - -AutoCompleteComponent.displayName = 'component/AutoCompleteComponent' -export default AutoCompleteComponent \ No newline at end of file diff --git a/src/components/FilterComponent.jsx b/src/components/FilterComponent.jsx index 6d5eb0d2..6e662e29 100644 --- a/src/components/FilterComponent.jsx +++ b/src/components/FilterComponent.jsx @@ -8,103 +8,13 @@ import 'rc-checkbox/assets/index.css' import _ from 'lodash' import Moment from 'moment' -import AutoCompleteComponent from '../components/AutoCompleteComponent' - +import AutoCompleteComponent from './AutoComplete' // autocomplete // select // time_select // checkbox -class AutoCompleteFilterComponent extends React.Component { - - constructor(props: Object) { - super(props) - - this.state = {} - - this.getOptions = this.getOptions.bind(this) - } - - componentDidMount() { - this.getOptions().then(result => { - this.setState({ - options: result - }) - }) - } - - // render (): ?React.Element { - // const field = this.props.option.field - // const output = this.props.option.options.map((label, idx) => { - // return ( - //
- //

- // - //

- //
- // ) - // }) - // return ( - //
- //
- //

{this.props.option.label}

- //
- // { - // output - // } - //
- // ) - // } - // } - - getOptions(input) { - - const brand_name_url = "http:\//ec2-54-211-65-171.compute-1.amazonaws.com:8000/drug/label.json?count=openfda.brand_name_exact" - - return fetch(brand_name_url) - .then((response) => { - return response.json(); - }).then((json) => { - const res = json.results.map(obj => { - return { - label: obj.term - } - }); - return res - }) - } - - render(): ?React.Element { - // onInputChange={this.onInputChange.bind(this)} - - return ( -
-
-

{this.props.option.label}

-
- + {" "}{ this.props.option.label } +
+ ); + } +}); + +const GravatarValue = createClass({ + propTypes: { + children: PropTypes.node, + placeholder: stringOrNode, + value: PropTypes.object, + ref: PropTypes.any + }, + render () { + var gravatarStyle = { + borderRadius: 3, + display: 'inline-block', + marginRight: 10, + position: 'relative', + top: -2, + verticalAlign: 'middle', + }; + return ( +
+ + Select {this.props.placeholder} to Compare + +
+ ); + } +}); + + +class ResultsComponent extends React.Component { + + constructor (props: Object) { + super(props) + + const shownColumnsCount = this.props.dataset.columns.filter(c => c.show).length + + this.state = { + _rows: [ + { + "effective_time": "30151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "40151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "50151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "60151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "70151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "80151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + }, + { + "effective_time": "90151102", + "inactive_ingredient": [ + "INACTIVE INGREDIENTS Sucrose" + ], + "purpose": [ + "USES Boils, Abscess, Otitis" + ], + "keep_out_of_reach_of_children": [ + "Keep this and all medication out of reach of children" + ], + "warnings": [ + "WARNINGS This product is to be used for self-limiting conditions If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional. As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product. Keep this and all medication out of reach of children Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "questions": [ + "QUESTIONS OR COMMENTS www.Rxhomeo.com | 1.888.2796642 | info@rxhomeo.com Rxhomeo, Inc 9415 Burnet Road, Suite 312, Austin, TX 78758" + ], + "spl_product_data_elements": [ + "SILICEA SILICEA SILICON DIOXIDE COLLOIDAL SILICON DIOXIDE SUCROSE" + ], + "openfda": { + "spl_id": [ + "8e9683a0-5608-4cf6-8cdc-13cfc199498f" + ], + "product_ndc": [ + "15631-0404" + ], + "substance_name": [ + "SILICON DIOXIDE" + ], + "product_type": [ + "HUMAN OTC DRUG" + ], + "route": [ + "ORAL" + ], + "is_original_packager": [ + true + ], + "package_ndc": [ + "15631-0404-4", + "15631-0404-5", + "15631-0404-2", + "15631-0404-3", + "15631-0404-0", + "15631-0404-1" + ], + "generic_name": [ + "SILICEA" + ], + "spl_set_id": [ + "0000025c-6dbf-4af7-a741-5cbacaed519a" + ], + "brand_name": [ + "SILICEA" + ], + "manufacturer_name": [ + "Rxhomeo Private Limited d.b.a. Rxhomeo, Inc" + ], + "unii": [ + "ETJ7Z6XBU4" + ] + }, + "version": "1", + "dosage_and_administration": [ + "DOSAGE Adults- Take 4 or 6 Pellets by mouth, three times daily or as suggested by physician. Children 2 years and older- take 1/2 the adult dose." + ], + "pregnancy_or_breast_feeding": [ + "As with any drug, if you are pregnant, or nursing a baby, seek professional advice before taking this product." + ], + "stop_use": [ + "If symptoms do not improve in 4 days, or worsen, discontinue use and seek assistance of health professional." + ], + "storage_and_handling": [ + "STORAGE Store in a cool dark place" + ], + "do_not_use": [ + "Do not use if capseal is broken or missing. Close the cap tightly after use." + ], + "package_label_principal_display_panel": [ + "image description" + ], + "indications_and_usage": [ + "INDICATIONS Condition listed above or as directed by the physician" + ], + "@drugtype": "human", + "set_id": "0000025c-6dbf-4af7-a741-5cbacaed519a", + "id": "8e9683a0-5608-4cf6-8cdc-13cfc199498f", + "active_ingredient": [ + "ACTIVE INGREDIENT SILICEA HPUS 2X and higher" + ] + } + ], + placeholder: `Manage Columns ${shownColumnsCount}/${this.props.dataset.columns.length}`, + choosenColumn: "" + } + } + + onColumnToggle(){ + + } + + componentDidMount () { + } + + render (): ?React.Element { + + if(!this || !this.state || !this.state._rows){ + return () + } + + return ( +
+
+

{this.state._rows.length} results

+ - {" "}{ this.props.option.label } + + {" "}{ this.props.option['Header'] }
); } }); -const GravatarValue = createClass({ - propTypes: { - children: PropTypes.node, - placeholder: stringOrNode, - value: PropTypes.object, - ref: PropTypes.any - }, - render () { - var gravatarStyle = { - borderRadius: 3, - display: 'inline-block', - marginRight: 10, - position: 'relative', - top: -2, - verticalAlign: 'middle', - }; - return ( -
- - Select {this.props.placeholder} to Compare - -
- ); - } -}); class ResultsComponent extends React.Component { @@ -91,7 +62,12 @@ class ResultsComponent extends React.Component { constructor (props: Object) { super(props) - const shownColumnsCount = this.props.dataset.columns.filter(c => c.show).length + let columns = this.props.dataset.columns + const shownColumnsCount = columns.filter(c => c.show).length + columns = columns.map((d,idx) => { + d.idx = idx + return d + }) this.state = { _rows: [ @@ -740,13 +716,50 @@ class ResultsComponent extends React.Component { ] } ], - placeholder: `Manage Columns ${shownColumnsCount}/${this.props.dataset.columns.length}`, - choosenColumn: "" + columns: columns, + placeholder: `Manage Columns ${shownColumnsCount}/${columns.length}`, + choosenColumn: "", + parser: new Json2csvParser.Parser() } + this.onColumnToggle = this.onColumnToggle.bind(this) + this.onExportChoosen = this.onExportChoosen.bind(this) } - onColumnToggle(){ + onColumnToggle(selectionObj){ + + this.state.columns[selectionObj.idx].show = !selectionObj.show + const shownColumnsCount = this.state.columns.filter(c => c.show).length + this.setState({ + columns: [...this.state.columns], + placeholder: `Manage Columns ${shownColumnsCount}/${this.state.columns.length}` + }) + } + onExportChoosen(selectionObj){ + + if(selectionObj.label === "CSV"){ + const fields = this.state.columns.filter(c => c.show).map(c => { + return { + label: c['Header'], + value: c.accessor + } + }) + const opts = { + fields, + doubleQuote: "" + }; + + try { + const csv = this.state.parser.parse(this.state._rows); + var blob = new Blob([csv], {type: "text/plain;charset=utf-8"}); + FileSaver.saveAs(blob, "download.csv"); + } catch (err) { + console.error(err); + } + } else { + var blob = new Blob(this.state._rows.map(obj => JSON.stringify(obj)), {type: "text/plain;charset=utf-8"}); + FileSaver.saveAs(blob, "download.json"); + } } componentDidMount () { @@ -768,31 +781,51 @@ class ResultsComponent extends React.Component { paddingBottom: 43 }}>

{this.state._rows.length} results

- {this.DOMNode = ref}} removeSelected={false} - valueComponent={GravatarValue} clearable={false} closeOnSelect={false} placeholder={this.state.placeholder} /> +
+ +
+ ) + } +} + class TimeSelectFilterComponent extends React.Component { constructor (props: Object) { @@ -57,6 +141,7 @@ class TimeSelectFilterComponent extends React.Component { } return d.format(formatStr) } + getTimeValue(range){ } @@ -264,6 +349,7 @@ class FilterComponent extends React.Component { } componentDidMount () { + } onChangeCheckbox(e) { @@ -287,7 +373,7 @@ class FilterComponent extends React.Component { } onChangeSelect(selectionObj, meta){ - this.props.parent.state.filters[selectionObj.idx].value = selectionObj.value + this.props.parent.state.filters[meta.idx].value = selectionObj.value this.props.parent.setState({ filters: this.props.parent.state.filters @@ -316,6 +402,16 @@ class FilterComponent extends React.Component { + ) + } else if(option.type === "select_autocomplete") { + return ( + ) } else if(option.type === "autocomplete") { diff --git a/src/pages/tools/dataexplorer/_datasets.yaml b/src/pages/tools/dataexplorer/_datasets.yaml index d34e342c..5289d558 100644 --- a/src/pages/tools/dataexplorer/_datasets.yaml +++ b/src/pages/tools/dataexplorer/_datasets.yaml @@ -6,7 +6,7 @@ names: listingLabels: label: Listing of Labels endpoint: drug/label.json - url: http://ec2-34-238-202-4.compute-1.amazonaws.com:8000 + url: http://ec2-54-211-65-171.compute-1.amazonaws.com:8000 filters: options: - label: Time Period @@ -22,43 +22,47 @@ names: - All Available - label: Inactive Ingredients field: inactive_ingredient - type: autocomplete - placeholder: Start typing to select Inactive Ingredient + type: select_autocomplete + remove_chars: inactive ingredients + can_query: false + placeholder: Select Inactive Ingredient limit: 100 - label: Active Ingredients field: active_ingredient - type: autocomplete - placeholder: Start typing to select Active Ingredient + type: select_autocomplete + remove_chars: active ingredients + can_query: false + placeholder: Select Active Ingredient limit: 100 - - label: Product type - type: select - options: - - HUMAN OTC DRUG - - HUMAN PRESCRIPTION DRUG - label: Brand Name - field: openfda.brand_name - type: autocomplete - placeholder: Start typing to select Brand Name + field: openfda.brand_name_exact + type: select_autocomplete + can_query: true + placeholder: Select Brand Name limit: 100 - label: Generic Name - field: openfda.generic_name - type: autocomplete - placeholder: Start typing to select Generic Name + field: openfda.generic_name_exact + type: select_autocomplete + can_query: true + placeholder: Select Generic Name limit: 100 - - label: Labeler Name - type: autocomplete - placeholder: Start typing to select Labeler Name + - label: Manufacturer Name + type: select_autocomplete + field: openfda.manufacturer_name_exact + placeholder: Select Manufacturer Name limit: 100 + can_query: true - label: NDC Code - type: autocomplete - field: product_ndc + type: select_autocomplete + can_query: true + field: openfda.product_ndc_exact placeholder: E.g. 59822-1 limit: 100 - - label: Application Number - type: autocomplete - field: status - placeholder: E.g. NADA14081 - limit: 100 + - label: Product type + type: checkbox + options: + - HUMAN OTC DRUG + - HUMAN PRESCRIPTION DRUG - label: Drug Approval Status type: checkbox field: status diff --git a/src/pages/tools/dataexplorer/index.jsx b/src/pages/tools/dataexplorer/index.jsx index f2fdae77..baae2a65 100644 --- a/src/pages/tools/dataexplorer/index.jsx +++ b/src/pages/tools/dataexplorer/index.jsx @@ -4,7 +4,7 @@ import React from 'react' import Hero from '../../../components/Hero/index' import FilterComponent from '../../../components/Filter' import DatasetExplorerContentComponent from '../../../components/DatasetExplorerContent' -import DatasetRetrievalService from '../../../components/DatasetRetrieval' +import DataRetrievalService from '../../../components/DataRetrieval' import meta from './_meta.yaml' import datasets from './_datasets.yaml' import Select from 'react-select' @@ -34,7 +34,9 @@ class DataExplorer extends React.Component { option.value = null return option }), - drs: new DatasetRetrievalService() + drs: new DataRetrievalService(dataset.url, dataset.endpoint), + sampleDocs: [], + _rows: [] } this.handleChange = this.handleChange.bind(this) @@ -48,11 +50,27 @@ class DataExplorer extends React.Component { this.handleChange(this.state.dataset) this.handleViewChange(this.state.view) + this.state.drs.getTopValuesByIterating().then(results => { + this.setState({ + sampleDocs: results + }) + }) + } updateResults(){ - const results = this.state.drs.convertFiltersToJson(this.state.filters) - console.log(results) + // this.state.drs.getData(this.state.filters).then(results => { + // console.log(results) + // }) + + const minimum = 0 + const maximum = 150 + const randomStart = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum; + + this.setState({ + _rows : this.state.sampleDocs.slice(randomStart) + }) + } updateState(params){ this.setState(params) @@ -148,12 +166,14 @@ class DataExplorer extends React.Component { placeholder='Select view' />
- + { !this.state.sampleDocs.length ? + : + + }
diff --git a/static/img/cancel_icon.png b/static/img/cancel_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccc0688fb3b0cbc992a917b9fc8df2da7414de7 GIT binary patch literal 7907 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 literal 0 HcmV?d00001 From 14c62a06d2becde76274e845d398e0ef94094001 Mon Sep 17 00:00:00 2001 From: John DeBovis Date: Wed, 28 Feb 2018 17:05:12 -0500 Subject: [PATCH 011/441] add multiple selections for field, show x box to remove for select box, plug in POST --- src/components/DataRetrieval.jsx | 40 +++-- src/components/Filter.jsx | 156 ++++++++++++++++---- src/pages/tools/dataexplorer/_datasets.yaml | 2 +- src/pages/tools/dataexplorer/index.jsx | 4 +- 4 files changed, 159 insertions(+), 43 deletions(-) diff --git a/src/components/DataRetrieval.jsx b/src/components/DataRetrieval.jsx index 232555ee..47c60bf3 100644 --- a/src/components/DataRetrieval.jsx +++ b/src/components/DataRetrieval.jsx @@ -9,29 +9,26 @@ class DataRetrievalService { constructor (url, endpoint) { this.url = url this.endpoint = endpoint + + this.convertFiltersToJson = this.convertFiltersToJson.bind(this) + this.getTopValues = this.getTopValues.bind(this) + this.getTopValuesByIterating = this.getTopValuesByIterating.bind(this) + this.getData = this.getData.bind(this) } convertFiltersToJson(filters){ const formattedFilters = {} - filters.forEach((filter,idx) => { - // { - // "0": { - // "key": "set_id", - // "value": ["0000025c-6dbf-4af7-a741-5cbacaed519a"] - // } - // } - if(filter.value){ + filters.filter(filter => filter.value.length ).forEach((filter,idx) => { formattedFilters[`${idx}`] = { "key": filter.field, - "value": [filter.value] + "value": filter.value } - } }) return { "data": { "queryJSON": { "searchType": "nonLLT", - "filters": formattedFilters + "filters": [formattedFilters] } } } @@ -41,13 +38,16 @@ class DataRetrievalService { return fetch( withQuery(`${this.url}/${this.endpoint}`,{ count: field + },{ + mode: 'cors' }) ) .then(res => res.json()) .then((json) => { const res = json.results.map(obj => { return { - label: obj.term + label: obj.term, + value: obj.term } }); return res @@ -86,10 +86,11 @@ class DataRetrievalService { return fetch(`${this.url}/${this.endpoint}`, { body: JSON.stringify(data), headers: { - 'content-type': 'application/json' + 'Accept': 'application/json, text/plain, *\/*', + 'Content-Type': 'text/plain' }, method: 'POST', - mode: 'no-cors' + mode: 'cors' }) .then(res => res.json()) .then(res => { @@ -99,4 +100,13 @@ class DataRetrievalService { } } -export default DataRetrievalService \ No newline at end of file +export default DataRetrievalService + + + + + + + + + diff --git a/src/components/Filter.jsx b/src/components/Filter.jsx index 604ad8a9..13ee9629 100644 --- a/src/components/Filter.jsx +++ b/src/components/Filter.jsx @@ -36,8 +36,11 @@ class SelectAutoCompleteFilterComponent extends React.Component { super(props) this.state = { + values: [], + elements: () } this.onChange = this.onChange.bind(this) + this.removeValue = this.removeValue.bind(this) } componentDidMount () { @@ -80,9 +83,90 @@ class SelectAutoCompleteFilterComponent extends React.Component { } } + removeValue(idx){ + const currentValues = this.state.values + + const selectionObj = { + label: this.state.values[idx], + value: this.state.values[idx] + } + currentValues.splice(idx, 1) + + this.setState({ + values: currentValues, + elements: currentValues.map((value,idx) => { + return ( +
+ +
+ ) + }) + }) + + this.props.onChange(selectionObj, { + field: this.props.option.field, + idx: this.props.option.idx + }) + } + onChange(selectionObj){ + const value = selectionObj.value + const currentValues = this.state.values + const currentIndex = currentValues.indexOf(value) + + // contains value already + if( currentIndex > -1 ){ + currentValues.splice(currentIndex, 1) + } else { + currentValues.push(value) + } + this.setState({ - value: selectionObj + values: currentValues, + value: this.props.option.placeholder, + elements: currentValues.map((value,idx) => { + return ( +
+ +
+ ) + }) }) if(this.props.onChange){ @@ -94,6 +178,7 @@ class SelectAutoCompleteFilterComponent extends React.Component { } render (): ?React.Element { + return (

@@ -109,11 +194,13 @@ class SelectAutoCompleteFilterComponent extends React.Component { options={this.state.options || []} clearable={false} /> + {this.state.elements}
) } } + class TimeSelectFilterComponent extends React.Component { constructor (props: Object) { @@ -149,7 +236,7 @@ class TimeSelectFilterComponent extends React.Component { getDateLabels() { const options = this.props.option.options.map(option => { let label = "" - let value = "" + let value = [] if(!true){} else if(option === "Last 7 Days"){ const startdate = Moment().subtract(7, "days") @@ -159,8 +246,8 @@ class TimeSelectFilterComponent extends React.Component { const startdateValue = this.getFormattedDate(startdate, "value") const enddateValue = this.getFormattedDate(enddate, "value") - value = `[${startdateValue}+TO+${enddateValue}]` label = `${option} (${startdateLabel} - ${enddateLabel})` + value = [`${startdateValue}`, `${enddateValue}`] } else if(option === "Last 30 Days"){ const enddate = Moment() const startdate = Moment().subtract(30, "days") @@ -170,7 +257,7 @@ class TimeSelectFilterComponent extends React.Component { const enddateValue = this.getFormattedDate(enddate, "value") label = `${option} (${startdateLabel} - ${enddateLabel})` - value = `[${startdateValue}+TO+${enddateValue}]` + value = [`${startdateValue}`, `${enddateValue}`] } else if(option === "YTD"){ const startdate = Moment().startOf('year') const enddate = Moment() @@ -179,8 +266,8 @@ class TimeSelectFilterComponent extends React.Component { const startdateValue = this.getFormattedDate(startdate, "value") const enddateValue = this.getFormattedDate(enddate, "value") - value = `[${startdateValue}+TO+${enddateValue}]` label = `${option} (${year})` + value = [`${startdateValue}`, `${enddateValue}`] } else if(option === "Last Year"){ const startdate = Moment().startOf('year').subtract(1, "years") const enddate = Moment().endOf('year').subtract(1, "years") @@ -189,8 +276,8 @@ class TimeSelectFilterComponent extends React.Component { const startdateValue = this.getFormattedDate(startdate, "value") const enddateValue = this.getFormattedDate(enddate, "value") - value = `[${startdateValue}+TO+${enddateValue}]` label = `${option} (${year})` + value = [`${startdateValue}`, `${enddateValue}`] } else if(option === "Last 5 Years"){ const enddate = Moment().endOf('year') const startdate = Moment().startOf('year').subtract(5, "years") @@ -201,13 +288,13 @@ class TimeSelectFilterComponent extends React.Component { label = `${option} (${startdateLabel} - ${enddateLabel})` - value = `[${startdateValue}+TO+${enddateValue}]` + value = [`${startdateValue}`, `${enddateValue}`] } else if(option === "All Available"){ const enddate = Moment().endOf('year') const enddateValue = this.getFormattedDate(enddate, "value") label = `${option}` - value = `[19800101+TO+${enddateValue}]` + value = ["19800101", `${enddateValue}`] } return { label: label, @@ -285,17 +372,13 @@ class CheckboxFilterComponent extends React.Component { onChange(e){ const value = e.target.value - const states = {} - this.props.option.options.forEach(label => { - let choice = null + Object.keys(this.state.states).forEach(label => { if(value === label){ - const currentValue = this.state.states[label] - choice = !currentValue ? 1 : 0 + this.state.states[label] = (!this.state.states[label] ? 1 : 0) } - states[label] = choice }) this.setState({ - states + states: this.state.states }) if(this.props.onChange){ @@ -354,15 +437,18 @@ class FilterComponent extends React.Component { onChangeCheckbox(e) { const value = e.target.value.toLowerCase() - const currentValue = this.props.parent.state.filters[e.target.filterIdx].value - let valueToSet = null + const currentValues = this.props.parent.state.filters[e.target.filterIdx].value - if(currentValue === value){ - valueToSet = null + let valueToSet = null + const currentIndex = currentValues.indexOf(value) + // contains value already + if( currentIndex > -1 ){ + currentValues.splice(currentIndex, 1) } else { - valueToSet = value + currentValues.push(value) } - this.props.parent.state.filters[e.target.filterIdx].value = valueToSet + + this.props.parent.state.filters[e.target.filterIdx].value = currentValues this.props.parent.setState({ filters: this.props.parent.state.filters @@ -373,13 +459,32 @@ class FilterComponent extends React.Component { } onChangeSelect(selectionObj, meta){ - this.props.parent.state.filters[meta.idx].value = selectionObj.value + const value = selectionObj.value + const currentValues = this.props.parent.state.filters[meta.idx].value + const currentIndex = currentValues.indexOf(value) + + // contains value already + if( currentIndex > -1 ){ + currentValues.splice(currentIndex, 1) + } else { + currentValues.push(value) + } + + this.props.parent.state.filters[meta.idx].value = currentValues this.props.parent.setState({ filters: this.props.parent.state.filters }) } + onChangeTimeSelect(selectionObj, meta){ + this.parent.state.filters[meta.idx].value = selectionObj.value + + this.parent.setState({ + filters: this.parent.state.filters + }) + } + render (): ?React.Element { if(!this.props.parent.state.dataset.filters.options || !this.props.parent.state.dataset.filters.options.length) { @@ -394,7 +499,8 @@ class FilterComponent extends React.Component { ) } else if(option.type === "select") { @@ -446,7 +552,7 @@ class FilterComponent extends React.Component { return (
this.props.parent.state.drs.getData(this.props.parent.state.filters) } style={{ backgroundColor: "lightgrey" }} diff --git a/src/pages/tools/dataexplorer/_datasets.yaml b/src/pages/tools/dataexplorer/_datasets.yaml index 5289d558..8f05e09b 100644 --- a/src/pages/tools/dataexplorer/_datasets.yaml +++ b/src/pages/tools/dataexplorer/_datasets.yaml @@ -6,7 +6,7 @@ names: listingLabels: label: Listing of Labels endpoint: drug/label.json - url: http://ec2-54-211-65-171.compute-1.amazonaws.com:8000 + url: http://ec2-34-239-135-224.compute-1.amazonaws.com:8000 filters: options: - label: Time Period diff --git a/src/pages/tools/dataexplorer/index.jsx b/src/pages/tools/dataexplorer/index.jsx index baae2a65..de305ea5 100644 --- a/src/pages/tools/dataexplorer/index.jsx +++ b/src/pages/tools/dataexplorer/index.jsx @@ -31,7 +31,7 @@ class DataExplorer extends React.Component { dataset: dataset, view: dataset.views[0], filters: dataset.filters.options.map(option => { - option.value = null + option.value = [] return option }), drs: new DataRetrievalService(dataset.url, dataset.endpoint), @@ -170,7 +170,7 @@ class DataExplorer extends React.Component { : + /> } Date: Fri, 2 Mar 2018 17:45:40 -0500 Subject: [PATCH 012/441] working querying --- src/components/DataRetrieval.jsx | 5 +- src/components/Filter.jsx | 100 ++++++++++++++++++-- src/pages/tools/dataexplorer/_datasets.yaml | 16 +++- src/pages/tools/dataexplorer/index.jsx | 20 ++-- 4 files changed, 119 insertions(+), 22 deletions(-) diff --git a/src/components/DataRetrieval.jsx b/src/components/DataRetrieval.jsx index 47c60bf3..e36ccebe 100644 --- a/src/components/DataRetrieval.jsx +++ b/src/components/DataRetrieval.jsx @@ -18,7 +18,7 @@ class DataRetrievalService { convertFiltersToJson(filters){ const formattedFilters = {} - filters.filter(filter => filter.value.length ).forEach((filter,idx) => { + filters.filter(filter => filter.value.length && filter.field !== "effective_time").forEach((filter,idx) => { formattedFilters[`${idx}`] = { "key": filter.field, "value": filter.value @@ -86,8 +86,7 @@ class DataRetrievalService { return fetch(`${this.url}/${this.endpoint}`, { body: JSON.stringify(data), headers: { - 'Accept': 'application/json, text/plain, *\/*', - 'Content-Type': 'text/plain' + "Content-Type": "application/json" }, method: 'POST', mode: 'cors' diff --git a/src/components/Filter.jsx b/src/components/Filter.jsx index 13ee9629..28647f18 100644 --- a/src/components/Filter.jsx +++ b/src/components/Filter.jsx @@ -4,10 +4,12 @@ import React from 'react' import Checkbox from 'rc-checkbox' import Select from 'react-select' +import Async from 'react-select' import 'rc-checkbox/assets/index.css' import _ from 'lodash' import Moment from 'moment' import AutoCompleteComponent from './AutoComplete' +import withQuery from 'with-query' class SelectFilterComponent extends React.Component { @@ -37,14 +39,24 @@ class SelectAutoCompleteFilterComponent extends React.Component { this.state = { values: [], - elements: () + elements: null, + url: this.props.parent.state.dataset.url, + endpoint: this.props.parent.state.dataset.endpoint } this.onChange = this.onChange.bind(this) this.removeValue = this.removeValue.bind(this) + this.escapeRegexCharacters = this.escapeRegexCharacters.bind(this) + this.onInputKeyDown = this.onInputKeyDown.bind(this) + this.getOptions = this.getOptions.bind(this) } componentDidMount () { + this.setState({ + options: [] + }) + const field = this.props.option.field + const autocomplete_field = this.props.option.autocomplete_field if(this.props.option.can_query){ this.props.parent.state.drs.getTopValues(field).then(options => { this.setState({ @@ -127,6 +139,64 @@ class SelectAutoCompleteFilterComponent extends React.Component { }) } + getOptions(value, callback) { + if(value){ + return fetch( + withQuery(`${this.state.url}/${this.state.endpoint}`,{ + searchField: this.props.option.autocomplete_field, + searchText: value, + searchType: 'autocomplete', + limit: this.props.option.limit + }) + ) + .then(res => res.json()) + .then((json) => { + var r = json.results.map(value => { + return { + value: value, + label: value + } + }) + return { + 'options': r + } + }) + .catch((err) => { + return { + options: [] + } + }) + } else { + callback(null, { + options: this.state.options, + complete: true, + }) + } + } + + escapeRegexCharacters(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + onInputKeyDown(value){ + const escapedValue = this.escapeRegexCharacters(value.trim()) + this.getSuggestions(escapedValue).then(results => { + if(!results){ + return + } + this.setState({ + options: results.map(result => { + return { + value: result, + label: result + } + }), + value: escapedValue + }) + }) + + } + onChange(selectionObj){ const value = selectionObj.value const currentValues = this.state.values @@ -184,14 +254,14 @@ class SelectAutoCompleteFilterComponent extends React.Component {

{this.props.option.label}


- + ) + } +} + class FilterComponent extends React.Component { constructor (props: Object) { @@ -591,6 +625,7 @@ class FilterComponent extends React.Component { this.onChangeCheckbox = this.onChangeCheckbox.bind(this) this.onChangeDatePickerEnd = this.onChangeDatePickerEnd.bind(this) this.onChangeDatePickerStart = this.onChangeDatePickerStart.bind(this) + this.onChangeText = this.onChangeText.bind(this) } componentDidMount () { @@ -683,7 +718,19 @@ class FilterComponent extends React.Component { } - render (): ?React.Element { + onChangeText(e){ + const value = e.target.value.toLowerCase() + + this.props.parent.state.filters[e.target.id].value = value + + this.props.parent.setState({ + filters: this.props.parent.state.filters + }) + + } + + + render (): ?React.Element { if(!this.props.parent.state.dataset.filters.options || !this.props.parent.state.dataset.filters.options.length) { return @@ -760,7 +807,25 @@ class FilterComponent extends React.Component { />
) + } else if(option.type === "free_text") { + return ( +
+
+

{option.label}

+
+ +
+ ) } + }) return ( diff --git a/src/pages/tools/dataexplorer/_datasets.yaml b/src/pages/tools/dataexplorer/_datasets.yaml index 56f91d1a..cbe09b5a 100644 --- a/src/pages/tools/dataexplorer/_datasets.yaml +++ b/src/pages/tools/dataexplorer/_datasets.yaml @@ -16,17 +16,15 @@ names: placeholder: Select Time Period - label: Inactive Ingredients field: inactive_ingredient - type: select_autocomplete + type: free_text query_type: term - autocomplete_field: inactive_ingredient remove_chars: inactive ingredients can_query: false placeholder: Search Inactive Ingredient limit: 100 - label: Active Ingredients field: active_ingredient - autocomplete_field: active_ingredient - type: select_autocomplete + type: free_text remove_chars: active ingredients can_query: false placeholder: Search Active Ingredient From f964a16c3344c6624032d89b61e4ac1225d904de Mon Sep 17 00:00:00 2001 From: gundalar Date: Wed, 7 Mar 2018 09:10:41 -0500 Subject: [PATCH 015/441] FDA-268 fix case for checkbox, field name --- src/components/Filter.jsx | 6 +----- src/pages/tools/dataexplorer/_datasets.yaml | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/components/Filter.jsx b/src/components/Filter.jsx index f5acb470..a846f688 100644 --- a/src/components/Filter.jsx +++ b/src/components/Filter.jsx @@ -633,7 +633,7 @@ class FilterComponent extends React.Component { } onChangeCheckbox(e) { - const value = e.target.value.toLowerCase() + const value = e.target.value const currentValues = this.props.parent.state.filters[e.target.filterIdx].value let valueToSet = null @@ -814,10 +814,6 @@ class FilterComponent extends React.Component {

{option.label}


Date: Fri, 9 Mar 2018 12:57:37 -0500 Subject: [PATCH 016/441] selected filters and day picker css fix --- package.json | 2 +- push-site.sh | 2 +- src/components/AutoComplete.jsx | 21 +- src/components/DataRetrieval.jsx | 14 + src/components/DatasetExplorerContent.jsx | 151 +++++++- src/components/Filter.jsx | 369 +++++++++++--------- src/css/components/DayPicker.styl | 212 +++++++++++ src/html.jsx | 2 +- src/pages/tools/dataexplorer/_datasets.yaml | 27 +- src/pages/tools/dataexplorer/index.jsx | 13 +- 10 files changed, 620 insertions(+), 193 deletions(-) create mode 100644 src/css/components/DayPicker.styl diff --git a/package.json b/package.json index 46dcfaf5..802de579 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Jack Finch ", "scripts": { "dev:css": "stylus -w ./src/css/app.styl -o ./src/css/build/app.css -I ./node-modules/nib", - "dev:site": "gatsby develop -H 127.0.0.1 -p 3000", + "dev:site": "gatsby develop -H 127.0.0.1 -p 3000 --verbose", "build": "gatsby build --verbose", "format": "prettier --trailing-comma es5 --no-semi --single-quote --write 'src/**/*.js'" }, diff --git a/push-site.sh b/push-site.sh index b5068f11..7ffb594d 100755 --- a/push-site.sh +++ b/push-site.sh @@ -1,7 +1,7 @@ #!/bin/bash TODAY=$(date +"%Y-%m-%d") -BUCKET="open.fda.gov-website-redesign" +BUCKET="open.fda.gov-data-explorer" echo "Pushing to: ${BUCKET}" diff --git a/src/components/AutoComplete.jsx b/src/components/AutoComplete.jsx index 66335036..03275d3a 100644 --- a/src/components/AutoComplete.jsx +++ b/src/components/AutoComplete.jsx @@ -10,7 +10,9 @@ class AutoCompleteComponent extends React.Component { this.state = { value: '', - suggestions: [] + suggestions: [], + url: this.props.parent.state.dataset.url, + endpoint: this.props.parent.state.dataset.endpoint } this.getSuggestions = this.getSuggestions.bind(this) this.onChange = this.onChange.bind(this) @@ -32,11 +34,11 @@ class AutoCompleteComponent extends React.Component { getSuggestions(value) { return fetch( - withQuery(`${this.props.url}/${this.props.endpoint}`,{ - searchField: this.props.field, - searchText: value.value, + withQuery(`${this.state.url}/${this.state.endpoint}`,{ + searchField: this.props.option.field, + searchText: value, searchType: 'autocomplete', - limit: this.props.limit + limit: this.props.option.limit }) ) .then(res => res.json()) @@ -59,6 +61,12 @@ class AutoCompleteComponent extends React.Component { }) } onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }){ + if(this.props.onChange){ + this.props.onChange(suggestion, { + field: this.props.option.field, + idx: this.props.option.idx + }) + } console.log(event, suggestion) } @@ -73,7 +81,6 @@ class AutoCompleteComponent extends React.Component { value: newValue }) } - } onSuggestionsClearRequested (){ @@ -92,7 +99,7 @@ class AutoCompleteComponent extends React.Component { getSuggestionValue={this.getSuggestionValue} renderSuggestion={this.renderSuggestion} inputProps={{ - placeholder: this.props.placeholder, + placeholder: this.props.option.placeholder, value: this.state.value, onChange: this.onChange }} diff --git a/src/components/DataRetrieval.jsx b/src/components/DataRetrieval.jsx index dedd3241..f90371de 100644 --- a/src/components/DataRetrieval.jsx +++ b/src/components/DataRetrieval.jsx @@ -29,13 +29,24 @@ class DataRetrievalService { "gte": Moment(value.value[0]).format('YYYYMMDD'), "lte": Moment(value.value[1]).format('YYYYMMDD') } + } else if (value["query-type"] == "exists"){ + } return value }) + formattedFilters.push({ + "query-type": "term", + "key": "@drugtype", + "value": [ + "animal" + ] + }) + return { "data": { "queryJSON": { + "size": 1000, "searchType": "nonLLT", "filters": formattedFilters } @@ -92,6 +103,8 @@ class DataRetrievalService { getData(params){ const data = this.convertFiltersToJson(params) + console.log("filters:") + console.log(JSON.stringify(data, null, 4)); return fetch(`${this.url}/${this.endpoint}`, { body: JSON.stringify(data), headers: { @@ -102,6 +115,7 @@ class DataRetrievalService { }) .then(res => res.json()) .then(res => { + console.log(res) return res }) .catch((err) => {}) diff --git a/src/components/DatasetExplorerContent.jsx b/src/components/DatasetExplorerContent.jsx index c5e805e8..0a4b581e 100644 --- a/src/components/DatasetExplorerContent.jsx +++ b/src/components/DatasetExplorerContent.jsx @@ -8,6 +8,8 @@ import createClass from 'create-react-class' import Select from 'react-select' import FileSaver from 'file-saver' import Json2csvParser from 'json2csv' +import PropTypes from 'prop-types' +import Moment from 'moment' const GravatarOption = createClass({ @@ -131,7 +133,7 @@ class ResultsComponent extends React.Component { height: 40, display:"flex", justifyContent: "space-between", - paddingTop:10, + paddingTop: 40, paddingBottom: 43 }}>

{this.props.rows.length} results

@@ -179,11 +181,13 @@ class ResultsComponent extends React.Component {
{ + if(filter.query_type !== "range"){ + filter.value = [] + } + return filter + }) + }) + } + + formatValues(values){ + const filters = [] + this.props.parent.state.filters.forEach((filter,idx) => { + if ( + filter.query_type === "term" && + filter.type === "checkbox" + ) { + filter.value.forEach( (f, valueIdx) => { + var valueObj = filter.options.filter(o => o.value === f) + if(valueObj.length){ + filters.push({ + value: valueObj[0].label, + label: filter.label, + query_type: filter.query_type, + idx: idx, + valueIdx: valueIdx + }) + } + }) + } else if( + filter.query_type == "range" && + filter.value.length + ) { + const startDay = Moment(filter.value[0]).format('MM/DD/YYYY') + const endDay = Moment(filter.value[1]).format('MM/DD/YYYY') + filters.push({ + value: `${startDay} - ${endDay}`, + label: filter.label, + query_type: filter.query_type, + idx: idx + }) + } else if ( + filter.query_type == "term" && + filter.value.length && + filter.type !== "checkbox" + ){ + filter.value.forEach( (f, valueIdx) => { + filters.push({ + value: f, + label: filter.label, + query_type: filter.query_type, + idx: idx, + valueIdx: valueIdx + }) + }) + } + }) + + return filters.map((filter, idx) => { + return ( + + ) + }) + } + render (): ?React.Element { + const filters = this.formatValues() return ( -
+
+

Selected Filters:

- ) @@ -276,10 +401,12 @@ class DatasetExplorerContentComponent extends React.Component { return (
- +
{ - return ( -
- -
- ) - }) - }) - - this.props.onChange(selectionObj, { + this.props.onChange({ + label: value, + value: value + }, { field: this.props.option.field, idx: this.props.option.idx }) @@ -184,62 +152,53 @@ class SelectAutoCompleteFilterComponent extends React.Component { onInputKeyDown(value){ const escapedValue = this.escapeRegexCharacters(value.trim()) this.getSuggestions(escapedValue).then(results => { - if(!results){ - return - } - this.setState({ - options: results.map(result => { - return { - value: result, - label: result - } - }), - value: escapedValue - }) + if(!results){ + return + } + this.setState({ + options: results.map(result => { + return { + value: result, + label: result + } + }), + value: escapedValue + }) }) } - onChange(selectionObj){ - const value = selectionObj.value - const currentValues = this.state.values - const currentIndex = currentValues.indexOf(value) - - // contains value already - if( currentIndex > -1 ){ - currentValues.splice(currentIndex, 1) - } else { - currentValues.push(value) - } - - this.setState({ - values: currentValues, - value: this.props.option.placeholder, - elements: currentValues.map((value,idx) => { - return ( -
{ + return ( +
+ -
- ) - }) + {value} + + +
+ ) + }) + } + + onChange(selectionObj){ + this.setState({ + value: this.props.option.placeholder }) if(this.props.onChange){ @@ -251,6 +210,7 @@ class SelectAutoCompleteFilterComponent extends React.Component { } render (): ?React.Element { + const elements = this.formatValues(this.props.parent.state.filters[this.props.option.idx].value) return (
@@ -267,7 +227,7 @@ class SelectAutoCompleteFilterComponent extends React.Component { loadOptions={this.getOptions} clearable={false} /> - {this.state.elements} + {elements}
) } @@ -428,7 +388,7 @@ class DatePickerFilterComponent extends React.Component { constructor (props: Object) { super(props) - const startDay = Moment().subtract(1, "months").toDate() + const startDay = Moment().subtract(10, "years").toDate() const endDay = new Date() this.state = { @@ -490,6 +450,7 @@ class DatePickerFilterComponent extends React.Component { { - states[label] = 0 - }) - this.setState({ - states - }) + // const field = this.props.option.field + // const states = {} + // this.props.option.options.forEach(option => { + // states[option.label] = 0 + // }) + // this.setState({ + // states + // }) } onChange(e){ - const value = e.target.value - Object.keys(this.state.states).forEach(label => { - if(value === label){ - this.state.states[label] = (!this.state.states[label] ? 1 : 0) - } - }) + // const value = e.target.value + // this.state.states[value] = (!this.state.states[value] ? 1 : 0) + + + // Object.keys(this.state.states).forEach(option => { + // const label = option.label + // if(value === label){ + // this.state.states[label] = (!this.state.states[label] ? 1 : 0) + // } + // }) + this.setState({ states: this.state.states }) if(this.props.onChange){ - this.props.onChange(e) + this.props.onChange(e, this.props.option) } } render (): ?React.Element { - if(!this.state.states){ - return () - } + const field = this.props.option.field - const output = this.props.option.options.map((label, idx) => { - return ( -
-

- -

-
- ) - }) + const output = this.props.option.options.map((option, idx) => { + const currentValue = this.props.parent.state.filters[this.props.option.idx].value + const checked = (currentValue.indexOf(option.value) > -1) + return ( +
+

+ +

+
+ ) + }) return (
{ output } @@ -581,35 +546,101 @@ class CheckboxFilterComponent extends React.Component { class FreeTextFilterComponent extends React.Component { constructor (props: Object) { - super(props) + super(props) - this.state = { - value:"" - } - - this.onChange = this.onChange.bind(this) + this.state = { + currentValue: "" + } + this.onChange = this.onChange.bind(this) + this.handleKeyPress = this.handleKeyPress.bind(this) + this.formatValues = this.formatValues.bind(this) + this.removeValue = this.removeValue.bind(this) } componentDidMount () { + } + + removeValue(idx){ + const value = this.props.parent.state.filters[this.props.option.idx].value[idx] + this.props.onChange(value, { + field: this.props.option.field, + idx: this.props.option.idx + }) } onChange(event) { - this.setState({value: event.target.value}); + this.setState({ + currentValue: event.target.value + }) + } + + formatValues(values){ + return values.map((value,idx) => { + return ( +
+ +
+ ) + }) + } + + handleKeyPress(e) { + if(e.key === "Enter"){ + const value = e.target.value + + this.setState({ + currentValue: "" + }) if(this.props.onChange){ - this.props.onChange(event) + this.props.onChange(value, { + field: this.props.option.field, + idx: this.props.option.idx + }) } + } } render(): ?React.Element { + const elements = this.formatValues(this.props.parent.state.filters[this.props.option.idx].value) - return ( - - ) + return ( +
+ + {elements} +
+ ) } } @@ -629,11 +660,12 @@ class FilterComponent extends React.Component { } componentDidMount () { - + this.props.parent.getData() } - onChangeCheckbox(e) { - const value = e.target.value + onChangeCheckbox(e, options) { + const value = options.options.filter(v => e.target.value === v.label)[0].value + const currentValues = this.props.parent.state.filters[e.target.filterIdx].value let valueToSet = null @@ -651,6 +683,7 @@ class FilterComponent extends React.Component { filters: this.props.parent.state.filters }) } + onChangeAutoComplete(value, meta){ const currentValues = this.parent.state.filters[meta.idx].value const currentIndex = currentValues.indexOf(value) @@ -669,7 +702,7 @@ class FilterComponent extends React.Component { }) } - onChangeSelect(selectionObj, meta){ + onChangeSelect(selectionObj, meta, cb){ const value = selectionObj.value const currentValues = this.props.parent.state.filters[meta.idx].value const currentIndex = currentValues.indexOf(value) @@ -704,7 +737,6 @@ class FilterComponent extends React.Component { this.props.parent.setState({ filters: this.props.parent.state.filters }) - } onChangeDatePickerStart(date, meta){ @@ -715,22 +747,28 @@ class FilterComponent extends React.Component { this.props.parent.setState({ filters: this.props.parent.state.filters }) - } - onChangeText(e){ - const value = e.target.value.toLowerCase() + onChangeText(value, meta){ + const currentValues = this.props.parent.state.filters[meta.idx].value + const currentIndex = currentValues.indexOf(value) - this.props.parent.state.filters[e.target.id].value = value + // contains value already + if( currentIndex > -1 ){ + currentValues.splice(currentIndex, 1) + } else { + currentValues.push(value) + } + + this.props.parent.state.filters[meta.idx].value = currentValues this.props.parent.setState({ - filters: this.props.parent.state.filters + filters: this.props.parent.state.filters }) - } - render (): ?React.Element { + render (): ?React.Element { if(!this.props.parent.state.dataset.filters.options || !this.props.parent.state.dataset.filters.options.length) { return @@ -789,6 +827,7 @@ class FilterComponent extends React.Component { key={`filter${idx}`} option={option} onChange={this.onChangeCheckbox} + parent={this.props.parent} />
) @@ -809,6 +848,7 @@ class FilterComponent extends React.Component { ) } else if(option.type === "free_text") { return ( +<<<<<<< Updated upstream

{option.label}

@@ -819,6 +859,23 @@ class FilterComponent extends React.Component { onChange={this.onChangeText} />
+======= +
+
+

{option.label}

+
+ +
+>>>>>>> Stashed changes ) } diff --git a/src/css/components/DayPicker.styl b/src/css/components/DayPicker.styl new file mode 100644 index 00000000..977f578e --- /dev/null +++ b/src/css/components/DayPicker.styl @@ -0,0 +1,212 @@ +/* DayPicker styles */ + +.DayPicker { + display: inline-block; +} + +.DayPicker-wrapper { + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + padding-bottom: 1rem; + flex-direction: row; +} + +.DayPicker-Months { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.DayPicker-Month { + display: table; + border-collapse: collapse; + border-spacing: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + margin: 0 1rem; + margin-top: 1rem; +} + +.DayPicker-NavBar { +} + +.DayPicker-NavButton { + position: absolute; + cursor: pointer; + top: 1rem; + right: 1.5rem; + margin-top: 2px; + color: #8b9898; + width: 1.25rem; + height: 1.25rem; + display: inline-block; + background-size: 50%; + background-repeat: no-repeat; + background-position: center; +} + +.DayPicker-NavButton:hover { + opacity: 0.8; +} + +.DayPicker-NavButton--prev { + margin-right: 1.5rem; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAwCAYAAAB5R9gVAAAABGdBTUEAALGPC/xhBQAAAVVJREFUWAnN2G0KgjAYwPHpGfRkaZeqvgQaK+hY3SUHrk1YzNLay/OiEFp92I+/Mp2F2Mh2lLISWnflFjzH263RQjzMZ19wgs73ez0o1WmtW+dgA01VxrE3p6l2GLsnBy1VYQOtVSEH/atCCgqpQgKKqYIOiq2CBkqtggLKqQIKgqgCBjpJ2Y5CdJ+zrT9A7HHSTA1dxUdHgzCqJIEwq0SDsKsEg6iqBIEoq/wEcVRZBXFV+QJxV5mBtlDFB5VjYTaGZ2sf4R9PM7U9ZU+lLuaetPP/5Die3ToO1+u+MKtHs06qODB2zBnI/jBd4MPQm1VkY79Tb18gB+C62FdBFsZR6yeIo1YQiLJWMIiqVjQIu1YSCLNWFgijVjYIuhYYCKoWKAiiFgoopxYaKLUWOii2FgkophYp6F3r42W5A9s9OcgNvva8xQaysKXlFytoqdYmQH6tF3toSUo0INq9AAAAAElFTkSuQmCC'); +} + +.DayPicker-NavButton--next { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAwCAYAAAB5R9gVAAAABGdBTUEAALGPC/xhBQAAAXRJREFUWAnN119ugjAcwPHWzJ1gnmxzB/BBE0n24m4xfNkTaOL7wOtsl3AXMMb+Vjaa1BG00N8fSEibPpAP3xAKKs2yjzTPH9RAjhEo9WzPr/Vm8zgE0+gXATAxxuxtqeJ9t5tIwv5AtQAApsfT6TPdbp+kUBcgVwvO51KqVhMkXKsVJFXrOkigVhCIs1Y4iKlWZxB1rX4gwlpRIIpa8SDkWmggrFq4IIRaJKCYWnSgnrXIQV1r8YD+1Vrn+bReagysIFfLABRt31v8oBu1xEBttfRbltmfjgEcWh9snUS2kNdBK6WN1vrOWxObWsz+fjxevsxmB1GQDfINWiev83nhaoiB/CoOU438oPrhXS0WpQ9xc1ZQWxWHqUYe0I0qrKCQKjygDlXIQV2r0IF6ViEBxVTBBSFUQQNhVYkHIVeJAtkNsbQ7c1LtzP6FsObhb2rCKv7NBIGoq4SDmKoEgTirXAcJVGkFSVVpgoSrXICGUMUH/QBZNSUy5XWUhwAAAABJRU5ErkJggg=='); +} + +.DayPicker-NavButton--interactionDisabled { + display: none; +} + +.DayPicker-Caption { + padding: 0 0.5rem; + display: table-caption; + text-align: left; + margin-bottom: 0.5rem; +} + +.DayPicker-Caption > div { + font-size: 1.15rem; + font-weight: 500; +} + +.DayPicker-Weekdays { + margin-top: 1rem; + display: table-header-group; +} + +.DayPicker-WeekdaysRow { + display: table-row; +} + +.DayPicker-Weekday { + display: table-cell; + padding: 0.5rem; + font-size: 0.875em; + text-align: center; + color: #8b9898; +} + +.DayPicker-Weekday abbr[title] { + border-bottom: none; + text-decoration: none; +} + +.DayPicker-Body { + display: table-row-group; +} + +.DayPicker-Week { + display: table-row; +} + +.DayPicker-Day { + display: table-cell; + padding: 0.5rem; + text-align: center; + cursor: pointer; + vertical-align: middle; + outline: none; +} + +.DayPicker-WeekNumber { + display: table-cell; + padding: 0.5rem; + text-align: right; + vertical-align: middle; + min-width: 1rem; + font-size: 0.75em; + cursor: pointer; + color: #8b9898; + border-right: 1px solid #eaecec; +} + +.DayPicker--interactionDisabled .DayPicker-Day { + cursor: default; +} + +.DayPicker-Footer { + padding-top: 0.5rem; +} + +.DayPicker-TodayButton { + border: none; + background-image: none; + background-color: transparent; + box-shadow: none; + cursor: pointer; + color: #4a90e2; + font-size: 0.875em; +} + +/* Default modifiers */ + +.DayPicker-Day--today { + color: #d0021b; + font-weight: 700; +} + +.DayPicker-Day--outside { + cursor: default; + color: #8b9898; +} + +.DayPicker-Day--disabled { + color: #dce0e0; + cursor: default; + /* background-color: #eff1f1; */ +} + +/* Example modifiers */ + +.DayPicker-Day--sunday { + background-color: #f7f8f8; +} + +.DayPicker-Day--sunday:not(.DayPicker-Day--today) { + color: #dce0e0; +} + +.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) { + position: relative; + color: #f0f8ff; + background-color: #4a90e2; + border-radius: 100%; +} + +.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside):hover { + background-color: #51a0fa; +} + +.DayPicker:not(.DayPicker--interactionDisabled) + .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover { + background-color: #f0f8ff; + border-radius: 50%; +} + +/* DayPickerInput */ + +.DayPickerInput { + display: inline-block; +} + +.DayPickerInput-OverlayWrapper { + position: relative; +} + +.DayPickerInput-Overlay { + left: 0; + z-index: 1; + position: absolute; + background: white; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); +} \ No newline at end of file diff --git a/src/html.jsx b/src/html.jsx index 796d903a..7a1dcfab 100644 --- a/src/html.jsx +++ b/src/html.jsx @@ -95,8 +95,8 @@ const HTML = ({ title = 'openFDA', favicon, body, postBodyComponents, headCompon