index.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import React, { Component } from 'react';
  2. import { RouteComponentProps } from 'react-router';
  3. import Navbar from 'components/Navbar';
  4. import { AppState } from 'store/configureStore';
  5. import { connect } from 'react-redux';
  6. import { PlannerState, Weekday, Meal, PlannerMode, RecipePlan, WeekPlan, Meals } from 'types/planner';
  7. import Card from 'components/Card';
  8. import PlannerActions, { fetchPlannerActionCreator, savePlannerActionCreator } from './actions';
  9. import moment, { Moment } from 'moment';
  10. import { DragDropContext, Droppable, Draggable, DropResult, OnDragEndResponder } from 'react-beautiful-dnd';
  11. import './styles.scss';
  12. import Button from 'components/Button';
  13. import { ThunkDispatch } from 'redux-thunk';
  14. import { bindActionCreators } from 'redux';
  15. import RecipeSearch from 'components/RecipeSearch';
  16. import { DBRecipe } from 'src/types/recipes';
  17. type Actions = typeof PlannerActions
  18. interface PlannerContainerProps extends RouteComponentProps, PlannerState, Actions {
  19. fetch: typeof fetchPlannerActionCreator,
  20. save: typeof savePlannerActionCreator
  21. }
  22. interface PlannerContainerState {
  23. week: [Weekday, string][],
  24. }
  25. class PlannerContainer extends Component<PlannerContainerProps, PlannerContainerState> {
  26. constructor(props: PlannerContainerProps) {
  27. super(props);
  28. this.state = {
  29. week: Object.keys(props.planner).map((weekday) => ([ weekday as Weekday, moment(props.planner[weekday as Weekday].date).format()])),
  30. }
  31. }
  32. _prevState = {
  33. planner: { ...this.props.planner },
  34. backlog: { ...this.props.backlog }
  35. }
  36. componentDidMount() {
  37. this.props.fetch(this.props.from, this.props.to);
  38. }
  39. componentDidUpdate(prevProps: PlannerContainerProps) {
  40. if (prevProps.mode === PlannerMode.View && this.props.mode === PlannerMode.Edit) {
  41. this._prevState = {
  42. planner: {...this.props.planner},
  43. backlog: [...this.props.backlog]
  44. };
  45. }
  46. }
  47. assignRecipe = (result: DropResult) => {
  48. const recipe = this.findRecipe(result.draggableId);
  49. if (result.destination && recipe) {
  50. const [idx, day, meal] = result.destination.droppableId.split('-');
  51. this.props.assignToDay(recipe, day as Weekday, parseInt(meal) as Meal);
  52. this.props.removeFromBacklog(recipe);
  53. this.props.editPlanner();
  54. }
  55. }
  56. removeMeal = (day: Weekday, meal: Meal) => {
  57. this.props.editPlanner();
  58. return this.props.removeMeal(day, meal);
  59. }
  60. findRecipe = (recipeId: string) => this.props.backlog.find(recipe => recipe._id === recipeId);
  61. changeMode = (mode: PlannerMode) => this.props.changePlannerMode(mode);
  62. cancel = () => this.props.cancelSavePlanner(this._prevState.planner, this._prevState.backlog)
  63. render () {
  64. return (
  65. <div className='cbk-planner'>
  66. <Navbar
  67. title='Planner'
  68. >
  69. {
  70. this.props.mode === PlannerMode.View ? (
  71. <Button outlined raised onClick={() => this.changeMode(PlannerMode.Edit)}>Edit</Button>
  72. ) : (
  73. <div>
  74. <Button outlined raised onClick={() => {
  75. this.props.save(this.props.planner, this.props.from, this.props.to)
  76. this.changeMode(PlannerMode.View)
  77. }}>Save</Button>
  78. <Button outlined raised onClick={() => {
  79. this.cancel();
  80. this.changeMode(PlannerMode.View)
  81. }}> Cancel </Button>
  82. </div>
  83. )
  84. }
  85. </Navbar>
  86. <DisplayPlanner
  87. mode={this.props.mode}
  88. week={this.state.week}
  89. weekNumber={this.props.week}
  90. onDragEnd={this.assignRecipe}
  91. backlog={this.props.backlog}
  92. planner={this.props.planner}
  93. removeMeal={this.removeMeal}
  94. addToBacklog={this.props.addToBacklog}
  95. />
  96. </div>
  97. );
  98. }
  99. }
  100. const DisplayPlanner: React.SFC<{
  101. week: [Weekday, string][],
  102. onDragEnd: OnDragEndResponder,
  103. weekNumber: number,
  104. backlog: RecipePlan[],
  105. planner: WeekPlan,
  106. removeMeal: typeof PlannerActions.removeMeal,
  107. addToBacklog: (recipe: DBRecipe) => {},
  108. mode?: PlannerMode,
  109. }> = ({ mode, onDragEnd, backlog, weekNumber, week, planner, removeMeal, addToBacklog }) => (
  110. <section className='cbk-planner__body'>
  111. <DragDropContext onDragEnd={onDragEnd}>
  112. {
  113. mode === PlannerMode.Edit ? (
  114. <div className='cbk-planner__body__backlog'>
  115. <RecipeSearch onSelect={(selected)=>{ addToBacklog(selected) }}/>
  116. <Droppable droppableId='recipeList'>
  117. {(provided) => (
  118. <div ref={provided.innerRef}>
  119. {backlog.map((item, index) => (
  120. <Draggable
  121. key={item._id}
  122. draggableId={item._id}
  123. index={index}>
  124. {provided => (
  125. <div
  126. ref={provided.innerRef}
  127. {...provided.draggableProps}
  128. {...provided.dragHandleProps}
  129. >
  130. <Card
  131. key={item._id}
  132. title={item.name}
  133. onClick={() => {}}
  134. />
  135. </div>
  136. )}
  137. </Draggable>
  138. ))}
  139. {provided.placeholder}
  140. </div>
  141. )}
  142. </Droppable>
  143. </div>
  144. ) : null
  145. }
  146. <div className='cbk-planner__body__calendar'>
  147. <div>Week {weekNumber}</div>
  148. <div className='container'>
  149. <div className='day-schedule day-schedule--meals'>
  150. <div className='day-schedule--date'></div>
  151. {
  152. Meals.map((meal, key) => (
  153. <div className='day-schedule--meal' key={key}>
  154. <h5>{Meal[meal]}</h5>
  155. </div>
  156. ))
  157. }
  158. </div>
  159. {
  160. week.map(([weekday, day], dayNumber) => (
  161. <div key={dayNumber} className='day-schedule'>
  162. <div className='day-schedule--date'>
  163. <h5>{weekday} {moment(day).format('DD')}</h5>
  164. </div>
  165. {
  166. Meals.map((meal, key) => {
  167. const recipe = planner[weekday][meal];
  168. return (
  169. <div className='day-schedule--meal' key={key}>
  170. <Droppable droppableId={`${dayNumber}-${weekday}-${meal}`}>
  171. {
  172. provided => (
  173. <div className='day-schedule-content' ref={provided.innerRef}>
  174. { provided.placeholder }
  175. {
  176. recipe ? (
  177. <div className='meal-card'>
  178. <div className="meal-card--actions">
  179. {
  180. recipe && mode === PlannerMode.Edit ? (
  181. <Button icon='clear' onClick={() => removeMeal(weekday, meal)} small></Button>
  182. ) : null
  183. }
  184. </div>
  185. <h5>{ recipe.name }</h5>
  186. </div>
  187. ) : null
  188. }
  189. </div>
  190. )
  191. }
  192. </Droppable>
  193. </div>
  194. )
  195. })
  196. }
  197. </div>
  198. ))
  199. }
  200. </div>
  201. </div>
  202. </DragDropContext>
  203. </section>
  204. )
  205. const mapStateToProps = (state: AppState) => {
  206. return state.planner
  207. }
  208. const mapDispatchToProps = (dispatch: ThunkDispatch<AppState, any, any>) => ({
  209. fetch: (from: Moment, to: Moment) => dispatch(fetchPlannerActionCreator(from, to)),
  210. save: (plan: WeekPlan, from: Moment, to: Moment) => dispatch(savePlannerActionCreator(plan, from, to)),
  211. ...bindActionCreators(PlannerActions, dispatch)
  212. })
  213. export default connect(
  214. mapStateToProps,
  215. mapDispatchToProps
  216. )(PlannerContainer);