diff --git a/contexts/CalenderContext.tsx b/contexts/CalenderContext.tsx deleted file mode 100644 index da8f6a4..0000000 --- a/contexts/CalenderContext.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import React, { createContext, useState, ReactNode } from "react"; -import { - format, - startOfMonth, - endOfMonth, - getDate, - add, - sub, - set, - isAfter, - isBefore, - compareAsc -} from "date-fns"; - -const CalenderContext = createContext({} as CalenderContextState); - -const CalenderContextProvider = ({ - children -}: { - children: ReactNode; -}): JSX.Element => { - const weekDays: WeekDays = { - sunday: [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" - ], - monday: [ - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - "Sunday" - ] - }; - - /** - * Using date-fns, this function checks if currDate is within the month of selectedDate or not. - * @param {Date} selectedDate The current month. - * @param {Date} currDate The date to be compared to the selected month. - * @returns True if currDate is outside of the month of selectedDate, false if otherwise. - */ - const isOverflow = ( - selectedDate: Date, - currDate: Date - ): { - isOverflow: boolean; - overflowDirection: "prev" | "next" | null; - } => { - let flag = false; - let direction: "next" | "prev" | null = null; - - const start = startOfMonth(selectedDate); - const end = endOfMonth(selectedDate); - - if (isBefore(currDate, start)) { - flag = true; - direction = "prev"; - } - - if (isAfter(currDate, end)) { - flag = true; - direction = "next"; - } - - return { isOverflow: flag, overflowDirection: direction }; - }; - - /** - * A function that will return a month layout when given a date. It produces - * an object with 6 weeks that include overflow from the previous and next month - * with all dates aligned with the day of the week. - * @param selectedDate The date of the month to generate a month layout for. - */ - const populateMonth = (selectedDate: Date): MonthLayout => { - const endLastMonth = getDate(endOfMonth(sub(selectedDate, { months: 1 }))); - const startOfSelectedMonth = format(startOfMonth(selectedDate), "iii"); - - const ISOToIndex = { - sunday: { - Sun: 0, - Mon: 1, - Tue: 2, - Wed: 3, - Thu: 4, - Fri: 5, - Sat: 6 - }, - monday: { - Mon: -1, - Tue: 0, - Wed: 1, - Thu: 2, - Fri: 3, - Sat: 4, - Sun: 5 - } - }; - - const sundays = { - week1: new Array(7).fill(null), - week2: new Array(7).fill(null), - week3: new Array(7).fill(null), - week4: new Array(7).fill(null), - week5: new Array(7).fill(null), - week6: new Array(7).fill(null) - }; - - const sunStartDay = - endLastMonth - (ISOToIndex.sunday[startOfSelectedMonth] - 1); - - let sunCurrDate = set(sub(selectedDate, { months: 1 }), { - date: sunStartDay - }); - - for (const week in sundays) { - const thisWeek = sundays[week]; - - thisWeek.forEach((e, i) => { - const overflowInfo = isOverflow(selectedDate, sunCurrDate); - - const day: MonthDay = { - ...overflowInfo, - date: sunCurrDate - }; - - sunCurrDate = add(sunCurrDate, { - days: 1 - }); - - sundays[week][i] = day; - }); - } - - const mondays = { - week1: new Array(7).fill(null), - week2: new Array(7).fill(null), - week3: new Array(7).fill(null), - week4: new Array(7).fill(null), - week5: new Array(7).fill(null), - week6: new Array(7).fill(null) - }; - - const monStartDay = endLastMonth - ISOToIndex.monday[startOfSelectedMonth]; - - let monCurrDate = set(sub(selectedDate, { months: 1 }), { - date: monStartDay - }); - - for (const week in mondays) { - const thisWeek = mondays[week]; - - thisWeek.forEach((e, i) => { - const overflowInfo = isOverflow(selectedDate, monCurrDate); - - const day: MonthDay = { - ...overflowInfo, - date: monCurrDate - }; - - monCurrDate = add(monCurrDate, { - days: 1 - }); - - mondays[week][i] = day; - }); - } - - const output = { - sunday: { - weekdays: weekDays.sunday, - month: sundays - }, - monday: { - weekdays: weekDays.monday, - month: mondays - } - }; - - return output; - }; - - const [selectedDate, setSelectedDate] = useState(new Date()); - const [selectedDateInfo, setSelectedMonthInfo] = useState({ - date: selectedDate, - title: format(selectedDate, "LLLL uuuu"), - layout: populateMonth(selectedDate) - }); - - /** - * Updates the selectedDateInfo state when given a date. - * @param {Date} newDate The date to set the selectedDateInfo state to. - */ - const updateDateInfo = (newDate: Date) => { - const output = { ...selectedDateInfo }; - output.date = newDate; - output.title = format(newDate, "LLLL uuuu"); - output.layout = populateMonth(newDate); - - setSelectedMonthInfo(output); - }; - - // TODO: Add a function that validated if a date has at least one sticker in it. Use that within the nav function (when filter is enabled). - - // TODO: Add a function that will give the closest date, if available, when the nav func detects an empty month. - // Use the chart creation date to aid with this. (When filter is enabled) - - /** - * TODO: Add logic that prevents navigation to the future and too far in the past. (Use chart creation date) - * Update to use a promise and return appropriate errors. Display those errors on the front end. - * Update the use of this function on the front to handle the fails of the promise. - */ - - // TODO: (When filter is enabled) Update the calender update function that will take in a direction so that the the navigation buttons will take the user to the next month with stickers. Assuming there was a gap with empty months. - - /** - * Updated the selectedDate state when given the appropriate object. - * @param {UpdateCalendarProps} input An object with year, month, - * and day keys that the selectedDate state will be updated to. - */ - const updateDate = (input: UpdateCalendarProps) => { - const { year, month: inputMonth, day } = input; - - if (!year || !inputMonth || day < 0 || day > 31) { - return false; - } else { - const month = inputMonth - 1; - const customDate: Date = new Date(year, month, day); - - if (compareAsc(customDate, selectedDate) !== 0) { - setSelectedDate(customDate); - updateDateInfo(customDate); - } - } - }; - - // * Attempting to fix an issue with static generation where the date does not appear to be updating after initial generation. - const [currDate, setCurrDate] = useState(new Date()); - - const calenderContextValues: CalenderContextState = { - currDate, - setCurrDate, - selectedDate, - title: selectedDateInfo.title, - layout: selectedDateInfo.layout, - updateDate - }; - - return ( - - {children} - - ); -}; - -export { CalenderContextProvider, CalenderContext }; diff --git a/lib/populateMonth.ts b/lib/populateMonth.ts new file mode 100644 index 0000000..0834a0b --- /dev/null +++ b/lib/populateMonth.ts @@ -0,0 +1,181 @@ +import { + getDate, + endOfMonth, + sub, + format, + startOfMonth, + set, + add, + isAfter, + isBefore +} from "date-fns"; + +const weekDays: WeekDays = { + sunday: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + monday: [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday" + ] +}; + +/** + * Using date-fns, this function checks if currDate is within the month of selectedDate or not. + * @param {Date} selectedDate The current month. + * @param {Date} currDate The date to be compared to the selected month. + * @returns True if currDate is outside of the month of selectedDate, false if otherwise. + */ +const isOverflow = ( + selectedDate: Date, + currDate: Date +): { + isOverflow: boolean; + overflowDirection: "prev" | "next" | null; +} => { + let flag = false; + let direction: "next" | "prev" | null = null; + + const start = startOfMonth(selectedDate); + const end = endOfMonth(selectedDate); + + if (isBefore(currDate, start)) { + flag = true; + direction = "prev"; + } + + if (isAfter(currDate, end)) { + flag = true; + direction = "next"; + } + + return { isOverflow: flag, overflowDirection: direction }; +}; + +/** + * A function that will return a month layout when given a date. It produces + * an object with 6 weeks that include overflow from the previous and next month + * with all dates aligned with the day of the week. + * @param selectedDate The date of the month to generate a month layout for. + * @returns The month layout object for the provided month. + */ +const populateMonth = (selectedDate: Date): MonthLayout => { + const endLastMonth = getDate(endOfMonth(sub(selectedDate, { months: 1 }))); + const startOfSelectedMonth = format(startOfMonth(selectedDate), "iii"); + + const ISOToIndex = { + sunday: { + Sun: 0, + Mon: 1, + Tue: 2, + Wed: 3, + Thu: 4, + Fri: 5, + Sat: 6 + }, + monday: { + Mon: -1, + Tue: 0, + Wed: 1, + Thu: 2, + Fri: 3, + Sat: 4, + Sun: 5 + } + }; + + const sundays = { + week1: new Array(7).fill(null), + week2: new Array(7).fill(null), + week3: new Array(7).fill(null), + week4: new Array(7).fill(null), + week5: new Array(7).fill(null), + week6: new Array(7).fill(null) + }; + + const sunStartDay = + endLastMonth - (ISOToIndex.sunday[startOfSelectedMonth] - 1); + + let sunCurrDate = set(sub(selectedDate, { months: 1 }), { + date: sunStartDay + }); + + for (const week in sundays) { + const thisWeek = sundays[week]; + + thisWeek.forEach((e, i) => { + const overflowInfo = isOverflow(selectedDate, sunCurrDate); + + const day: MonthDay = { + ...overflowInfo, + date: sunCurrDate.toJSON() + }; + + sunCurrDate = add(sunCurrDate, { + days: 1 + }); + + sundays[week][i] = day; + }); + } + + const mondays = { + week1: new Array(7).fill(null), + week2: new Array(7).fill(null), + week3: new Array(7).fill(null), + week4: new Array(7).fill(null), + week5: new Array(7).fill(null), + week6: new Array(7).fill(null) + }; + + const monStartDay = endLastMonth - ISOToIndex.monday[startOfSelectedMonth]; + + let monCurrDate = set(sub(selectedDate, { months: 1 }), { + date: monStartDay + }); + + for (const week in mondays) { + const thisWeek = mondays[week]; + + thisWeek.forEach((e, i) => { + const overflowInfo = isOverflow(selectedDate, monCurrDate); + + const day: MonthDay = { + ...overflowInfo, + date: monCurrDate.toJSON() + }; + + monCurrDate = add(monCurrDate, { + days: 1 + }); + + mondays[week][i] = day; + }); + } + + const output = { + sunday: { + weekdays: weekDays.sunday, + month: sundays + }, + monday: { + weekdays: weekDays.monday, + month: mondays + } + }; + + return output; +}; + +export default populateMonth; diff --git a/src/app/hooks.ts b/src/app/hooks.ts index e2a4fed..72d1476 100644 --- a/src/app/hooks.ts +++ b/src/app/hooks.ts @@ -1,5 +1,5 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; import { RootState, AppDispatch } from "./store"; -export const useAppDiscpatch = () => useDispatch(); +export const useAppDispatch = () => useDispatch(); export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/components/calender/CalenderNav.tsx b/src/components/calender/CalenderNav.tsx index e6b730a..fa06d69 100644 --- a/src/components/calender/CalenderNav.tsx +++ b/src/components/calender/CalenderNav.tsx @@ -1,14 +1,19 @@ -import React, { useContext } from "react"; +import React from "react"; import { useRouter } from "next/router"; import { HStack, IconButton } from "@chakra-ui/react"; import { Icon } from "@iconify/react"; import { format, isSameMonth, addMonths, subMonths } from "date-fns"; import findValidDateRange from "../../../lib/findValidDateRange"; import DatePicker from "./DatePicker"; -import { CalenderContext } from "../../../contexts/CalenderContext"; +import { useAppSelector } from "../../app/hooks"; const CalenderNav = (): JSX.Element => { - const { selectedDate } = useContext(CalenderContext); + const selectedDate = useAppSelector( + (state) => state.calender.selectedDateInfo + ); + const { date } = selectedDate; + + const selectedDateObj = new Date(date); const validDateRange = findValidDateRange(); const { start: validStart, end: validEnd } = validDateRange; @@ -17,14 +22,14 @@ const CalenderNav = (): JSX.Element => { const handleNavButtons = (direction: "next" | "prev") => { if (direction === "next") { - const newMonth = addMonths(selectedDate, 1); + const newMonth = addMonths(selectedDateObj, 1); const year = format(newMonth, "y"); const month = format(newMonth, "L"); router.push(`/calendar/${year}/${month}`); } else if (direction === "prev") { - const newMonth = subMonths(selectedDate, 1); + const newMonth = subMonths(selectedDateObj, 1); const year = format(newMonth, "y"); const month = format(newMonth, "L"); @@ -36,14 +41,14 @@ const CalenderNav = (): JSX.Element => { return ( } onClick={() => handleNavButtons("prev")} /> } onClick={() => handleNavButtons("next")} diff --git a/src/components/calender/DatePicker.tsx b/src/components/calender/DatePicker.tsx index 8aabc1e..02ca4de 100644 --- a/src/components/calender/DatePicker.tsx +++ b/src/components/calender/DatePicker.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useRef, useState } from "react"; +import React, { useRef, useState } from "react"; import { useRouter } from "next/router"; import { Button, @@ -27,10 +27,12 @@ import { import { format } from "date-fns"; import findValidDateRange from "../../../lib/findValidDateRange"; import FormValidateEmoji from "./FormValidateEmoji"; -import { CalenderContext } from "../../../contexts/CalenderContext"; +import { useAppSelector } from "../../app/hooks"; const DatePicker = (): JSX.Element => { - const { title } = useContext(CalenderContext); + const selectedDate = useAppSelector( + (state) => state.calender.selectedDateInfo + ); const router = useRouter(); @@ -129,7 +131,7 @@ const DatePicker = (): JSX.Element => { diff --git a/src/components/calender/Day.tsx b/src/components/calender/Day.tsx index 53efd0a..5953a0e 100644 --- a/src/components/calender/Day.tsx +++ b/src/components/calender/Day.tsx @@ -19,7 +19,7 @@ interface DayProps { overflowDirection?: "next" | "prev" | null; sticker: StickerVal; date: Date; - selectedDate: Date; + selectedDate: string; currDate: Date; isToday: boolean; } @@ -42,17 +42,19 @@ const Day = ({ currDate, isToday }: DayProps): JSX.Element => { + const selectedDateObj = new Date(selectedDate); + const handleNav = (direction: "next" | "prev") => { if (direction === "next") { console.log(overflowDirection); - const newMonth = add(selectedDate, { months: 1 }); + const newMonth = add(selectedDateObj, { months: 1 }); const year = getYear(newMonth); const month = getMonth(newMonth) + 1; router.push(`/calendar/${year}/${month}`); } else if (direction === "prev") { - const newMonth = sub(selectedDate, { months: 1 }); + const newMonth = sub(selectedDateObj, { months: 1 }); const year = getYear(newMonth); const month = getMonth(newMonth) + 1; diff --git a/src/components/calender/index.tsx b/src/components/calender/index.tsx index 6008304..e09d6dd 100644 --- a/src/components/calender/index.tsx +++ b/src/components/calender/index.tsx @@ -1,41 +1,50 @@ import React, { useContext, useEffect } from "react"; import { Box, HStack, SimpleGrid, Text, VStack } from "@chakra-ui/react"; import { isSameDay, format } from "date-fns"; -import { useAppDiscpatch, useAppSelector } from "../../app/hooks"; -import { updateCurrDate, updateMonth } from '../../features/calender/calender'; -import { CalenderContext } from "../../../contexts/CalenderContext"; +import { useAppDispatch, useAppSelector } from "../../app/hooks"; +import { updateCurrDate, updateMonth } from "../../features/calender/calender"; import { StickersContext } from "../../../contexts/StickerContext"; import CalenderNav from "./CalenderNav"; import Day from "./Day"; const Calender = (newDate?: UpdateCalendarProps): JSX.Element => { - const currDate: Date = useAppSelector(state => state.calender.currDate); - const seletedMonth = useAppSelector(state => state.calender.selectedDateInfo); + const currDate: string = useAppSelector((state) => state.calender.currDate); + const selectedDate = useAppSelector( + (state) => state.calender.selectedDateInfo + ); + const { layout } = selectedDate; + const dispatch = useAppDispatch(); - const { selectedDate, layout, updateDate,/* currDate, */setCurrDate } = - useContext(CalenderContext); const { stickersMonth } = useContext(StickersContext); + const currDateObj = new Date(currDate); + useEffect(() => { if (newDate && newDate.year && newDate.month && newDate.day) { const { year, month, day } = newDate; if (year > 0 && month > 0 && day > 0) { - updateDate(newDate); + const generatedDate: Date = new Date(year, month - 1, day); + const dateString: string = generatedDate.toJSON(); + + dispatch(updateMonth(dateString)); } else { console.warn("Invalid date format: ", newDate); } } - }, [newDate, updateDate]); + }, [dispatch, newDate]); useEffect(() => { console.info("Check to update date."); - if (!isSameDay(currDate, new Date())) { + + const currDateObj = new Date(currDate); + + if (!isSameDay(currDateObj, new Date())) { console.info("Updated date."); - setCurrDate(new Date()); + dispatch(updateCurrDate()); } - }, [currDate, setCurrDate]); + }, [currDate, dispatch]); // Simulated user settings context const userSettings = { @@ -87,12 +96,14 @@ const Calender = (newDate?: UpdateCalendarProps): JSX.Element => { return thisWeek.map((day: MonthDay) => { const { date, isOverflow, overflowDirection } = day; + const toDateObj: Date = new Date(date); + let sticker = null; let id = ""; stickersMonth.map((stickerDay) => { - if (isSameDay(stickerDay.date, date)) { + if (isSameDay(stickerDay.date, toDateObj)) { sticker = stickerDay.sticker; id = stickerDay.id; @@ -104,15 +115,15 @@ const Calender = (newDate?: UpdateCalendarProps): JSX.Element => { isOverflow={isOverflow} overflowDirection={overflowDirection} sticker={sticker} - date={date} - selectedDate={selectedDate} - currDate={currDate} - isToday={isSameDay(currDate, date)} + date={toDateObj} + selectedDate={selectedDate.date} + currDate={currDateObj} + isToday={isSameDay(currDateObj, toDateObj)} key={ id.length ? id - : format(date, "yyyyddLL") + - `/${sticker === null ? 0 : sticker}` + : format(toDateObj, "yyyyddLL") + + `/${sticker === null ? 0 : sticker}` } /> ); diff --git a/src/features/calender/calender.ts b/src/features/calender/calender.ts index d8acd6b..edb0ee8 100644 --- a/src/features/calender/calender.ts +++ b/src/features/calender/calender.ts @@ -1,43 +1,59 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { format } from "date-fns"; +import populate from "../../../lib/populateMonth"; interface CalenderSlice { - currDate: Date; + currDate: string; selectedDateInfo: { - selectedDate: Date; + date: string; title: string; layout: MonthLayout; }; } const getCurrDate = (): Date => new Date(); +const dateParse = (date: Date) => date.toJSON(); const dateFormatter = (date: Date): string => format(date, "LLLL uuuu"); const initialState: CalenderSlice = { - currDate: getCurrDate(), + currDate: dateParse(getCurrDate()), selectedDateInfo: { - selectedDate: getCurrDate(), - title: dateFormatter(new Date()), - layout: {} as MonthLayout + date: dateParse(getCurrDate()), + title: dateFormatter(getCurrDate()), + layout: populate(getCurrDate()) } }; +// TODO: Add a function that validated if a month has at least one sticker in it. Use that within the nav function (when filter is enabled). + +// TODO: Add a function that will give the closest date, if available, when the nav func detects an empty month. +// Use the chart creation date to aid with this. (When filter is enabled) + +/** + * TODO: Add logic that prevents navigation to the future and too far in the past. (Use chart creation date) + * Update to use a promise and return appropriate errors. Display those errors on the front end. + * Update the use of this function on the front to handle the fails of the promise. + */ + +// TODO: (When filter is enabled) Update the calender update function that will take in a direction so that the the navigation buttons will take the user to the next month with stickers. Assuming there was a gap with empty months. + const calenderSlice = createSlice({ name: "Calender", initialState, reducers: { - // Populate month // Update month info - updateMonth(state: CalenderSlice, action: PayloadAction) { - const { payload: newDate } = action; + updateMonth(state: CalenderSlice, action: PayloadAction) { + const { payload } = action; - state.selectedDateInfo.selectedDate = newDate; - state.selectedDateInfo.title = dateFormatter(newDate); - // ! Add the layout formatter function + const toDateObj: Date = new Date(payload); + + state.selectedDateInfo.date = payload; + state.selectedDateInfo.title = dateFormatter(toDateObj); + state.selectedDateInfo.layout = populate(toDateObj); }, // Update current date updateCurrDate(state: CalenderSlice) { - state.currDate = new Date(); + state.currDate = dateParse(new Date()); } } }); diff --git a/src/pages/calendar/[...date].tsx b/src/pages/calendar/[...date].tsx index a86176e..4009677 100644 --- a/src/pages/calendar/[...date].tsx +++ b/src/pages/calendar/[...date].tsx @@ -13,7 +13,8 @@ import { // import findValidDateRange from "../../lib/findValidDateRange"; import ErrorPage from "next/error"; import Calender from "../../components/calender"; -import { CalenderContextProvider } from "../../../contexts/CalenderContext"; +import { Provider } from "react-redux"; +import { store } from "../../app/store"; import { StickersContextProvider } from "../../../contexts/StickerContext"; const DateRoute: React.FC = () => { @@ -59,8 +60,6 @@ const DateRoute: React.FC = () => { } else if (!dateArr[2]) { date.day = 1; } - } else { - return date; } return date; @@ -199,11 +198,11 @@ const DateRoute: React.FC = () => { ) : ( - - + + - - + + ); }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2b12185..3f13881 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,7 +4,6 @@ import { format } from "date-fns"; import { Provider } from "react-redux"; import { store } from "../app/store"; import { StickersContextProvider } from "../../contexts/StickerContext"; -import { CalenderContextProvider } from "../../contexts/CalenderContext"; import Calender from "../components/calender"; const IndexPage = (): JSX.Element => { @@ -17,11 +16,9 @@ const IndexPage = (): JSX.Element => { return ( - - - - - + + + ); diff --git a/types/CalenderContext.d.ts b/types/CalenderContext.d.ts index 946a3fd..7655514 100644 --- a/types/CalenderContext.d.ts +++ b/types/CalenderContext.d.ts @@ -15,7 +15,7 @@ interface WeekDays { } interface MonthDay { - date: Date; + date: string; isOverflow: boolean; overflowDirection: "prev" | "next" | null; }