mirror of
https://gitlab.com/wheres-the-tp/ui-mobile.git
synced 2026-01-25 06:14:55 -06:00
quantities round trip api and stream
make POST requests to /quantity to update the quantity of an existing food item - then mixin the new quantity with the existing food items observable
This commit is contained in:
parent
70892af248
commit
9221b7f9b5
10 changed files with 105 additions and 19 deletions
|
|
@ -1,9 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { memoize } from 'ramda';
|
import { memoize } from 'ramda';
|
||||||
import FilterRecord from '../records/FilterRecord';
|
import FilterRecord from '../records/FilterRecord';
|
||||||
|
import { BASE_URL } from '../constants/AppConstants';
|
||||||
const BASE_URL = 'aretherecookies.herokuapp.com';
|
|
||||||
// const BASE_URL = '192.168.1.6:3000';
|
|
||||||
|
|
||||||
export type FoodItemsFilter = {
|
export type FoodItemsFilter = {
|
||||||
radius?: number,
|
radius?: number,
|
||||||
|
|
|
||||||
27
js/apis/QuantityApi.js
Normal file
27
js/apis/QuantityApi.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// @flow
|
||||||
|
import type { Quantity } from '../constants/QuantityConstants';
|
||||||
|
import { BASE_URL } from '../constants/AppConstants';
|
||||||
|
import type { QuantityResponse } from '../constants/QuantityConstants';
|
||||||
|
|
||||||
|
export const setQuantity = ({
|
||||||
|
foodItemId,
|
||||||
|
quantity,
|
||||||
|
}: {
|
||||||
|
foodItemId: string,
|
||||||
|
quantity: Quantity,
|
||||||
|
}): ?Promise<QuantityResponse> => {
|
||||||
|
try {
|
||||||
|
return fetch(`http://${BASE_URL}/quantity`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
foodItemId,
|
||||||
|
quantity,
|
||||||
|
}),
|
||||||
|
}).then(res => res.json());
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error); //eslint-disable-line
|
||||||
|
}
|
||||||
|
};
|
||||||
4
js/constants/AppConstants.js
Normal file
4
js/constants/AppConstants.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
export const BASE_URL = 'aretherecookies.herokuapp.com';
|
||||||
|
// export const BASE_URL = '192.168.1.6:3000';
|
||||||
|
|
@ -3,5 +3,15 @@ export const QUANTITY_NONE: 'none' = 'none';
|
||||||
export const QUANTITY_FEW: 'few' = 'few';
|
export const QUANTITY_FEW: 'few' = 'few';
|
||||||
export const QUANTITY_MANY: 'many' = 'many';
|
export const QUANTITY_MANY: 'many' = 'many';
|
||||||
export const QUANTITY_LOTS: 'lots' = 'lots';
|
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 QUANTITIES = [QUANTITY_NONE, QUANTITY_FEW, QUANTITY_LOTS, QUANTITY_MANY];
|
||||||
|
|
||||||
|
export type Quantity = typeof QUANTITY_NONE | typeof QUANTITY_FEW | typeof QUANTITY_MANY | typeof QUANTITY_LOTS;
|
||||||
|
|
||||||
|
export type QuantityResponse = {
|
||||||
|
food_item_id: string,
|
||||||
|
quantity: Quantity,
|
||||||
|
date: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type QuantityFragment = { quantity: Quantity, lastupdated: number };
|
||||||
|
|
|
||||||
13
js/enhancers/quantityEnhancers.js
Normal file
13
js/enhancers/quantityEnhancers.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
//@flow
|
||||||
|
import { withProps } from 'recompose';
|
||||||
|
import { setQuantity } from '../apis/QuantityApi';
|
||||||
|
import { emit } from '../streams/QuantityStream';
|
||||||
|
import type { Quantity, QuantityResponse } from '../constants/QuantityConstants';
|
||||||
|
import { nth } from 'ramda';
|
||||||
|
|
||||||
|
export const withUpdateQuantity = withProps({
|
||||||
|
updateQuantity: async ({ foodItemId, quantity }: { foodItemId: string, quantity: Quantity }) => {
|
||||||
|
const newQuantity: QuantityResponse = nth(0, await setQuantity({ foodItemId, quantity }));
|
||||||
|
emit(newQuantity);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -3,6 +3,10 @@ import { type List, type Map } from 'immutable';
|
||||||
|
|
||||||
export const pushInto = <T>(list: List<T>, item: T): List<T> => list.push(item);
|
export const pushInto = <T>(list: List<T>, item: T): List<T> => list.push(item);
|
||||||
|
|
||||||
|
export function merge(a: Map<any, Object>, b: Map<any, Object>) {
|
||||||
|
return a.merge(b);
|
||||||
|
}
|
||||||
|
|
||||||
export const setById = (map: Map<string, any>, item: ?{ id?: string }): Map<string, any> => {
|
export const setById = (map: Map<string, any>, item: ?{ id?: string }): Map<string, any> => {
|
||||||
if (!item || !item.id) {
|
if (!item || !item.id) {
|
||||||
return map;
|
return map;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import React, { Component } from 'react';
|
||||||
import { Image, View } from 'react-native';
|
import { Image, View } from 'react-native';
|
||||||
import theme from '../ui-theme';
|
import theme from '../ui-theme';
|
||||||
import { StrongText, SubText } from '../components/ItemTile';
|
import { StrongText, SubText } from '../components/ItemTile';
|
||||||
import { type FoodItem } from '../records/FoodItemRecord';
|
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||||
import { type Quantity, QUANTITY_MANY } from '../constants/QuantityConstants';
|
import { type Quantity } from '../constants/QuantityConstants';
|
||||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||||
import { compose, pure } from 'recompose';
|
import { compose, pure } from 'recompose';
|
||||||
import IconButton from '../components/IconButton';
|
import IconButton from '../components/IconButton';
|
||||||
|
|
@ -16,6 +16,7 @@ import QuantityPicker from '../components/QuantityPicker';
|
||||||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||||
import { Link } from 'react-router-native';
|
import { Link } from 'react-router-native';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { withUpdateQuantity } from '../enhancers/quantityEnhancers';
|
||||||
|
|
||||||
const { foodItemDetails: style } = theme;
|
const { foodItemDetails: style } = theme;
|
||||||
|
|
||||||
|
|
@ -31,19 +32,18 @@ export class FoodItemDetail extends Component {
|
||||||
static displayName = 'FoodItemDetail';
|
static displayName = 'FoodItemDetail';
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
foodItem: FoodItem,
|
foodItem: FoodItemRecord,
|
||||||
place: PlaceRecord,
|
place: PlaceRecord,
|
||||||
|
updateQuantity: ({ foodItemId: string, quantity: Quantity }) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
state: {
|
state: {
|
||||||
isFavorite: boolean,
|
isFavorite: boolean,
|
||||||
quantity: Quantity,
|
|
||||||
currentImage: number,
|
currentImage: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
currentImage: 0,
|
currentImage: 0,
|
||||||
quantity: QUANTITY_MANY,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO placeholder implementation until we get a backend
|
// TODO placeholder implementation until we get a backend
|
||||||
|
|
@ -52,7 +52,10 @@ export class FoodItemDetail extends Component {
|
||||||
isFavorite: !prevState.isFavorite,
|
isFavorite: !prevState.isFavorite,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
updateAmount = (quantity: Quantity) => this.setState({ quantity });
|
updateAmount = (quantity: Quantity) => {
|
||||||
|
const { updateQuantity, foodItem: { id: foodItemId } } = this.props;
|
||||||
|
updateQuantity({ foodItemId, quantity });
|
||||||
|
};
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
addPhoto = () => {};
|
addPhoto = () => {};
|
||||||
|
|
@ -102,11 +105,11 @@ export class FoodItemDetail extends Component {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<QuantityPicker
|
<QuantityPicker
|
||||||
quantity={this.state.quantity || quantity}
|
quantity={quantity}
|
||||||
onValueChange={this.updateAmount}
|
onValueChange={this.updateAmount}
|
||||||
style={{ flex: 1, marginBottom: 10 }}
|
style={{ flex: 1, marginBottom: 10 }}
|
||||||
/>
|
/>
|
||||||
<SubText>Last updated at {moment(foodItem.lastupdated).format('H:mm A on MMM D, YYYY')}</SubText>
|
<SubText>Last updated at {moment(foodItem.lastupdated).format('h:mm A on MMM D, YYYY')}</SubText>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 2, ...contentTileStyle }}>
|
<View style={{ flex: 2, ...contentTileStyle }}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
@ -127,6 +130,6 @@ export class FoodItemDetail extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const enhance = compose(pure, withFoodItem, withPlaceForFoodItem);
|
const enhance = compose(pure, withFoodItem, withPlaceForFoodItem, withUpdateQuantity);
|
||||||
|
|
||||||
export default enhance(FoodItemDetail);
|
export default enhance(FoodItemDetail);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
//@flow
|
//@flow
|
||||||
import { createFoodItem } from '../records/FoodItemRecord';
|
import FoodItemRecord, { createFoodItem } from '../records/FoodItemRecord';
|
||||||
import { setById } from '../helpers/ImmutableHelpers';
|
import { setById } from '../helpers/ImmutableHelpers';
|
||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
import location$ from './LocationStream';
|
import location$ from './LocationStream';
|
||||||
import { getFoodItems, type FoodItemsForLocation } from '../apis/FoodItemsApi';
|
import { getFoodItems, type FoodItemsForLocation } from '../apis/FoodItemsApi';
|
||||||
import FilterSubject from './FilterStream';
|
import Filter$ from './FilterStream';
|
||||||
import FilterRecord from '../records/FilterRecord';
|
import FilterRecord from '../records/FilterRecord';
|
||||||
|
import Quantity$ from './QuantityStream';
|
||||||
|
import type { QuantityFragment } from '../constants/QuantityConstants';
|
||||||
|
|
||||||
export default location$
|
export default location$
|
||||||
.combineLatest(FilterSubject)
|
.combineLatest(Filter$)
|
||||||
.mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter }))
|
.mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter }))
|
||||||
.map(({ fooditems = [] }: FoodItemsForLocation) => {
|
.map(({ fooditems = [] }: FoodItemsForLocation) => {
|
||||||
return fooditems.map(createFoodItem).reduce(setById, new Map());
|
return fooditems.map(createFoodItem).reduce(setById, new Map());
|
||||||
|
})
|
||||||
|
.combineLatest(Quantity$, (foodItems: Map<string, FoodItemRecord>, quantities: Map<string, QuantityFragment>) => {
|
||||||
|
return foodItems.mergeDeepWith((foodItem, quantity) => foodItem.merge(quantity), quantities);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
24
js/streams/QuantityStream.js
Normal file
24
js/streams/QuantityStream.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
//@flow
|
||||||
|
import { ReplaySubject } from 'rxjs';
|
||||||
|
import type { QuantityResponse, QuantityFragment } from '../constants/QuantityConstants';
|
||||||
|
import { Map } from 'immutable';
|
||||||
|
|
||||||
|
const observable: ReplaySubject<QuantityResponse> = new ReplaySubject();
|
||||||
|
|
||||||
|
export function emit(val: ?QuantityResponse) {
|
||||||
|
observable.next(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
// force our observable to emit an initial empty map so that food items will load
|
||||||
|
emit(null);
|
||||||
|
|
||||||
|
export default observable.scan((quantitiesByFoodItemId: Map<string, QuantityFragment>, quantity?: QuantityResponse) => {
|
||||||
|
if (!quantity) {
|
||||||
|
return quantitiesByFoodItemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return quantitiesByFoodItemId.set(quantity.food_item_id, {
|
||||||
|
quantity: quantity.quantity,
|
||||||
|
lastupdated: quantity.date,
|
||||||
|
});
|
||||||
|
}, new Map());
|
||||||
|
|
@ -10,9 +10,7 @@ SELECT
|
||||||
q.quantity AS quantity,
|
q.quantity AS quantity,
|
||||||
q.date AS lastUpdated
|
q.date AS lastUpdated
|
||||||
FROM food_items f
|
FROM food_items f
|
||||||
LEFT OUTER JOIN (
|
LEFT OUTER JOIN latest_quantities q
|
||||||
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
|
ON f.id = q.food_item_id
|
||||||
WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(-97.7286718, 30.3033267),4326)::geography, 20 * 1609)
|
WHERE ST_DWithin(loc, ST_SetSRID(ST_Point(-97.7286718, 30.3033267),4326)::geography, 20 * 1609)
|
||||||
AND f.category IN ('desserts', 'beverages', 'entrees', 'other')
|
AND f.category IN ('desserts', 'beverages', 'entrees', 'other')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue