@@ -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", | |||
@@ -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", | |||
@@ -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() { | |||
<Route path="/shelf-details/" component={ShelfDetails} /> | |||
<Route path="/word-details/" component={WordDetails} /> | |||
<Route path="/revise" component={Revise} /> | |||
<Route path="/calendar" component={Calendar} /> | |||
<Redirect from="/" to="/home" /> | |||
</Switch> | |||
<Tabs /> | |||
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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<ISchedule> = [{ | |||
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 <section className="modalPage"> | |||
<header className={styles.navHeader}> | |||
<button onClick={() => window.history.back()}> | |||
<ChevronLeft /> | |||
</button> | |||
</header> | |||
<header className={styles.todayHeader}> | |||
<h4> Today </h4> | |||
<p> Productive day, Neymar </p> | |||
<h5> { today.month_name }, { today.year } </h5> | |||
</header> | |||
<ul className={styles.currentWeek}> | |||
{ current_week.map((week, index) => { | |||
return <li key={index} className={week.date === selectedDate.date && week.month === selectedDate.month ? styles.active : ''} | |||
onClick={() => setSelectedDate({ | |||
date: week.date, | |||
month: week.month, | |||
year: week.year | |||
})}> | |||
<label> { week.day } </label> | |||
<span> { week.date } </span> | |||
</li> | |||
}) } | |||
</ul> | |||
<ul className={styles.currentDayDetails}> | |||
{ hours.map((hours, index) => { | |||
return <li key={index} className={(hours % 4 === 0) ? styles.cycle : ''}> | |||
<label> { hours.toString().padStart(2, '0') }:00 </label> | |||
{ 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 <NavLink to={schedule.revision_link} key={schedule_index} | |||
className={styles.task + ' ' + (schedule.isCompleted ? styles.completed: '')} | |||
onClick={() => schedule.isCompleted = true}> | |||
{ currentTime.hours >= schedule.calendar_date.time.hours && | |||
currentTime.minutes >= schedule.calendar_date.time.minutes && | |||
!schedule.isCompleted && <div className={styles.indicator + ' ' + styles.overTime}> | |||
! | |||
</div> } | |||
{ schedule.isCompleted && <div className={styles.indicator + ' ' + styles.completed}> | |||
✓ | |||
</div> } | |||
<h5> { schedule.title } </h5> | |||
<p> { schedule.description } </p> | |||
<p> { schedule.duration_in_min } minutes practice </p> | |||
</NavLink> | |||
} | |||
}) } | |||
</li> | |||
}) } | |||
</ul> | |||
</section> | |||
} |
@@ -68,9 +68,9 @@ export const Home: React.FC = () => { | |||
<BookIcon /> | |||
Revisions | |||
</h4> | |||
<button> | |||
<NavLink to="/calendar"> | |||
<CalendarIcon /> | |||
</button> | |||
</NavLink> | |||
</header> | |||
<ul> | |||
<li> | |||
@@ -134,9 +134,9 @@ export const Home: React.FC = () => { | |||
<span className={styles.text}> { shelf.words.length > 0 ? (shelf.revisedWords.length * 100/ shelf.words.length) : 0 }% </span> | |||
</div> | |||
<h5> { category.name } </h5> | |||
<p> { shelf.name } </p> | |||
<h5> { shelf.name } </h5> | |||
<p> { shelf.words.length } words </p> | |||
<p> { category.name } </p> | |||
</li> | |||
}) | |||
}) } | |||
@@ -21,11 +21,9 @@ export const Revise: React.FC = () => { | |||
{ progressState === 'END' && <div> | |||
<Summary /> | |||
<NavLink to={'/home'}> | |||
<button className={styles.finishButton}> | |||
Done | |||
</button> | |||
</NavLink> | |||
<button className={styles.finishButton} onClick={() => window.history.back()}> | |||
Done | |||
</button> | |||
</div> } | |||
</section> | |||
} |
@@ -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, | |||
}; |