Merge branch 'dedupe-name-suggestions' into 'master'

dedupe fooditem name suggestions

See merge request aretherecookies/ui-mobile!1
This commit is contained in:
Bart Akeley 2018-09-09 17:28:34 +00:00
commit 1827b35403
4 changed files with 90 additions and 43 deletions

View file

@ -6,13 +6,12 @@ import { withReplaceRoute } from '../enhancers/routeEnhancers';
import { compose, onlyUpdateForKeys, withHandlers } from 'recompose'; import { compose, onlyUpdateForKeys, withHandlers } from 'recompose';
import { routeWithTitle } from '../helpers/RouteHelpers'; import { routeWithTitle } from '../helpers/RouteHelpers';
const FoodItemSaveBtn = ({ type Props = {
saveFoodItem,
loading,
}: {
saveFoodItem?: Function,
loading: boolean, loading: boolean,
}) => { doSave: Function,
};
const FoodItemSaveBtn = ({ loading, doSave }: Props) => {
const textStyle = { const textStyle = {
color: 'white', color: 'white',
marginRight: 20, marginRight: 20,
@ -21,7 +20,7 @@ const FoodItemSaveBtn = ({
}; };
return ( return (
<TouchableOpacity onPress={loading ? null : saveFoodItem}> <TouchableOpacity onPress={loading ? null : doSave}>
<Text style={textStyle}>SAVE</Text> <Text style={textStyle}>SAVE</Text>
</TouchableOpacity> </TouchableOpacity>
); );
@ -31,7 +30,7 @@ export default compose(
withCreateFoodItemState, withCreateFoodItemState,
withReplaceRoute, withReplaceRoute,
withHandlers({ withHandlers({
saveFoodItem: ({ saveFoodItem, setLoading, setError, replaceRoute }) => async () => { doSave: ({ saveFoodItem, setLoading, setError, replaceRoute }) => async () => {
try { try {
setLoading(true); setLoading(true);
const { id, name } = await saveFoodItem(); const { id, name } = await saveFoodItem();

View file

@ -1,11 +1,33 @@
// @flow // @flow
import withProps from 'recompose/withProps'; import withProps from 'recompose/withProps';
import compose from 'recompose/compose'; import compose from 'recompose/compose';
import { path } from 'ramda';
import mapPropsStream from 'recompose/mapPropsStream'; import mapPropsStream from 'recompose/mapPropsStream';
import FoodItems$ from '../streams/FoodItemsStream'; import FoodItems$ from '../streams/FoodItemsStream';
import typeof FoodItemRecord from '../records/FoodItemRecord'; import typeof FoodItemRecord from '../records/FoodItemRecord';
import { Map } from 'immutable'; import { Map, Set } from 'immutable';
import { pipe, path, toLower, equals } from 'ramda';
type FindPredicate<A> = (left: A) => (right: A) => Boolean;
const getName: (f: FoodItemRecord) => String = pipe(
path(['name']),
toLower
);
const matchesName: FindPredicate<FoodItemRecord> = left => right => {
return equals(getName(left), getName(right));
};
const addIfNotExisting = (predicate: FindPredicate<FoodItemRecord>) => (
set: Set<FoodItemRecord>,
item: FoodItemRecord
) => {
return !set.find(predicate(item)) ? set.add(item) : set;
};
const intoSet = reducer => items => {
return items ? items.reduce(reducer, new Set()) : new Set();
};
export const withFoodItems = mapPropsStream(props$ => export const withFoodItems = mapPropsStream(props$ =>
props$.combineLatest(FoodItems$, (props, foodItems) => { props$.combineLatest(FoodItems$, (props, foodItems) => {
@ -57,3 +79,12 @@ export const withFoodItemsGroupedByPlace = compose(
}; };
}) })
); );
export const withUniqueFoodItems = compose(
withFoodItemsAsSeq,
withProps(({ foodItemsSeq }) => {
return {
foodItemsSeq: intoSet(addIfNotExisting(matchesName))(foodItemsSeq),
};
})
);

View file

@ -6,30 +6,37 @@ import FoodItemList from '../components/FoodItemList';
import FoodItemRecord from '../records/FoodItemRecord'; import FoodItemRecord from '../records/FoodItemRecord';
import { StrongText } from '../components/ItemTile'; import { StrongText } from '../components/ItemTile';
import { Toolbar } from 'react-native-material-ui'; import { Toolbar } from 'react-native-material-ui';
import FilterRecord from '../records/FilterRecord';
import { compose, pure } from 'recompose';
import { withUniqueFoodItems } from '../enhancers/foodItemEnhancers';
import { Set } from 'immutable';
type Props = {
onClose: () => void,
onUpdate: (name: string) => void,
foodItemsSeq: ?Set<FoodItemRecord>,
};
class NameModal extends Component { class NameModal extends Component {
static displayName = 'NameModal'; static displayName = 'NameModal';
props: { props: Props;
onClose: () => void,
onUpdate: (name: string) => void,
};
state: { state: {
text: string, filter: FilterRecord,
}; };
state = { state = {
text: '', filter: new FilterRecord(),
}; };
save = () => { save = () => {
this.props.onUpdate(this.state.text); this.props.onUpdate(this.state.filter.search);
this.props.onClose(); this.props.onClose();
}; };
setText = (text: string) => { setText = (text: string) => {
this.setState({ text }); this.setState(({ filter }) => ({ filter: filter.set('search', text) }));
}; };
updateAndClose = (text: string) => { updateAndClose = (text: string) => {
@ -37,15 +44,9 @@ class NameModal extends Component {
this.props.onClose(); this.props.onClose();
}; };
renderFoodItem = (foodItem: typeof FoodItemRecord) => (
<TouchableOpacity key={foodItem.id} onPress={() => this.updateAndClose(foodItem.name)}>
<StrongText style={{ paddingTop: 20, paddingBottom: 20 }}>{foodItem.name}</StrongText>
</TouchableOpacity>
);
render() { render() {
const { onClose } = this.props; const { onClose, foodItemsSeq } = this.props;
const { text } = this.state; const { filter } = this.state;
return ( return (
<View <View
style={{ style={{
@ -71,7 +72,7 @@ class NameModal extends Component {
}, },
}} }}
/> />
{!!text && ( {!!filter.search && (
<TextButton <TextButton
text="Not here, it's a new product" text="Not here, it's a new product"
onPress={this.save} onPress={this.save}
@ -79,11 +80,24 @@ class NameModal extends Component {
/> />
)} )}
<ScrollView style={{ paddingLeft: 78 }}> <ScrollView style={{ paddingLeft: 78 }}>
<FoodItemList filter={text} limit={10} renderFoodItem={this.renderFoodItem} /> <FoodItemList filter={filter} limit={10} foodItemsSeq={foodItemsSeq}>
{(foodItem: FoodItemRecord) => (
<TouchableOpacity
key={foodItem.id}
onPress={() => this.updateAndClose(foodItem.name)}>
<StrongText style={{ paddingTop: 20, paddingBottom: 20 }}>
{foodItem.name}
</StrongText>
</TouchableOpacity>
)}
</FoodItemList>
</ScrollView> </ScrollView>
</View> </View>
); );
} }
} }
export default NameModal; export default compose(
pure,
withUniqueFoodItems
)(NameModal);

View file

@ -24,6 +24,23 @@ import { withCreateFoodItemState } from '../enhancers/createFoodItemEnhancers';
import { withPlaceForFoodItem, withPlaceId, withPlaceActions } from '../enhancers/placeEnhancers'; import { withPlaceForFoodItem, withPlaceId, withPlaceActions } from '../enhancers/placeEnhancers';
import Spinner from 'react-native-loading-spinner-overlay'; import Spinner from 'react-native-loading-spinner-overlay';
import { openImagePicker } from '../helpers/ImagePickerHelpers'; import { openImagePicker } from '../helpers/ImagePickerHelpers';
import { IndexedSeq } from 'immutable';
type Props = {
foodItem: typeof FoodItemRecord,
setPropOfFoodItem: Function,
toggleNameModal: Function,
setFoodItem: Function,
setModalsVisible: Function,
place: ?PlaceRecord,
updatePlace: (place: GooglePlaceObject) => void,
addImage: (uri: string) => void,
setImagePreview: (index?: number) => void,
loading: boolean,
setLoading: (arg: boolean) => void,
emitPlace: (place: Object) => void,
withFoodItemsAsSeq: IndexedSeq<FoodItemRecord>,
};
type GooglePlaceObject = { type GooglePlaceObject = {
placeID: string, placeID: string,
@ -54,20 +71,6 @@ const openPlaceModal = (onChoosePlace: (place: GooglePlaceObject) => void) => ()
RNGooglePlaces.openAutocompleteModal({ type: 'establishment' }).then(onChoosePlace); RNGooglePlaces.openAutocompleteModal({ type: 'establishment' }).then(onChoosePlace);
}; };
type Props = {
foodItem: typeof FoodItemRecord,
setPropOfFoodItem: Function,
toggleNameModal: Function,
setFoodItem: Function,
setModalsVisible: Function,
place: ?PlaceRecord,
updatePlace: (place: GooglePlaceObject) => void,
addImage: (uri: string) => void,
setImagePreview: (index?: number) => void,
loading: boolean,
setLoading: (arg: boolean) => void,
emitPlace: (place: Object) => void,
};
const CreateFoodItem = (props: Props) => { const CreateFoodItem = (props: Props) => {
const { const {
foodItem, foodItem,