From e15c5b4df636ee6f7e7ddb655720f2bc2ef34d17 Mon Sep 17 00:00:00 2001 From: Bart Akeley Date: Sat, 11 Nov 2017 20:15:19 -0600 Subject: [PATCH] filter state management bits --- .prettierrc | 2 - js/apis/FoodItemsApi.js | 70 +++++++++++++----------- js/components/CategoryPicker.js | 11 ++-- js/components/FilterModal.js | 49 +++++++++++++++++ js/components/FoodItemList.js | 6 +- js/components/Modal.js | 40 +++++++------- js/components/PlaceTile.js | 2 +- js/components/QuantityPicker.js | 11 ++-- js/constants/CategoryConstants.js | 11 ++++ js/constants/QuantityConstants.js | 7 +++ js/enhancers/filterEnhancers.js | 13 +++++ js/helpers/CategoryHelpers.js | 8 +-- js/helpers/QuantityHelpers.js | 2 +- js/pages/Food.js | 3 +- js/pages/FoodItemDetail.js | 25 ++++----- js/pages/FoodList.js | 32 ++++++++++- js/records/FilterRecord.js | 12 ++++ js/records/FoodItemRecord.js | 20 +------ js/streams/FilterStream.js | 13 +++++ js/streams/FoodItemsStream.js | 7 ++- js/streams/PlacesStream.js | 2 +- package.json | 91 ++++++++++++++++--------------- sample-query.sql | 30 +++++----- yarn.lock | 8 ++- 24 files changed, 305 insertions(+), 170 deletions(-) delete mode 100644 .prettierrc create mode 100644 js/components/FilterModal.js create mode 100644 js/constants/CategoryConstants.js create mode 100644 js/constants/QuantityConstants.js create mode 100644 js/enhancers/filterEnhancers.js create mode 100644 js/records/FilterRecord.js create mode 100644 js/streams/FilterStream.js diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 671105b..0000000 --- a/.prettierrc +++ /dev/null @@ -1,2 +0,0 @@ -printWidth: 100; -parser: flow; diff --git a/js/apis/FoodItemsApi.js b/js/apis/FoodItemsApi.js index 72be3ea..1d0f616 100644 --- a/js/apis/FoodItemsApi.js +++ b/js/apis/FoodItemsApi.js @@ -1,7 +1,9 @@ // @flow import { memoize } from 'ramda'; +import FilterRecord from '../records/FilterRecord'; const BASE_URL = 'aretherecookies.herokuapp.com'; +// const BASE_URL = '192.168.1.6:3000'; export type FoodItemsFilter = { radius?: number, @@ -25,38 +27,42 @@ export type FoodItemsForLocation = { fooditems: Array, }; -export const getFoodItemsForLocation = memoize( - async ({ pos: { coords: { latitude, longitude } } }: { pos: Position }): Promise => { - try { - return fetch(`https://${BASE_URL}/fooditems`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', +export const getFoodItems = memoize(async ({ loc, filter }: { loc: Position, filter: FilterRecord }): Promise< + FoodItemsForLocation +> => { + const { coords: { latitude: lat, longitude: lng } } = loc; + const { orderby, categories, radius } = filter; + + try { + return fetch(`http://${BASE_URL}/fooditems`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + lat, + lng, + orderby, + filter: { + ...(categories ? { categories } : {}), + radius, }, - body: JSON.stringify({ - lat: latitude, - lng: longitude, - orderby: 'distance', - filter: { - radius: 10, - }, - }), - }) - .then(res => res.json()) - .then(json => ({ - ...json, - loading: false, - error: null, - })); - } catch (error) { - console.error(error); // eslint-disable-line no-console - return { - orderby: 'distance', - filter: {}, - fooditems: [], + }), + }) + .then(res => res.json()) + .then(json => ({ + ...json, loading: false, - error: error, - }; - } + error: null, + })); + } catch (error) { + console.error(error); // eslint-disable-line no-console + return { + orderby: 'distance', + filter: {}, + fooditems: [], + loading: false, + error: error, + }; } -); +}); diff --git a/js/components/CategoryPicker.js b/js/components/CategoryPicker.js index 3c38999..726ab79 100644 --- a/js/components/CategoryPicker.js +++ b/js/components/CategoryPicker.js @@ -4,7 +4,7 @@ import { getCategoryText } from '../helpers/CategoryHelpers'; import { Picker, View } from 'react-native'; import theme from '../ui-theme'; import { pure } from 'recompose'; -import { type Category, CATEGORIES } from '../records/FoodItemRecord'; +import { type Category, CATEGORIES } from '../constants/CategoryConstants'; import debounce from '../helpers/debounce'; const { picker: { color: selectedColor } } = theme; @@ -13,16 +13,17 @@ const defaultColor = 'black'; const getItemColor = (selected, current) => (selected === current ? selectedColor : defaultColor); /* eslint-disable react/display-name */ -const renderItem = (selected: Category) => (item: Category) => - ; +const renderItem = (selected: Category) => (item: Category) => ( + +); type categoryPickerProps = { selected: Category, onValueChange: Function, style: Object }; -const categoryPicker = pure(({ selected, onValueChange, style }: categoryPickerProps) => +const categoryPicker = pure(({ selected, onValueChange, style }: categoryPickerProps) => ( {CATEGORIES.map(renderItem(selected))} -); +)); export default categoryPicker; diff --git a/js/components/FilterModal.js b/js/components/FilterModal.js new file mode 100644 index 0000000..ad65aba --- /dev/null +++ b/js/components/FilterModal.js @@ -0,0 +1,49 @@ +// @flow +import React from 'react'; +import { View, TouchableOpacity, Text } from 'react-native'; +import CheckBox from 'react-native-checkbox'; +import Modal from './Modal'; +// import { Icon } from 'react-native-material-ui'; +import { withFilter } from '../enhancers/filterEnhancers'; +import typeof FilterRecord from '../records/FilterRecord'; +import { CATEGORIES, type Category } from '../constants/CategoryConstants'; +import { getCategoryText } from '../helpers/CategoryHelpers'; +// import { Set } from 'immutable'; + +type Props = { + isVisible: boolean, + onClose: () => void, + filter: FilterRecord, + setFilter: (f: FilterRecord) => void, +}; + +const FilterModal = withFilter(({ isVisible, onClose, filter, setFilter }: Props) => { + const { orderby, categories, radius } = filter; + + const toggleCategory = category => checked => { + setFilter( + filter.update('categories', categories => { + return checked ? categories.delete(category) : categories.add(category); + }) + ); + }; + + return ( + + + Filters + {CATEGORIES.map((category: Category) => ( + + ))} + + + ); +}); + +export default FilterModal; diff --git a/js/components/FoodItemList.js b/js/components/FoodItemList.js index d1a7458..c9854de 100644 --- a/js/components/FoodItemList.js +++ b/js/components/FoodItemList.js @@ -43,11 +43,7 @@ class FoodItemList extends Component { const { filter, foodItemsSeq, renderFoodItem, limit } = this.props; const getItems = R.compose(intoArray, limitBy(limit), sortByDistance, filterBy(filter)); const items = getItems(foodItemsSeq); - return ( - - {items.map(renderFoodItem)} - - ); + return {items.map(renderFoodItem)}; } } diff --git a/js/components/Modal.js b/js/components/Modal.js index 822db31..dc31da9 100644 --- a/js/components/Modal.js +++ b/js/components/Modal.js @@ -6,31 +6,31 @@ import { pure } from 'recompose'; import theme from '../ui-theme'; import { Icon } from 'react-native-material-ui'; -export default pure(({ children, isVisible }) => - - - {children} - - -); +export default pure(({ children, isVisible }) => { + return ( + + + {children} + + + ); +}); export const TextButton = ({ text, onPress, style = {} }: { text: string, onPress: () => void, style: Object }) => { return ( - - {text} - + {text} ); diff --git a/js/components/PlaceTile.js b/js/components/PlaceTile.js index 5d8372a..baf0dab 100644 --- a/js/components/PlaceTile.js +++ b/js/components/PlaceTile.js @@ -36,7 +36,7 @@ type PlaceTileProps = { }; export default pure(({ place, foodItems }: PlaceTileProps) => { - if (!place) { + if (!place || foodItems.size === 0) { return ; } diff --git a/js/components/QuantityPicker.js b/js/components/QuantityPicker.js index 390a461..89b3eab 100644 --- a/js/components/QuantityPicker.js +++ b/js/components/QuantityPicker.js @@ -4,7 +4,7 @@ import { getQuantityText } from '../helpers/QuantityHelpers'; import { Picker, View } from 'react-native'; import theme from '../ui-theme'; import { pure } from 'recompose'; -import { type Quantity, QUANTITIES } from '../records/FoodItemRecord'; +import { type Quantity, QUANTITIES } from '../constants/QuantityConstants'; import debounce from '../helpers/debounce'; const { picker: { color: selectedColor } } = theme; @@ -13,21 +13,22 @@ const defaultColor = 'black'; const getItemColor = (selected, current) => (selected === current ? selectedColor : defaultColor); /* eslint-disable react/display-name */ -const renderQuantityItem = (selectedQuantity: Quantity) => (quantity: Quantity) => +const renderQuantityItem = (selectedQuantity: Quantity) => (quantity: Quantity) => ( ; + /> +); type QuantityPickerProps = { quantity: Quantity, onValueChange: Function, style: Object }; -const QuantityPicker = pure(({ quantity, onValueChange, style }: QuantityPickerProps) => +const QuantityPicker = pure(({ quantity, onValueChange, style }: QuantityPickerProps) => ( {QUANTITIES.map(renderQuantityItem(quantity))} -); +)); export default QuantityPicker; diff --git a/js/constants/CategoryConstants.js b/js/constants/CategoryConstants.js new file mode 100644 index 0000000..b12f7dc --- /dev/null +++ b/js/constants/CategoryConstants.js @@ -0,0 +1,11 @@ +// @flow +export const CATEGORY_BEVERAGES: 'beverages' = 'beverages'; +export const CATEGORY_DESSERTS: 'desserts' = 'desserts'; +export const CATEGORY_ENTREES: 'entrees' = 'entrees'; +export const CATEGORY_OTHER: 'other' = 'other'; +export type Category = + | typeof CATEGORY_BEVERAGES + | typeof CATEGORY_DESSERTS + | typeof CATEGORY_ENTREES + | typeof CATEGORY_OTHER; +export const CATEGORIES = [CATEGORY_BEVERAGES, CATEGORY_DESSERTS, CATEGORY_ENTREES, CATEGORY_OTHER]; diff --git a/js/constants/QuantityConstants.js b/js/constants/QuantityConstants.js new file mode 100644 index 0000000..99ee0bd --- /dev/null +++ b/js/constants/QuantityConstants.js @@ -0,0 +1,7 @@ +// @flow +export const QUANTITY_NONE: 'none' = 'none'; +export const QUANTITY_FEW: 'few' = 'few'; +export const QUANTITY_MANY: 'many' = 'many'; +export const QUANTITY_LOTS: 'lots' = 'lots'; +export type Quantity = typeof QUANTITY_NONE | typeof QUANTITY_FEW | typeof QUANTITY_MANY | typeof QUANTITY_LOTS; +export const QUANTITIES = [QUANTITY_NONE, QUANTITY_FEW, QUANTITY_LOTS, QUANTITY_MANY]; diff --git a/js/enhancers/filterEnhancers.js b/js/enhancers/filterEnhancers.js new file mode 100644 index 0000000..7247a5c --- /dev/null +++ b/js/enhancers/filterEnhancers.js @@ -0,0 +1,13 @@ +// @flow +import mapPropsStream from 'recompose/mapPropsStream'; +import Filter$, { emitter } from '../streams/FilterStream'; + +export const withFilter = mapPropsStream(props$ => { + return props$.combineLatest(Filter$, (props, filter) => { + return { + ...props, + filter, + setFilter: emitter, + }; + }); +}); diff --git a/js/helpers/CategoryHelpers.js b/js/helpers/CategoryHelpers.js index 08b1aea..b32883b 100644 --- a/js/helpers/CategoryHelpers.js +++ b/js/helpers/CategoryHelpers.js @@ -1,10 +1,6 @@ // @flow -import FoodItemRecord, { - type Category, - CATEGORY_BEVERAGES, - CATEGORY_DESSERTS, - CATEGORY_ENTREES, -} from '../records/FoodItemRecord'; +import FoodItemRecord from '../records/FoodItemRecord'; +import { type Category, CATEGORY_BEVERAGES, CATEGORY_DESSERTS, CATEGORY_ENTREES } from '../constants/CategoryConstants'; import { type Map } from 'immutable'; export const getCategoryText = (category: Category) => { diff --git a/js/helpers/QuantityHelpers.js b/js/helpers/QuantityHelpers.js index a935fbf..6180331 100644 --- a/js/helpers/QuantityHelpers.js +++ b/js/helpers/QuantityHelpers.js @@ -1,5 +1,5 @@ // @flow -import { type Quantity } from '../records/FoodItemRecord'; +import { type Quantity } from '../constants/QuantityConstants'; export const getQuantityText = (quantity: Quantity) => { switch (quantity) { diff --git a/js/pages/Food.js b/js/pages/Food.js index 64f97f9..a456a88 100644 --- a/js/pages/Food.js +++ b/js/pages/Food.js @@ -1,5 +1,5 @@ // @flow -import { compose, branch } from 'recompose'; +import { compose, branch, withState } from 'recompose'; import FoodList from './FoodList'; import FoodMap from './FoodMap'; import { withRouterContext, withViewMode, withPushRoute } from '../enhancers/routeEnhancers'; @@ -8,6 +8,7 @@ export default compose( withRouterContext, withViewMode, withPushRoute, + withState('isFilterModalOpen', 'toggleFilterModal', false), branch(({ viewMode }) => { return viewMode === 'map'; }, FoodMap) diff --git a/js/pages/FoodItemDetail.js b/js/pages/FoodItemDetail.js index 7024811..589a253 100644 --- a/js/pages/FoodItemDetail.js +++ b/js/pages/FoodItemDetail.js @@ -3,7 +3,8 @@ import React, { Component } from 'react'; import { Image, View } from 'react-native'; import theme from '../ui-theme'; import { StrongText, SubText } from '../components/ItemTile'; -import { type FoodItem, type Quantity, QUANTITY_MANY } from '../records/FoodItemRecord'; +import { type FoodItem } from '../records/FoodItemRecord'; +import { type Quantity, QUANTITY_MANY } from '../constants/QuantityConstants'; import typeof PlaceRecord from '../records/PlaceRecord'; import { compose, pure } from 'recompose'; import IconButton from '../components/IconButton'; @@ -70,14 +71,16 @@ export class FoodItemDetail extends Component { return ( - {viewableImages.size === 1 && - } - {viewableImages.size > 1 && + {viewableImages.size === 1 && ( + + )} + {viewableImages.size > 1 && ( - {viewableImages.map(uri => + {viewableImages.map(uri => ( - )} - } + ))} + + )} @@ -86,12 +89,8 @@ export class FoodItemDetail extends Component { underlayColor={theme.itemTile.pressHighlightColor} > - - {place.name} - - - {place.address} - + {place.name} + {place.address} diff --git a/js/pages/FoodList.js b/js/pages/FoodList.js index 755c204..c72afe5 100644 --- a/js/pages/FoodList.js +++ b/js/pages/FoodList.js @@ -6,6 +6,7 @@ import { ActionButton } from 'react-native-material-ui'; import { routeWithTitle } from '../helpers/RouteHelpers'; import FoodItemList from '../components/FoodItemList'; import typeof FoodItemRecord from '../records/FoodItemRecord'; +import FilterModal from '../components/FilterModal'; import theme from '../ui-theme'; @@ -16,6 +17,8 @@ export default class FoodList extends Component { props: { pushRoute: Object => void, + isFilterModalOpen: boolean, + toggleFilterModal: (val: boolean) => void, }; addFoodItem = () => { @@ -23,13 +26,40 @@ export default class FoodList extends Component { this.props.pushRoute(newRoute); }; + toggleFilterModal = () => { + const { isFilterModalOpen, toggleFilterModal } = this.props; + toggleFilterModal(!isFilterModalOpen); + }; + + onActionPressed = (action: string) => { + switch (action) { + case 'add': + this.addFoodItem(); + break; + case 'filter': + this.toggleFilterModal(); + break; + } + }; + render() { + const { isFilterModalOpen } = this.props; + return ( - + + ); } diff --git a/js/records/FilterRecord.js b/js/records/FilterRecord.js new file mode 100644 index 0000000..2694a0f --- /dev/null +++ b/js/records/FilterRecord.js @@ -0,0 +1,12 @@ +// @flow +import { Record } from 'immutable'; +import { CATEGORIES } from '../constants/CategoryConstants'; +import { Set } from 'immutable'; + +const FilterRecord = Record({ + orderby: 'distance', + categories: Set(CATEGORIES), + radius: 20, +}); + +export default FilterRecord; diff --git a/js/records/FoodItemRecord.js b/js/records/FoodItemRecord.js index 61bea5e..e985602 100644 --- a/js/records/FoodItemRecord.js +++ b/js/records/FoodItemRecord.js @@ -1,24 +1,8 @@ //@flow import { fromJS, List, Record } from 'immutable'; import { type RawFoodItem } from '../apis/FoodItemsApi'; - -export const QUANTITY_NONE: 'none' = 'none'; -export const QUANTITY_FEW: 'few' = 'few'; -export const QUANTITY_MANY: 'many' = 'many'; -export const QUANTITY_LOTS: 'lots' = 'lots'; -export type Quantity = typeof QUANTITY_NONE | typeof QUANTITY_FEW | typeof QUANTITY_MANY | typeof QUANTITY_LOTS; -export const QUANTITIES = [QUANTITY_NONE, QUANTITY_FEW, QUANTITY_LOTS, QUANTITY_MANY]; - -export const CATEGORY_BEVERAGES: 'beverages' = 'beverages'; -export const CATEGORY_DESSERTS: 'desserts' = 'desserts'; -export const CATEGORY_ENTREES: 'entrees' = 'entrees'; -export const CATEGORY_OTHER: 'other' = 'other'; -export type Category = - | typeof CATEGORY_BEVERAGES - | typeof CATEGORY_DESSERTS - | typeof CATEGORY_ENTREES - | typeof CATEGORY_OTHER; -export const CATEGORIES = [CATEGORY_BEVERAGES, CATEGORY_DESSERTS, CATEGORY_ENTREES, CATEGORY_OTHER]; +import { type Category, CATEGORY_DESSERTS } from '../constants/CategoryConstants'; +import { type Quantity, QUANTITY_MANY } from '../constants/QuantityConstants'; export type FoodItem = { id: ?string, diff --git a/js/streams/FilterStream.js b/js/streams/FilterStream.js new file mode 100644 index 0000000..a3a89cc --- /dev/null +++ b/js/streams/FilterStream.js @@ -0,0 +1,13 @@ +// @flow +import { Subject, Observable, ReplaySubject } from 'rxjs'; +import FilterRecord from '../records/FilterRecord'; + +const multicaster = new ReplaySubject(); + +export const emitter = val => multicaster.next(val); + +// Observable.from([new FilterRecord()]).subscribe(multicaster); + +emitter(new FilterRecord()); + +export default multicaster; diff --git a/js/streams/FoodItemsStream.js b/js/streams/FoodItemsStream.js index 5d50a82..57a248a 100644 --- a/js/streams/FoodItemsStream.js +++ b/js/streams/FoodItemsStream.js @@ -3,10 +3,13 @@ import { createFoodItem } from '../records/FoodItemRecord'; import { setById } from '../helpers/ImmutableHelpers'; import { Map } from 'immutable'; import location$ from './LocationStream'; -import { getFoodItemsForLocation, type FoodItemsForLocation } from '../apis/FoodItemsApi'; +import { getFoodItems, type FoodItemsForLocation } from '../apis/FoodItemsApi'; +import FilterSubject from './FilterStream'; +import FilterRecord from '../records/FilterRecord'; export default location$ - .mergeMap(pos => getFoodItemsForLocation({ pos })) + .combineLatest(FilterSubject) + .mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter })) .map(({ fooditems = [] }: FoodItemsForLocation) => { return fooditems.map(createFoodItem).reduce(setById, new Map()); }); diff --git a/js/streams/PlacesStream.js b/js/streams/PlacesStream.js index 58cfedb..7a8abec 100644 --- a/js/streams/PlacesStream.js +++ b/js/streams/PlacesStream.js @@ -6,7 +6,7 @@ import { getPlaceDetails } from '../apis/PlaceDetailsApi'; import { memoize } from 'ramda'; import { Observable } from 'rxjs'; import typeof FoodItemRecord from '../records/FoodItemRecord'; -import PlaceRecord, { type GooglePlaceObj } from '../records/PlaceRecord'; +import { type GooglePlaceObj } from '../records/PlaceRecord'; import { setById } from '../helpers/ImmutableHelpers'; /** diff --git a/package.json b/package.json index 1f2ecf6..a729520 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,50 @@ { - "name": "AreThereCookies", - "version": "0.0.1", - "private": true, - "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start", - "test": "jest", - "lint": "flow && eslint js", - "android": "react-native run-android" - }, - "dependencies": { - "babel-preset-es2015": "^6.24.0", - "immutable": "^3.8.1", - "ramda": "^0.24.1", - "react": "~15.4.1", - "react-native": "~0.42.0", - "react-native-drawer": "^2.3.0", - "react-native-google-places": "^2.1.0", - "react-native-image-picker": "^0.26.3", - "react-native-looped-carousel": "^0.1.5", - "react-native-maps": "0.15.1", - "react-native-material-ui": "^1.7.0", - "react-native-modal": "^2.2.0", - "react-native-scrollable-tab-view": "^0.7.4", - "react-native-vector-icons": "^4.0.0", - "react-router-native": "^4.0.0", - "recompose": "^0.23.4", - "rxjs": "^5.4.2" - }, - "devDependencies": { - "babel-eslint": "^7.1.1", - "babel-jest": "18.0.0", - "babel-preset-react-native": "1.9.1", - "eslint": "^3.14.1", - "eslint-plugin-react": "^6.9.0", - "eslint-plugin-react-native": "^2.2.1", - "flow-bin": "0.38", - "jest": "18.1.0", - "jshint": "^2.9.4", - "react-test-renderer": "15.4.2" - }, - "jest": { - "preset": "react-native" - } + "name": "aretherecookies", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start", + "test": "jest", + "lint": "flow && eslint js", + "android": "react-native run-android" + }, + "dependencies": { + "babel-preset-es2015": "^6.24.0", + "immutable": "^3.8.1", + "ramda": "^0.24.1", + "react": "~15.4.1", + "react-native": "~0.42.0", + "react-native-checkbox": "^2.0.0", + "react-native-drawer": "^2.3.0", + "react-native-google-places": "^2.1.0", + "react-native-image-picker": "^0.26.3", + "react-native-looped-carousel": "^0.1.5", + "react-native-maps": "0.15.1", + "react-native-material-ui": "^1.7.0", + "react-native-modal": "^2.2.0", + "react-native-scrollable-tab-view": "^0.7.4", + "react-native-vector-icons": "^4.0.0", + "react-router-native": "^4.0.0", + "recompose": "^0.23.4", + "rxjs": "^5.4.2" + }, + "devDependencies": { + "babel-eslint": "^7.1.1", + "babel-jest": "18.0.0", + "babel-preset-react-native": "1.9.1", + "eslint": "^3.14.1", + "eslint-plugin-react": "^6.9.0", + "eslint-plugin-react-native": "^2.2.1", + "flow-bin": "0.38", + "jest": "18.1.0", + "jshint": "^2.9.4", + "react-test-renderer": "15.4.2" + }, + "jest": { + "preset": "react-native" + }, + "prettier": { + "lineWidth": 100, + "parser": "flow" + } } diff --git a/sample-query.sql b/sample-query.sql index 766372b..cabcda4 100644 --- a/sample-query.sql +++ b/sample-query.sql @@ -1,15 +1,19 @@ SELECT - *, - ST_AsGeoJSON(loc) as location, - ST_Distance( - loc, - ST_GeogFromText('SRID=4326;POINT(-97.7286718 30.3033267)') - ) / 1609 as distance -FROM food_items -WHERE - ST_DWithin( - loc, - ST_GeogFromText('SRID=4326;POINT(-97.7286718 30.3033267)'), - 10 * 1609 - ) + f.id AS id, + f.name AS name, + f.place_id AS place_id, + f.category AS category, + f.images AS images, + f.thumbImage AS thumbImage, + ST_AsGeoJSON(f.loc) AS location, + ST_Distance(f.loc, ST_SetSRID(ST_Point(-97.7286718, 30.3033267),4326)::geography) / 1609 AS distance, + q.quantity AS quantity, + q.date AS lastUpdated +FROM food_items f +LEFT OUTER JOIN ( + SELECT food_item_id, quantity, MAX(date) AS date FROM quantities GROUP BY food_item_id, quantity +) q +ON f.id = q.food_item_id +WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(-97.7286718, 30.3033267),4326)::geography, 20 * 1609) +AND f.category IN ('desserts', 'beverages', 'entrees', 'other') ORDER BY distance ASC; diff --git a/yarn.lock b/yarn.lock index 9a5e6a2..c2e7ed3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3479,7 +3479,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@15.5.10, prop-types@^15.5.4, prop-types@^15.5.8: +prop-types@15.5.10, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.8: version "15.5.10" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" dependencies: @@ -3545,6 +3545,12 @@ react-native-animatable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/react-native-animatable/-/react-native-animatable-1.2.0.tgz#fd279c6ee4b49161c6cc3b951ed7765b35a73467" +react-native-checkbox@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-checkbox/-/react-native-checkbox-2.0.0.tgz#453bbfd2e055a21e69ebe7842414a055d50ff449" + dependencies: + prop-types "^15.5.10" + react-native-drawer@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/react-native-drawer/-/react-native-drawer-2.3.0.tgz#a0369ec80ff0b61c9f152dbdea91fe76c843113a"