index.tsx 8.0 KB

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