Przeglądaj źródła

Add drag and drop functionality: Sets meals on planner

Tatiana Inama 7 lat temu
rodzic
commit
21077724b9

+ 3 - 1
recipes/package.json

@@ -20,6 +20,7 @@
     "@types/jest": "^24.0.15",
     "@types/node": "^11.13.17",
     "@types/react": "^16.8.23",
+    "@types/react-beautiful-dnd": "^11.0.3",
     "@types/react-dom": "^16.8.3",
     "@types/react-router-dom": "^4.3.4",
     "@types/storybook__addon-actions": "^3.4.3",
@@ -35,6 +36,7 @@
     "npm": "^6.11.2",
     "ramda": "^0.26.1",
     "react": "^16.8.4",
+    "react-beautiful-dnd": "^11.0.5",
     "react-dom": "^16.8.4",
     "react-redux": "^7.1.0",
     "react-router-dom": "^5.0.1",
@@ -47,7 +49,7 @@
     "typescript": "^3.5.3"
   },
   "scripts": {
-    "start": "set PORT=3006 && react-scripts start",
+    "start": "PORT=3006 react-scripts start",
     "build": "react-scripts build",
     "test": "react-scripts test",
     "eject": "react-scripts eject",

+ 5 - 4
recipes/src/components/Card/index.tsx

@@ -18,6 +18,7 @@ type CBKCardProps = {
   title: string,
   summary?: string,
   img?: string,
+  noMedia?: boolean,
   actions?: {
     label: string,
     handler: (event: React.MouseEvent) => void,
@@ -29,14 +30,14 @@ type CBKCardProps = {
   onClick: (event: React.MouseEvent) => void,
 }
 
-function CBKCard(props: CBKCardProps){
-  const {onClick, img, title, summary, actions, icons} = props;
+function CBKCard({onClick, img, title, summary, actions, icons, noMedia = false}: CBKCardProps){
+  
   return(
     <Card outlined className='cbk-card'>
       <CardPrimaryContent onClick={onClick}>
-        <CardMedia square imageUrl={props.img || sample_img}></CardMedia>
+        { noMedia ? null : <CardMedia square imageUrl={img || sample_img}></CardMedia>}
         <div className='cbk-card__main'>
-          <h6 className='cbk-card__main__title'>{props.title}</h6>
+          <h6 className='cbk-card__main__title'>{title}</h6>
           {
             summary ? 
               <p className='cbk-card__main__summary'>{summary}</p> :

+ 2 - 3
recipes/src/containers/Planner/actions.ts

@@ -42,6 +42,5 @@ export type AssignToDay = {
 
 export type ActionTypes = AddToBacklog | RemoveFromBacklog | AssignToDay;
 
-export default {
-  ...actions
-}
+export type Actions = typeof actions;
+export default actions

+ 132 - 8
recipes/src/containers/Planner/index.tsx

@@ -3,13 +3,57 @@ import { RouteComponentProps } from 'react-router';
 import Navbar from 'components/Navbar';
 import { AppState } from 'store/configureStore';
 import { connect } from 'react-redux';
-import { PlannerState } from 'types/planner';
+import { PlannerState, Weekday, Meal } from 'types/planner';
 import { Grid, Row, Cell } from "@material/react-layout-grid";
+import Card from 'components/Card';
+import PlannerActions, { Actions } from './actions';
+import moment, { Moment } from 'moment';
+import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
+import './styles.scss';
+import { mkWeekData, mkWeekDay } from 'services/time';
+import { DBRecipe } from 'types/recipes';
 
-interface PlannerContainerProps extends RouteComponentProps, PlannerState {
+
+interface PlannerContainerProps extends RouteComponentProps, PlannerState, Actions {
+}
+
+const [ DAY, DATE ] = [ 0, 1 ];
+
+type _WeekDay = [ Weekday, string ]
+interface PlannerContainerState {
+  week: [Weekday, string][]
 }
 
-class PlannerContainer extends Component<PlannerContainerProps> {
+const DisplayMeal = (meal?: DBRecipe) => meal ? (
+  <Card
+    key={meal._id}
+    onClick={()=>{}}
+    title={meal.name}
+    noMedia
+  />
+) : null;
+
+class PlannerContainer extends Component<PlannerContainerProps, PlannerContainerState> {
+
+  constructor(props: PlannerContainerProps) {
+    super(props);
+    this.state = {
+      week: mkWeekData(this.props.data.week)
+    }
+  }
+
+  assignRecipe = (result: DropResult) => {
+    const recipe = this.findRecipe(result.draggableId);
+    if (result.destination && recipe) {
+      const [idx, day, meal] = result.destination.droppableId.split('-');
+      const dayData = this.state.week[parseInt(idx)];
+      this.props.assignToDay(recipe, moment(dayData[DATE]), meal as Meal);
+      this.props.removeFromBacklog(recipe);
+    }
+  }
+
+  findRecipe = (recipeId: string) => this.props.backlog.find(recipe => recipe._id === recipeId);
+
   render () {
     return (
       <div className='cbk-planner'>
@@ -19,10 +63,89 @@ class PlannerContainer extends Component<PlannerContainerProps> {
           <div>Week {this.props.data.week}</div>
         </Navbar>
         <section className='cbk-planner__body'>
-          <div className='cbk-planner__body__calendar'>
-            
-          </div>
-          <div className='cbk-planner__body__backlog'></div>
+          <DragDropContext onDragEnd={this.assignRecipe}>
+            <Grid>
+              <Row>
+                <Cell columns={2}>
+                  <div className='cbk-planner__body__backlog'>
+                    <Droppable droppableId='recipeList'>
+                      {(provided) => (
+                        <div ref={provided.innerRef}>
+                          {this.props.backlog.map((item, index) => (
+                              <Draggable
+                                  key={item._id}
+                                  draggableId={item._id}
+                                  index={index}>
+                                  {(provided, snapshot) => (
+                                    <div
+                                      ref={provided.innerRef}
+                                      {...provided.draggableProps}
+                                      {...provided.dragHandleProps}
+                                    >
+                                      <Card
+                                        key={item._id}
+                                        title={item.name}
+                                        onClick={() => {}}
+                                      />
+                                    </div>
+                                  )}
+                              </Draggable>
+                          ))}
+                          {provided.placeholder}
+                      </div>
+                      )}
+                    </Droppable>
+                  </div>
+                </Cell>
+
+                {/* {
+                  this.props.backlog.map((recipe, i) => (
+                    <Card
+                      key={i}
+                      title={recipe.name}
+                      onClick={() => {}}
+                      summary={recipe.summary}
+                    />
+                  ))
+                } */}
+                <Cell columns={10}>
+                  <div className='cbk-planner__body__calendar'>
+                    <div className='container'>
+                      {
+                        this.state.week.map((data, weekdayNumber) => (
+                          <div key={weekdayNumber} className='day-schedule'>
+                            <div className='day-schedule--name'>
+                              {data[DAY]} {moment(data[DATE]).format('D')}
+                            </div>
+                            <div className='day-schedule--lunch'>
+                              <Droppable droppableId={`${weekdayNumber}-${data[DAY]}-lunch`}>
+                                {provided => (
+                                  <div className='day-schedule-content' ref={provided.innerRef}>
+                                    {provided.placeholder}
+                                    { DisplayMeal(this.props.data[data[DAY] as Weekday].lunch) }
+                                  </div>
+                                )}
+                              </Droppable>
+                            </div>
+                            <div className='day-schedule--dinner'>
+                              <Droppable droppableId={`${weekdayNumber}-${data[DAY]}-dinner`}>
+                                {provided => (
+                                  <div className='day-schedule-content' ref={provided.innerRef}>
+                                    {provided.placeholder}
+                                    { DisplayMeal(this.props.data[data[DAY] as Weekday].dinner) }
+                                  </div>
+                                )}
+                              </Droppable>
+                            </div>
+                          </div>
+                        ))
+                      }
+                    </div>
+                  </div>
+                </Cell>
+              </Row>
+            </Grid>
+          </DragDropContext>
         </section>
       </div>
     );
@@ -34,5 +157,6 @@ const mapStateToProps = (state: AppState) => {
 }
 
 export default connect(
-  mapStateToProps
+  mapStateToProps,
+  PlannerActions
 )(PlannerContainer);

+ 10 - 5
recipes/src/containers/Planner/reducers.ts

@@ -1,7 +1,8 @@
 import { ActionTypes, ADD_TO_BACKLOG, ASSIGN_TO_DAY } from './actions';
-import Planner, { PlannerState } from 'types/planner';
+import Planner, { PlannerState, Weekday } from 'types/planner';
 import { getWeekNumber, mkWeekDay, getWeekDay } from 'services/time';
 
+
 const initialState: PlannerState = {
   isFetching: false,
   data: {
@@ -10,7 +11,7 @@ const initialState: PlannerState = {
     week: getWeekNumber(),
     monday:   { date: mkWeekDay(1)},
     tuesday:  { date: mkWeekDay(2)},
-    wednsday: { date: mkWeekDay(3)},
+    wednesday: { date: mkWeekDay(3)},
     thursday: { date: mkWeekDay(4)},
     friday:   { date: mkWeekDay(5)},
     saturday: { date: mkWeekDay(6)},
@@ -30,11 +31,15 @@ const PlannerReducer = (
         backlog: state.backlog.concat([action.recipe])
       };
     case 'ASSIGN_TO_DAY':
+      const weekday = getWeekDay(action.day) as Weekday;
       return {
         ...state,
-        [getWeekDay(action.day)]: {
-          date: action.day,
-          [action.meal]: action.recipe
+        data: {
+          ...state.data,
+          [weekday]: {
+            ...state.data[weekday],
+            [action.meal]: action.recipe
+          }
         }
       };
     case 'REMOVE_FROM_BACKLOG':

+ 44 - 0
recipes/src/containers/Planner/styles.scss

@@ -0,0 +1,44 @@
+.cbk-planner {
+  &__body {
+
+    &__backlog {
+    }
+
+    &__calendar {
+      .container {
+        display: flex;
+        justify-content: space-between;
+
+        .day-schedule {
+          flex-basis: 14%;
+          max-width: 14%;
+          background-color:white;
+          text-align: center;
+          display: flex;
+          flex-direction: column;
+
+          &--name {
+            height: 3rem;
+            justify-content: center;
+            align-items: center;
+            line-height: 2rem;
+          }
+
+          &--lunch,
+          &--dinner {
+            height: 5rem;
+            justify-content: center;
+            align-items: center;
+            border: 1px solid gainsboro;
+          }
+
+          &-content {
+            width: inherit;
+            height: 100%;
+          }
+        }
+      }
+    }
+  }
+
+}

+ 12 - 0
recipes/src/containers/Recipes/List/index.tsx

@@ -8,6 +8,8 @@ import {
   addRecipeToCart,
   removeFromCart
 } from 'containers/ShoppingCart/actions';
+import plannerActions from 'containers/Planner/actions';
+
 import { connect } from "react-redux";
 import { Grid, Row, Cell } from "@material/react-layout-grid";
 import Button from "components/Button";
@@ -30,6 +32,7 @@ interface RecipeListProps extends RouteComponentProps {
   selectRecipe: (recipe?: DBRecipe) => undefined,
   removeFromCart: (recipe: DBRecipe) => undefined,
   addRecipeToCart: (recipe: DBRecipe) => undefined,
+  addRecipeToPlanner: (recipe: DBRecipe) => undefined
 };
 
 class RecipeList extends Component<RecipeListProps, {phoneDisplay: boolean, search: string}> {
@@ -90,6 +93,9 @@ class RecipeList extends Component<RecipeListProps, {phoneDisplay: boolean, sear
     this.props.addRecipeToCart(recipe)
   }
 
+  handleAddToPlanner = (recipe: DBRecipe) => (event: React.MouseEvent) => {
+    this.props.addRecipeToPlanner(recipe)
+  }
   handler = (event: React.MouseEvent) => {
     console.log('click', event);
   }
@@ -100,6 +106,9 @@ class RecipeList extends Component<RecipeListProps, {phoneDisplay: boolean, sear
   }, {
     label: 'shopping',
     handler: this.handleAddToShopping(recipe)
+  }, {
+    label: 'planner',
+    handler: this.handleAddToPlanner(recipe)
   }];
 
   icons = (id = '') => [{
@@ -187,6 +196,9 @@ const mapDispatchToProps = (dispatch: any) => {
     },
     removeFromCart: (recipe: DBRecipe) => {
       dispatch(removeFromCart(recipe))
+    },
+    addRecipeToPlanner: (recipe: DBRecipe) => {
+      dispatch(plannerActions.addToBacklog(recipe))
     }
   }
 }

+ 9 - 1
recipes/src/services/time.ts

@@ -1,4 +1,7 @@
 import moment, { Moment } from 'moment';
+import { Weekday } from 'types/planner';
+
+export const mkWeek = () => ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] as Weekday[];
 
 export const mkWeekDay = (day: string | number): Moment => moment().isoWeekday(day);
 
@@ -6,8 +9,13 @@ export const getWeekNumber = (): number => moment().isoWeek();
 
 export const getWeekDay = (day: Moment): string => day.format('dddd').toLowerCase();
 
+export const mkWeekData = (weekNumber: number): [Weekday, string][] => mkWeek().map(day => (
+  [ day, moment().week(weekNumber).isoWeekday(day).format() ]
+))
+
 export default {
   mkWeekDay,
   getWeekNumber,
-  getWeekDay
+  getWeekDay,
+  mkWeekData
 }

+ 1 - 1
recipes/src/types/planner.ts

@@ -4,7 +4,7 @@ import { DBRecipe } from "./recipes";
 export type Weekday =
   'monday' |
   'tuesday' |
-  'wednsday' |
+  'wednesday' |
   'thursday' |
   'friday' |
   'saturday' |