index.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import React, { Component } from "react";
  2. import {
  3. fetchIfNeeded as fetch,
  4. receiveRecipes as receive,
  5. selectRecipe as select,
  6. deleteRecipeActionCreator as deleteRecipe,
  7. } from "containers/Recipes/List/actions";
  8. import {
  9. addRecipeToCart,
  10. removeFromCart
  11. } from 'containers/ShoppingCart/actions';
  12. import plannerActions from 'containers/Planner/actions';
  13. import { connect } from "react-redux";
  14. import {
  15. Grid,
  16. GridCell as Cell,
  17. GridRow as Row
  18. } from '@rmwc/grid';
  19. import Button from "components/Button";
  20. import Card from 'components/Card';
  21. import RecipeCard from 'components/RecipeCard';
  22. import { DBRecipe } from 'types/recipes';
  23. import Navbar from 'components/Navbar';
  24. import { Link, RouteComponentProps } from 'react-router-dom';
  25. import Input from 'components/Input';
  26. import { throttle } from 'throttle-debounce';
  27. import Spinner from 'components/Spinner';
  28. import '@rmwc/grid/styles';
  29. import './styles.scss';
  30. import { UiState, Display } from "types/ui";
  31. interface RecipeListProps extends RouteComponentProps {
  32. data: DBRecipe[],
  33. isFetching: boolean,
  34. selectedRecipe: DBRecipe | undefined,
  35. ui: UiState,
  36. fetchRecipes: (query: string) => undefined,
  37. receiveRecipes: (recipes: DBRecipe[]) => undefined,
  38. selectRecipe: (recipe?: DBRecipe) => undefined,
  39. removeFromCart: (recipe: DBRecipe) => undefined,
  40. addRecipeToCart: (recipe: DBRecipe) => undefined,
  41. addRecipeToPlanner: (recipe: DBRecipe) => undefined,
  42. deleteRecipe: (id: string) => undefined
  43. };
  44. enum Layout {
  45. VerticalSplit,
  46. Module,
  47. List
  48. }
  49. type RecipeListState = {
  50. phoneDisplay: boolean,
  51. search: string,
  52. view: Layout
  53. }
  54. class RecipeList extends Component<RecipeListProps, RecipeListState> {
  55. constructor(props: RecipeListProps) {
  56. super(props);
  57. this.state = {
  58. phoneDisplay: this.props.ui.display === Display.Mobile,
  59. search: '',
  60. view: Layout.Module
  61. }
  62. }
  63. componentDidMount() {
  64. const { fetchRecipes } = this.props;
  65. fetchRecipes('');
  66. }
  67. autocompleteSearch = throttle(500, (query: string) => {
  68. this.props.fetchRecipes(query)
  69. })
  70. changeQuery = (query: string) => {
  71. this.setState({
  72. search: query
  73. }, () => {
  74. this.autocompleteSearch(this.state.search)
  75. })
  76. }
  77. componentDidUpdate(prevProps: any) {
  78. const { data, receiveRecipes, isFetching } = this.props;
  79. if (isFetching === false && data.length !== prevProps.data.length) {
  80. receiveRecipes(data);
  81. }
  82. }
  83. cleanRecipeSelection() {
  84. this.props.selectRecipe();
  85. }
  86. handleRecipeSelection(recipe: DBRecipe) {
  87. return (event: React.MouseEvent) => {
  88. if (this.state.phoneDisplay) {
  89. this.props.history.push('/recipes/view/' + recipe._id)
  90. this.cleanRecipeSelection();
  91. } else {
  92. this.props.selectRecipe(recipe)
  93. }
  94. };
  95. }
  96. handleEditRecipe = (id = '') => (event: React.MouseEvent) => {
  97. this.props.history.push('/recipes/edit/' + id)
  98. this.cleanRecipeSelection();
  99. }
  100. handleAddToShopping = (recipe: DBRecipe) => (event: React.MouseEvent) => {
  101. this.props.addRecipeToCart(recipe)
  102. }
  103. handleAddToPlanner = (recipe: DBRecipe) => (event: React.MouseEvent) => {
  104. this.props.addRecipeToPlanner(recipe)
  105. }
  106. handleDeleteRecipe = (id: string) => (event: React.MouseEvent) => {
  107. this.props.deleteRecipe(id)
  108. }
  109. handler = (event: React.MouseEvent) => {
  110. console.log('click', event);
  111. }
  112. actions = (recipe: DBRecipe) => [{
  113. label: 'edit',
  114. handler: this.handleEditRecipe(recipe._id),
  115. }, {
  116. label: 'shopping',
  117. handler: this.handleAddToShopping(recipe)
  118. }, {
  119. label: 'planner',
  120. handler: this.handleAddToPlanner(recipe)
  121. }];
  122. icons = (id = '') => [{
  123. icon: 'open_in_new',
  124. handler: () => this.props.history.push('/recipes/view/' + id)
  125. }, {
  126. icon: 'delete_outline',
  127. handler: this.handleDeleteRecipe(id)
  128. }]
  129. render() {
  130. const {data, selectedRecipe, isFetching} = this.props;
  131. return(
  132. <div className='cbk-recipes-list'>
  133. <Navbar
  134. title="Recipes"
  135. >
  136. <Input
  137. value={this.state.search}
  138. label='search'
  139. onChange={(e) => this.changeQuery(e.currentTarget.value)}
  140. button={{
  141. icon: 'clear',
  142. onClick: () => this.changeQuery('')
  143. }}
  144. className='cbk-recipes-list__search'
  145. />
  146. <Link to='/recipes/create'>
  147. <Button unelevated>
  148. Create Recipe
  149. </Button>
  150. </Link>
  151. <Button icon="view_module" active></Button>
  152. <Button icon="view_list"></Button>
  153. <Button icon="view_sidebar"></Button>
  154. </Navbar>
  155. {
  156. isFetching && (<Spinner/>)
  157. }
  158. <Grid>
  159. <Row>
  160. <Cell span={4} phone={4} tablet={8}>
  161. {
  162. data.map((recipe, i) => {
  163. return (
  164. <Card
  165. key={i}
  166. title={recipe.name}
  167. onClick={this.handleRecipeSelection(recipe)}
  168. summary={recipe.summary}
  169. actions={this.actions(recipe)}
  170. icons={this.icons(recipe._id)}
  171. img={recipe.image}
  172. highlight={selectedRecipe ? recipe._id === selectedRecipe._id : undefined}
  173. />
  174. )
  175. })
  176. }
  177. </Cell>
  178. <Cell span={8} phone={4} tablet={8}>
  179. { selectedRecipe !== undefined &&
  180. <RecipeCard
  181. recipe={selectedRecipe}
  182. />
  183. }
  184. </Cell>
  185. </Row>
  186. </Grid>
  187. </div>
  188. )
  189. }
  190. }
  191. const mapStateToProps = (state: any) => {
  192. return {
  193. ...state.recipes,
  194. ui: state.ui,
  195. shoppingCart: state.shoppingCart.cart
  196. };
  197. }
  198. const mapDispatchToProps = (dispatch: any) => {
  199. return {
  200. fetchRecipes: (query: string) => {
  201. dispatch(fetch(query))
  202. },
  203. receiveRecipes: (recipes: DBRecipe[]) => {
  204. dispatch(receive(recipes))
  205. },
  206. selectRecipe: (recipe: DBRecipe) => {
  207. dispatch(select(recipe))
  208. },
  209. addRecipeToCart: (recipe: DBRecipe) => {
  210. dispatch(addRecipeToCart(recipe))
  211. },
  212. removeFromCart: (recipe: DBRecipe) => {
  213. dispatch(removeFromCart(recipe))
  214. },
  215. addRecipeToPlanner: (recipe: DBRecipe) => {
  216. dispatch(plannerActions.addToBacklog(recipe))
  217. },
  218. deleteRecipe: (id: string) => {
  219. dispatch(deleteRecipe(id))
  220. }
  221. }
  222. }
  223. export default connect(
  224. mapStateToProps,
  225. mapDispatchToProps
  226. )(RecipeList);