diff --git a/package-lock.json b/package-lock.json index 575f70b..6041745 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10601,6 +10601,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index 2f29711..c08a173 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@types/node": "^12.20.15", "@types/react": "^17.0.13", "@types/react-dom": "^17.0.8", + "moment": "^2.29.1", "node-sass": "^6.0.1", "query-string": "^7.0.1", "react": "^17.0.2", diff --git a/src/App.tsx b/src/App.tsx index 6508533..8d2280b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import { ReactComponent as TimeIcon } from './assets/icons/time.svg'; import { IProfile } from "./structure/profile"; import { ALL_WORDS } from "./data/all-words"; import { WordDetails } from "./components/word-details/WordDetails"; +import { Calendar } from "./components/calendar/Calendar"; export var userProfileData : IProfile = { @@ -78,6 +79,7 @@ function App() { + diff --git a/src/components/calendar/Calendar.module.scss b/src/components/calendar/Calendar.module.scss new file mode 100644 index 0000000..3ea386c --- /dev/null +++ b/src/components/calendar/Calendar.module.scss @@ -0,0 +1,207 @@ +.navHeader { + background-color: transparent; + text-align: center; + position: relative; + display: flex; + justify-content: flex-start; + align-items: center; + padding: 1rem 1.5rem; + + button { + width: 4rem; + text-align: center; + background-color: transparent; + border: 0; + display: flex; + align-items: center; + justify-content: flex-start; + + svg { + width: 1rem; + color: var(--black); + } + } +} + + +.todayHeader { + padding: 0 2rem; + margin-top: 2rem; + + h4 { + font-size: 2rem; + color: var(--black); + } + + p { + font-size: 1.2rem; + color: var(--light-grey); + letter-spacing: 0.5px; + font-weight: 300; + } + + h5 { + margin-top: 3rem; + font-size: 1.6rem; + font-weight: 400; + color: var(--black); + padding-bottom: 0.5rem; + } +} + +.currentWeek { + padding: 0.5rem; + list-style: none; + display: grid; + grid-template-columns: repeat(7, 1fr); + background-color: var(--creamy-white); + z-index: 1; + position: sticky; + top: 0; + box-shadow: 0px 5px 5px -8px var(--light-grey); + + li { + text-align: center; + padding: 0.5rem 0; + + &:last-child { + label, span { + color: var(--red); + } + } + + &.active { + background-color: var(--teal); + border-radius: 1rem; + + label, span { + color: white; + } + } + } + + label { + display: block; + font-size: 1.2rem; + color: var(--light-grey); + } + + span { + display: block; + font-size: 1.2rem; + font-weight: 600; + color: var(--black); + } +} + +.currentDayDetails { + padding: 1rem; + list-style: none; + + li { + min-height: 4rem; + position: relative; + + &:nth-child(4n - 2) .task::before { + background-color: var(--orange); + } + + &:nth-child(4n - 1) .task::before { + background-color: var(--blue); + } + + &:nth-child(4n - 3) .task::before { + background-color: var(--red); + } + + &:nth-child(4n - 4) .task::before { + background-color: var(--teal); + } + + &.cycle { + label { + display: flex; + align-items: center; + justify-content: flex-start; + + &::after { + content: ''; + flex-grow: 1; + height: 2px; + border-bottom: 2px dashed var(--light-grey); + margin-left: 2rem; + transform: scaleY(0.4); + } + } + } + + label { + font-size: 1.4rem; + color: var(--light-grey); + text-align: left; + opacity: 0.8; + } + } + + .task { + width: calc(100% - 6rem); + display: block; + margin-left: auto; + padding: 1rem 2rem; + border-radius: 2.5rem; + position: relative; + overflow: visible; + line-height: 1.7; + + &.completed { + opacity: 0.5; + } + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--orange); + opacity: 0.5; + border-radius: inherit; + } + + &>* { + position: relative; + } + + h5 { + font-size: 1.6rem; + color: var(--black); + } + + p { + font-size: 1.1rem; + color: var(--grey); + } + + .indicator { + position: absolute; + left: -2rem; + top: calc(50% - 0.8rem); + width: 1.6rem; + height: 1.6rem; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: white; + + &.overTime { + background-color: var(--red); + } + + &.completed { + background-color: var(--teal); + } + } + } +} \ No newline at end of file diff --git a/src/components/calendar/Calendar.tsx b/src/components/calendar/Calendar.tsx new file mode 100644 index 0000000..4e7a623 --- /dev/null +++ b/src/components/calendar/Calendar.tsx @@ -0,0 +1,173 @@ +import React, { useState } from 'react'; +import styles from './Calendar.module.scss'; +import { ReactComponent as ChevronLeft } from '../../assets/icons/chevron-left.svg'; +import moment from 'moment'; +import { ISchedule } from '../../structure/schedule'; +import { NavLink } from 'react-router-dom'; + + +const SCHEDULE: Array = [{ + calendar_date: { + date: moment().format('DD'), + month: moment().format('MM'), + year: moment().format('YYYY'), + time: { + hours: 8, + minutes: 0 + } + }, + title: 'Vocabulary: All Words', + description: 'Revision of "All Words" in Vocabulary shelf', + revision_link: '/revise', + external_link: '', + duration_in_min: 10, + isCompleted: true +}, { + calendar_date: { + date: moment().format('DD'), + month: moment().format('MM'), + year: moment().format('YYYY'), + time: { + hours: 10, + minutes: 0 + } + }, + title: 'Revision Test: All Shelves', + description: '', + revision_link: '/revise', + external_link: '', + duration_in_min: 30, + isCompleted: false +}, { + calendar_date: { + date: moment().format('DD'), + month: moment().format('MM'), + year: moment().format('YYYY'), + time: { + hours: 17, + minutes: 30 + } + }, + title: 'Books: Sapiens', + description: 'Revision of "Sapiens" in Books shelf', + revision_link: '/revise', + external_link: '', + duration_in_min: 30, + isCompleted: false +}, ]; + +function getCurrentWeek() { + let currentDate = moment(); + + let weekStart = currentDate.clone().startOf('isoWeek'); + + let days = []; + + for (let i = 0; i <= 6; i++) { + days.push({ + date: moment(weekStart).add(i, 'days').format("DD"), + day: moment(weekStart).add(i, 'days').format("ddd"), + month_name: moment(weekStart).add(i, 'days').format("MMMM"), + month: moment(weekStart).add(i, 'days').format("MM"), + year: moment().format('YYYY') + }); + } + + return days; +} + +export const Calendar: React.FC = () => { + const today = { + date: moment().format('DD'), + day: moment().format('ddd'), + month_name: moment().format('MMMM'), + month: moment().format('MM'), + year: moment().format('YYYY') + }; + + const currentTime = { + hours: Number(moment().format('HH')), + minutes: Number(moment().format('mm')) + } + + let current_week = getCurrentWeek(); + + const [selectedDate, setSelectedDate] = useState<{ + date: string, + month: string, + year: string + }>({ + date: today.date, + month: today.month, + year: today.year + }); + + let hours = []; + + for (let i = 0; i < 24; i += 1) { + hours.push(i); + } + + return
+
+ +
+ +
+

Today

+

Productive day, Neymar

+ +
{ today.month_name }, { today.year }
+
+ +
    + { current_week.map((week, index) => { + return
  • setSelectedDate({ + date: week.date, + month: week.month, + year: week.year + })}> + + { week.date } +
  • + }) } +
+ +
    + { hours.map((hours, index) => { + return
  • + + + { SCHEDULE.map((schedule, schedule_index) => { + if (schedule.calendar_date.date === selectedDate.date && + schedule.calendar_date.month === selectedDate.month && + schedule.calendar_date.year === selectedDate.year && + schedule.calendar_date.time.hours === hours) { + return schedule.isCompleted = true}> + + { currentTime.hours >= schedule.calendar_date.time.hours && + currentTime.minutes >= schedule.calendar_date.time.minutes && + !schedule.isCompleted &&
    + ! +
    } + + { schedule.isCompleted &&
    + ✓ +
    } + +
    { schedule.title }
    +

    { schedule.description }

    +

    { schedule.duration_in_min } minutes practice

    +
    + } + }) } +
  • + }) } +
+
+} \ No newline at end of file diff --git a/src/components/home/Home.tsx b/src/components/home/Home.tsx index 25dd812..3a7c343 100644 --- a/src/components/home/Home.tsx +++ b/src/components/home/Home.tsx @@ -68,9 +68,9 @@ export const Home: React.FC = () => { Revisions - +
  • @@ -134,9 +134,9 @@ export const Home: React.FC = () => { { shelf.words.length > 0 ? (shelf.revisedWords.length * 100/ shelf.words.length) : 0 }% -
    { category.name }
    -

    { shelf.name }

    +
    { shelf.name }

    { shelf.words.length } words

    +

    { category.name }

  • }) }) } diff --git a/src/components/revise/Revise.tsx b/src/components/revise/Revise.tsx index a377426..ff9a23b 100644 --- a/src/components/revise/Revise.tsx +++ b/src/components/revise/Revise.tsx @@ -21,11 +21,9 @@ export const Revise: React.FC = () => { { progressState === 'END' &&
    - - - +
    } } \ No newline at end of file diff --git a/src/structure/schedule.ts b/src/structure/schedule.ts new file mode 100644 index 0000000..56c0677 --- /dev/null +++ b/src/structure/schedule.ts @@ -0,0 +1,17 @@ +export type ISchedule = { + calendar_date: { + date: string, + month: string, + year: string, + time: { + hours: number, + minutes: number, + } + }, + title: string, + description: string, + revision_link: string, + external_link: string, + duration_in_min: number, + isCompleted: boolean, +}; \ No newline at end of file