mirror of
https://gitlab.com/wheres-the-tp/ui-mobile.git
synced 2026-01-25 02:44:56 -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
|
||||
import { memoize } from 'ramda';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
|
||||
const BASE_URL = 'aretherecookies.herokuapp.com';
|
||||
// const BASE_URL = '192.168.1.6:3000';
|
||||
import { BASE_URL } from '../constants/AppConstants';
|
||||
|
||||
export type FoodItemsFilter = {
|
||||
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_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 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 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> => {
|
||||
if (!item || !item.id) {
|
||||
return map;
|
||||
|
|
|
|||
|
|
@ -3,8 +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 } from '../records/FoodItemRecord';
|
||||
import { type Quantity, QUANTITY_MANY } from '../constants/QuantityConstants';
|
||||
import typeof FoodItemRecord from '../records/FoodItemRecord';
|
||||
import { type Quantity } from '../constants/QuantityConstants';
|
||||
import typeof PlaceRecord from '../records/PlaceRecord';
|
||||
import { compose, pure } from 'recompose';
|
||||
import IconButton from '../components/IconButton';
|
||||
|
|
@ -16,6 +16,7 @@ import QuantityPicker from '../components/QuantityPicker';
|
|||
import { routeWithTitle } from '../helpers/RouteHelpers';
|
||||
import { Link } from 'react-router-native';
|
||||
import moment from 'moment';
|
||||
import { withUpdateQuantity } from '../enhancers/quantityEnhancers';
|
||||
|
||||
const { foodItemDetails: style } = theme;
|
||||
|
||||
|
|
@ -31,19 +32,18 @@ export class FoodItemDetail extends Component {
|
|||
static displayName = 'FoodItemDetail';
|
||||
|
||||
props: {
|
||||
foodItem: FoodItem,
|
||||
foodItem: FoodItemRecord,
|
||||
place: PlaceRecord,
|
||||
updateQuantity: ({ foodItemId: string, quantity: Quantity }) => void,
|
||||
};
|
||||
|
||||
state: {
|
||||
isFavorite: boolean,
|
||||
quantity: Quantity,
|
||||
currentImage: number,
|
||||
};
|
||||
|
||||
state = {
|
||||
currentImage: 0,
|
||||
quantity: QUANTITY_MANY,
|
||||
};
|
||||
|
||||
// TODO placeholder implementation until we get a backend
|
||||
|
|
@ -52,7 +52,10 @@ export class FoodItemDetail extends Component {
|
|||
isFavorite: !prevState.isFavorite,
|
||||
}));
|
||||
|
||||
updateAmount = (quantity: Quantity) => this.setState({ quantity });
|
||||
updateAmount = (quantity: Quantity) => {
|
||||
const { updateQuantity, foodItem: { id: foodItemId } } = this.props;
|
||||
updateQuantity({ foodItemId, quantity });
|
||||
};
|
||||
|
||||
// TODO
|
||||
addPhoto = () => {};
|
||||
|
|
@ -102,11 +105,11 @@ export class FoodItemDetail extends Component {
|
|||
}}
|
||||
>
|
||||
<QuantityPicker
|
||||
quantity={this.state.quantity || quantity}
|
||||
quantity={quantity}
|
||||
onValueChange={this.updateAmount}
|
||||
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 style={{ flex: 2, ...contentTileStyle }}>
|
||||
<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);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
//@flow
|
||||
import { createFoodItem } from '../records/FoodItemRecord';
|
||||
import FoodItemRecord, { createFoodItem } from '../records/FoodItemRecord';
|
||||
import { setById } from '../helpers/ImmutableHelpers';
|
||||
import { Map } from 'immutable';
|
||||
import location$ from './LocationStream';
|
||||
import { getFoodItems, type FoodItemsForLocation } from '../apis/FoodItemsApi';
|
||||
import FilterSubject from './FilterStream';
|
||||
import Filter$ from './FilterStream';
|
||||
import FilterRecord from '../records/FilterRecord';
|
||||
import Quantity$ from './QuantityStream';
|
||||
import type { QuantityFragment } from '../constants/QuantityConstants';
|
||||
|
||||
export default location$
|
||||
.combineLatest(FilterSubject)
|
||||
.combineLatest(Filter$)
|
||||
.mergeMap(([loc, filter]: [Position, FilterRecord]) => getFoodItems({ loc, filter }))
|
||||
.map(({ fooditems = [] }: FoodItemsForLocation) => {
|
||||
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.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
|
||||
LEFT OUTER JOIN latest_quantities 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')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue