diff --git a/.eslintrc.js b/.eslintrc.js index 45af087..072442c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,28 +1,28 @@ -module.exports = { - parser: '@typescript-eslint/parser', - extends: [ - 'plugin:react/recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier/@typescript-eslint', - 'plugin:prettier/recommended', - 'plugin:react-hooks/recommended', - ], - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { - jsx: true, - }, - }, - rules: { - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_|^req|^next' }], - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/explicit-function-return-type': 0, - 'react/prop-types': 0, - }, - settings: { - react: { - version: 'detect', - }, - }, -}; +// module.exports = { +// parser: '@typescript-eslint/parser', +// extends: [ +// 'plugin:react/recommended', +// 'plugin:@typescript-eslint/recommended', +// 'prettier/@typescript-eslint', +// 'plugin:prettier/recommended', +// 'plugin:react-hooks/recommended', +// ], +// parserOptions: { +// ecmaVersion: 2020, +// sourceType: 'module', +// ecmaFeatures: { +// jsx: true, +// }, +// }, +// rules: { +// '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_|^req|^next' }], +// '@typescript-eslint/no-explicit-any': 0, +// '@typescript-eslint/explicit-function-return-type': 0, +// 'react/prop-types': 0, +// }, +// settings: { +// react: { +// version: 'detect', +// }, +// }, +// }; diff --git a/package-lock.json b/package-lock.json index 8205261..7433740 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2012,71 +2012,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, - "@typescript-eslint/experimental-utils": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.9.1.tgz", - "integrity": "sha512-lkiZ8iBBaYoyEKhCkkw4SAeatXyBq9Ece5bZXdLe1LWBUwTszGbmbiqmQbwWA8cSYDnjWXp9eDbXpf9Sn0hLAg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.9.1", - "@typescript-eslint/typescript-estree": "3.9.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.9.1.tgz", - "integrity": "sha512-y5QvPFUn4Vl4qM40lI+pNWhTcOWtpZAJ8pOEQ21fTTW4xTJkRplMjMRje7LYTXqVKKX9GJhcyweMz2+W1J5bMg==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.9.1", - "@typescript-eslint/types": "3.9.1", - "@typescript-eslint/typescript-estree": "3.9.1", - "eslint-visitor-keys": "^1.1.0" - } - }, - "@typescript-eslint/types": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.9.1.tgz", - "integrity": "sha512-15JcTlNQE1BsYy5NBhctnEhEoctjXOjOK+Q+rk8ugC+WXU9rAcS2BYhoh6X4rOaXJEpIYDl+p7ix+A5U0BqPTw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.9.1.tgz", - "integrity": "sha512-IqM0gfGxOmIKPhiHW/iyAEXwSVqMmR2wJ9uXHNdFpqVvPaQ3dWg302vW127sBpAiqM9SfHhyS40NKLsoMpN2KA==", - "dev": true, - "requires": { - "@typescript-eslint/types": "3.9.1", - "@typescript-eslint/visitor-keys": "3.9.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - }, - "dependencies": { - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - } - } - }, - "@typescript-eslint/visitor-keys": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.9.1.tgz", - "integrity": "sha512-zxdtUjeoSh+prCpogswMwVUJfEFmCOjdzK9rpNjNBfm6EyPt99x3RrJoBOGZO23FCt0WPKUCOL5mb/9D5LjdwQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -8794,6 +8729,15 @@ "sort-keys": "^1.0.0" } }, + "notistack": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-1.0.1.tgz", + "integrity": "sha512-2T1WkokzRCM8N9EdueaXja160IMFIMHVhRu0fGkDje7qCzwBHlTMZY2NULQzB2GFOO6iGVzl5GCX2XrJIzI8bw==", + "requires": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -10636,6 +10580,11 @@ "whatwg-fetch": "^3.0.0" } }, + "react-click-away-listener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/react-click-away-listener/-/react-click-away-listener-1.4.3.tgz", + "integrity": "sha512-c7d6mfZuHu/rIdnEHnovX/QsScQXlqtdAynSnZUyyH+6kPOAyB40k2c5br56c/qp4KBkHD0JQV4C7rVuAmroMw==" + }, "react-dev-utils": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", diff --git a/package.json b/package.json index f0c6554..a5ee4b5 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", "axios": "^0.19.2", + "notistack": "^1.0.1", "react": "^16.13.1", + "react-click-away-listener": "^1.4.3", "react-dom": "^16.13.1", "react-scripts": "3.4.1", "styled-components": "^5.1.1" @@ -21,7 +23,6 @@ "@types/react": "^16.9.46", "@types/react-dom": "^16.9.8", "@types/styled-components": "^5.1.2", - "@typescript-eslint/parser": "^3.9.1", "prettier": "^2.0.5", "typescript": "^3.9.7" }, diff --git a/public/index.html b/public/index.html index 2a7a20a..cbfdddb 100644 --- a/public/index.html +++ b/public/index.html @@ -1,22 +1,24 @@ - - - - - - - - + + + + + + + + + + + + PlanNaPlan + + + + +
+ - PlanNaPlan - - - -
- \ No newline at end of file diff --git a/src/assets/PL.png b/src/assets/PL.png deleted file mode 100644 index 656d382..0000000 Binary files a/src/assets/PL.png and /dev/null differ diff --git a/src/assets/UK.png b/src/assets/UK.png deleted file mode 100644 index 0f5e14b..0000000 Binary files a/src/assets/UK.png and /dev/null differ diff --git a/src/assets/account.svg b/src/assets/account.svg new file mode 100644 index 0000000..87ffb3b --- /dev/null +++ b/src/assets/account.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/bin.svg b/src/assets/bin.svg new file mode 100644 index 0000000..cbfe7d7 --- /dev/null +++ b/src/assets/bin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/expand.png b/src/assets/expand.png deleted file mode 100644 index 239474f..0000000 Binary files a/src/assets/expand.png and /dev/null differ diff --git a/src/assets/expand.svg b/src/assets/expand.svg new file mode 100644 index 0000000..d89d637 --- /dev/null +++ b/src/assets/expand.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/poland.svg b/src/assets/poland.svg new file mode 100644 index 0000000..d5a386e --- /dev/null +++ b/src/assets/poland.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/united-kingdom.svg b/src/assets/united-kingdom.svg new file mode 100644 index 0000000..60da99a --- /dev/null +++ b/src/assets/united-kingdom.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/user.png b/src/assets/user.png deleted file mode 100644 index 4b429a0..0000000 Binary files a/src/assets/user.png and /dev/null differ diff --git a/src/components/App.tsx b/src/components/App.tsx index 5390571..ff473d8 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -8,6 +8,8 @@ import styled from 'styled-components'; const Wrapper = styled.div` display: flex; height: calc(100vh - 80px); + background-color: #ECEEF4; + padding: 20px; `; export const App = () => { diff --git a/src/components/CourseCard.tsx b/src/components/CourseCard.tsx index ad7d482..8fae122 100644 --- a/src/components/CourseCard.tsx +++ b/src/components/CourseCard.tsx @@ -1,72 +1,87 @@ import React, { useState, useContext, MouseEvent } from 'react'; import Collapse from '@material-ui/core/Collapse'; -import ExpandIcon from '../assets/expand.png'; -import { Course, Group, GroupType } from '../types/index'; +import { ReactComponent as Expand } from '../assets/expand.svg'; +import { Course, Group } from '../types/index'; import { coursesContext } from '../contexts/CoursesProvider'; import styled from 'styled-components'; import { makeStyles } from '@material-ui/core/styles'; -import { ReactComponent as CloseIcon } from '../assets/close.svg'; +import { ReactComponent as Bin } from '../assets/bin.svg'; -interface ClassExandIconProps { - isSelected: boolean; -} - -const CourseStyled = styled.div` +const CourseCardWrapper = styled.div` + position: relative; display: flex; min-height: 40px; - background-color: rgb(100, 181, 246) !important; + background-color: rgb(100, 181, 246); align-items: center; justify-content: center; flex-direction: column; margin-top: 10px; - padding-top: 10px; - padding-bottom: 10px; - border-radius: 10px; + border-radius: 10px; cursor: pointer; align-items: stretch; - position: relative; - box-shadow: 9px 9px 8px -2px rgba(0,0,0,0.59); + box-shadow: 9px 9px 8px -2px rgba(0, 0, 0, 0.59); `; -const CourseNameStyled = styled.div` -padding-top:20px; -padding-bottom:10px; -padding-left:35px; -padding-right:35px; + +const TitleWrapper = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; +` + +const BinIcon = styled(Bin)` + width: 20px; + height: 20px; + max-width: 20px; + min-width: 20px; + cursor: pointer; + &:hover { + fill: white; + } `; -interface ClassGroupProps { - groupType: GroupType; -} +const CourseName = styled.div` + padding-left: 3px; + padding-right: 3px; + font-size: 16px; + user-select: none; +`; const ClassGroupStyled = styled.div` -position:relative; + position: relative; padding-top: 1px; padding-bottom: 1px; :hover { cursor: pointer; - background-color:#9ED3FF; + background-color: #9ed3ff; } `; -const ClassExandIconStyled = styled.img` - margin-top: 5px; +interface ExpandIconProps { + isSelected: boolean; +} + +const ExpandIcon = styled(Expand) ` width: 20px; + height: 20px; + max-width: 20px; + min-width: 20px; transition: 0.2s; - transform: ${(props) => (props.isSelected ? 'scaleY(-1);' : 'scaleY(1);')}; + transform: ${({ isSelected }) => (isSelected ? 'scaleY(-1);' : 'scaleY(1);')}; `; const TypeClass = styled.div` - font-size:12px; - position:absolute; - border-radius:15px; - background-color:#00506B; - border:2px solid; - min-width:45px; - top:5px; - left:5px; - color:white; - font-weight:bold; + font-size: 12px; + position: absolute; + border-radius: 15px; + background-color: #00506b; + border: 2px solid; + min-width: 45px; + top: 5px; + left: 5px; + color: white; + font-weight: bold; `; const useStyles = makeStyles({ @@ -87,16 +102,7 @@ const useStyles = makeStyles({ }, }); -const DeleteFromBasketIcon = styled(CloseIcon)` - width: 20px; - cursor: pointer; - position: absolute; - left: 230px; - top: -5px; - &:hover { - fill: white; - } -`; + interface CourseCardProps { course: Course; @@ -111,24 +117,24 @@ export const CourseCard = ({ course }: CourseCardProps) => { const onGroupClick = (group: Group, id: number) => addGroup(group, id); return ( - - deleteFromBasket(course.id)}> - setSelected(!isSelected)}>{course.name} + + + deleteFromBasket(course.id)}> + setSelected(!isSelected)}>{course.name} + setSelected(!isSelected)} isSelected={isSelected} /> + {groups .sort((a, b) => b.type.localeCompare(a.type)) .map((group, index) => ( onGroupClick(group, course.id)}> - {group.type==="CLASS"? "Ćw." : "Wyk."} + {group.type === 'CLASS' ? 'Ćw.' : 'Wyk.'}

{group.time} {group.room}

{group.lecturer}

))}
-
setSelected(!isSelected)}> - -
-
+ ); }; diff --git a/src/components/Dropdown.tsx b/src/components/Dropdown.tsx index b270f2d..206ce34 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -1,20 +1,19 @@ -import React, { useState, useContext, useEffect, MouseEvent, ChangeEvent } from 'react'; -import axios from 'axios'; -import { Input } from '@material-ui/core'; -import ClickAwayListener from '@material-ui/core/ClickAwayListener'; +import React, { useState, useContext, useEffect, MouseEvent, forwardRef } from 'react'; import { coursesContext } from '../contexts/CoursesProvider'; -import { Course, Basket } from '../types'; +import { Course } from '../types'; import styled from 'styled-components'; -import { makeStyles } from '@material-ui/core/styles'; -const DropdownStyled = styled.div` + + +const DropdownContainer = styled.div` + position: relative; + z-index: 99999999; max-height: 420px; + border-radius: 3px; overflow-y: auto; + box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2); scroll-snap-type: y mandatory; scroll-behavior: smooth; - z-index: 100; - position: relative; - border-radius:0px 0px 0px 15px; ::-webkit-scrollbar-track { border-radius: 10px; background-color: #f5f5f5; @@ -25,70 +24,69 @@ const DropdownStyled = styled.div` } ::-webkit-scrollbar-thumb { border-radius: 10px; - background-color: #d4b851; + background-color: black; border: 1px solid; } `; -const CourseStyled = styled.div` - position: relative; - z-index: 10; +const CourseContainer = styled.div` padding: 5px; padding-left: 20px; - background-color: #e6c759; + background-color: #f2f4f7; font-size: 18px; - font-family: Lato; + font-weight: 500; scroll-snap-align: end; - border-bottom:1px solid; :hover { - background-color: #d4b851; + background-color: #eceef4; cursor: pointer; } `; -const useStyles = makeStyles({ - topbarInput: { - marginTop: '8px', - width: '100%', - }, -}); - interface DropdownProps { - clearInput: boolean; - handleClearInput: () => void; + open: boolean; + input: string; + handleCloseDropdown: () => void; } -export const Dropdown = ({ clearInput, handleClearInput }: DropdownProps) => { - const classes = useStyles(); - - const [open, setOpen] = React.useState(false); - const [input, setInput] = useState(''); - +export const Dropdown = forwardRef(({ open, input, handleCloseDropdown }: DropdownProps, ref: any) => { //courses - choosenCourses const [filteredCourses, setFilteredCourses] = useState>([]); const { courses, basket, addToBasket } = useContext(coursesContext)!; + useEffect(() => { + console.log('wut'); + }, [open, input, handleCloseDropdown]); + + useEffect(() => { + console.log('input is: ', input); + }, [input]); + + useEffect(() => { + console.log('is open: ', open); + }, [open]); + useEffect(() => { const filterCourses = (input: string) => { const choosenCoursesNames = basket.map(({ name }) => name.trim()); const filteredCourses = courses.filter( - ({ name }) => name.toLowerCase().includes(input.toLowerCase()) && !choosenCoursesNames.includes(name), + ({ name }) => + name + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .includes( + input + .toLowerCase() + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, ''), + ) && !choosenCoursesNames.includes(name), ); setFilteredCourses(filteredCourses); }; + console.log("filtering courses"); filterCourses(input); - }, [input, open, basket]); - - useEffect(() => { - clearInput && (setInput(''), handleClearInput()); - }, [clearInput]); - - const handleChange = (event: ChangeEvent) => setInput(event.target.value); - - const handleClick = () => setOpen(true); - - const handleClickAway = () => setOpen(false); + }, [open, input, basket]); const onCourseClick = async (event: MouseEvent) => { const target = event.currentTarget; @@ -97,31 +95,21 @@ export const Dropdown = ({ clearInput, handleClearInput }: DropdownProps) => { console.log('added course is'); console.log(course); addToBasket(course); - setOpen(false); + handleCloseDropdown(); } }; return ( - -
- - {open && ( - - {filteredCourses.map(({ name, id }, index) => ( - -

{name}

-
- ))} -
- )} -
-
+ + {open && ( + <> + {filteredCourses.map(({ name, id }, index) => ( + +

{name}

+
+ ))} + + )} +
); -}; +}); diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index cb97783..c50de4b 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -12,9 +12,8 @@ export const Profile = ({ anchorEl, handleClose }: ProfileProps) => { return ( - Profile - My account - Logout + Profil + Wyloguj ); }; diff --git a/src/components/Rightbar.tsx b/src/components/Rightbar.tsx index ec93536..66554f3 100644 --- a/src/components/Rightbar.tsx +++ b/src/components/Rightbar.tsx @@ -1,17 +1,14 @@ import React, { useContext } from 'react'; -import Snackbar from '@material-ui/core/Snackbar'; import { CourseCard } from './CourseCard'; import { coursesContext } from '../contexts/CoursesProvider'; -import MuiAlert, { AlertProps } from '@material-ui/lab/Alert'; import styled from 'styled-components'; -import { debounce } from "lodash"; +import { debounce } from 'lodash'; const RightbarStyled = styled.div` padding-top: 10px; padding-left: 15px; padding-right: 15px; text-align: center; - font-family: Lato; height: 100%; width: 300px; overflow-y: scroll; @@ -25,40 +22,32 @@ const RightbarStyled = styled.div` } ::-webkit-scrollbar-thumb { border-radius: 10px; - background-color: #d4b851; + background-color: black; border: 1px solid; } + background-color: white; + border-radius: 5px; + box-shadow: 3px 3px 3px -2px rgba(0, 0, 0, 0.59); `; -const RightbarTextStyled = styled.div` - display: flex; - flex-direction: column; -`; - const SaveButton = styled.div` display: flex; justify-content: center; align-items: center; - background-color: #417cab !important; + user-select: none; + background-color: #417cab; border-radius: 10px; cursor: pointer; height: 40px; - background-color: red; margin-bottom: 10px; &:hover { color: white; } - box-shadow: 6px 6px 6px -2px rgba(0,0,0,0.59); + box-shadow: 6px 6px 6px -2px rgba(0, 0, 0, 0.59); `; -function Alert(props: AlertProps) { - return ; -} - export const Rightbar = () => { const { courses, basket, saveBasket } = useContext(coursesContext)!; - const [open, setOpen] = React.useState(false); - const getBasketGroups = () => { const names = basket.map(({ name }) => name); return courses.filter(({ name }) => names.includes(name)); @@ -66,38 +55,15 @@ export const Rightbar = () => { const filteredCourses = getBasketGroups(); - const save = debounce(() => { - saveBasket(); - setOpen(true); - console.log("zmiana") - },500); - - const handleClose = (event?: React.SyntheticEvent, reason?: string) => { - if (reason === 'clickaway') { - return; - } - - setOpen(false); - }; + const handleSave = debounce(() => saveBasket(), 500); //need to insert student name from db and course maybe based on current time or from db too return ( - -

- Hubert Wrzesiński

- Semestr zimowy 2020/2021 -

- ZAPISZ -
+ ZAPISZ {filteredCourses.map((course, index) => ( ))} - - - Zapisano plan! - -
); }; diff --git a/src/components/Scheduler.tsx b/src/components/Scheduler.tsx index 1db75bd..b37e12d 100644 --- a/src/components/Scheduler.tsx +++ b/src/components/Scheduler.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, MouseEvent, useRef } from 'react'; +import React, { useEffect, MouseEvent, useRef, useCallback, useLayoutEffect } from 'react'; import { useState } from 'react'; import { SchedulerEvents } from './SchedulerEvents'; import { days, hours } from '../constants/index'; @@ -6,74 +6,93 @@ import styled from 'styled-components/macro'; const SchedulerWrapper = styled.div` border-collapse: collapse; - flex-grow: 1; + flex: 1; + background-color: white; + padding: 10px 40px 25px 10px; + border-radius: 5px; + margin-right: 20px; + flex-direction: column; + justify-content: center; + align-items: center; + box-shadow: 3px 3px 3px -2px rgba(0, 0, 0, 0.59); `; const TableBody = styled.div` + position: relative; width: 100%; display: flex; flex-direction: column; + height: calc(100% * 25 / 26); `; const TableRow = styled.div` display: flex; + height: 100%; `; const TableHead = styled.div` display: flex; width: 100%; + height: calc(100% / 26); `; interface TableCellProps { - height: number; + cellHeight?: number; + isHourColumn?: boolean; } const TableCell = styled.div` - height: ${({ height }) => height}px; - border: 1px solid #ddd; + border-width: ${({ isHourColumn }) => !isHourColumn && '2px'}; + border-style: ${({ isHourColumn }) => !isHourColumn && 'none solid dotted none'}; + border-color: rgb(242, 243, 245); display: flex; align-items: center; - justify-content: center; - flex: 1; - font-size: 1.25vw; -`; - -const T = styled.table` - width: 100%; - height: 100%; + justify-content: ${({ isHourColumn }) => (isHourColumn ? 'flex-end' : 'center')}; + flex: ${({ isHourColumn }) => (isHourColumn ? '1' : '5')}; + margin-right: ${({ isHourColumn }) => (isHourColumn ? '10px' : '0px')}; + margin-top: ${({ isHourColumn, cellHeight }) => (isHourColumn ? `-${cellHeight}px` : '0px')}; + font-size: 0.75vw; + user-select: none; + border-collapse: collapse; + :nth-child(2) { + border-left: 2px solid rgb(242, 243, 245); + } + font-weight: bold; `; export const Scheduler = () => { const cellRef = useRef(null); const [cellWidth, setCellWidth] = useState(0); const [cellTop, setCellTop] = useState(0); + const [cellHeight, setCellHeight] = useState(0); - const wrapperRef = useRef(null); - const [wrapperHeight, setWrapperHeight] = useState(0); + console.log('cell height: ', cellHeight); useEffect(() => { const handleResize = () => { - if (cellRef.current && wrapperRef.current) { + if (cellRef.current) { setCellWidth(cellRef.current.getBoundingClientRect().width); setCellTop(cellRef.current.getBoundingClientRect().top); - setWrapperHeight(wrapperRef.current.getBoundingClientRect().height); + setCellHeight(cellRef.current.getBoundingClientRect().height); + cellRef.current.style.backgroundColor = 'blue'; } }; handleResize(); window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); }, []); return ( <> - + {days.map((day, indexCell) => indexCell === 0 ? ( - + {day} ) : ( - + {day} ), @@ -84,19 +103,31 @@ export const Scheduler = () => { {[hour, '', '', '', '', ''].map((value, indexCell) => indexCell === 0 ? ( - + + {value} + + ) : indexRow === 0 && indexCell === 1 ? ( + + {value} + + ) : indexRow === 23 ? ( + + {value} + + ) : indexCell === 5 ? ( + {value} + ) : indexRow % 2 !== 0 ? ( + {value} ) : ( - - {value} - + {value} ), )} ))} + - ); diff --git a/src/components/SchedulerEvents.tsx b/src/components/SchedulerEvents.tsx index da8c9db..75a7048 100644 --- a/src/components/SchedulerEvents.tsx +++ b/src/components/SchedulerEvents.tsx @@ -11,13 +11,10 @@ interface SchedulerEventsProps { export const SchedulerEvents = ({ cellTop, cellWidth, cellHeight }: SchedulerEventsProps) => { const { basket } = useContext(coursesContext)!; - + console.log(`values: cellTop: ${cellTop}, cellWidth: ${cellWidth}, cellHeight: ${cellHeight}`); const [choosenGroupsMappedToEvents, setChoosenGroupsMappedToEvents] = useState([]); - interface GroupTimeToEventRowMapping { - [time: string]: number; - } - const groupTimeToEventRowMapping: GroupTimeToEventRowMapping = { + const groupTimeToEventRowMapping: { [time: string]: number } = { '8.15': 0, '10.00': 1, '11.45': 2, @@ -33,7 +30,7 @@ export const SchedulerEvents = ({ cellTop, cellWidth, cellHeight }: SchedulerEve const merged = [...classes, ...lectures]; //deleted if statement, maybe it is needed - const groupsMapped = merged.map(({ id, day, lecturer, room, time, name,type }) => ({ + const groupsMapped = merged.map(({ id, day, lecturer, room, time, name, type }) => ({ id, day, lecturer, @@ -56,17 +53,17 @@ export const SchedulerEvents = ({ cellTop, cellWidth, cellHeight }: SchedulerEve indexRow={index} cellTop={ index === 0 - ? cellTop + (cellHeight + cellHeight * 2 * index + cellHeight / 4) + ? cellHeight / 2 : index === 1 - ? cellTop + (cellHeight + cellHeight * 2 * index) + ? cellHeight * 4 : index === 2 - ? cellTop + (cellHeight + cellHeight * 2 * index - cellHeight / 4) + ? cellHeight * 7.5 : index === 3 - ? cellTop + (cellHeight + cellHeight * 2 * index - cellHeight / 4) + ? cellHeight * 11.5 : index === 4 - ? cellTop + (cellHeight + cellHeight * 2 * index - cellHeight / 2) + ? cellHeight * 15 : index === 5 - ? cellTop + (cellHeight + cellHeight * 2 * index - (cellHeight * 3) / 4) + ? cellHeight * 18.5 : 0 } cellWidth={cellWidth} diff --git a/src/components/SchedulerRow.tsx b/src/components/SchedulerRow.tsx index cb45953..5c0aced 100644 --- a/src/components/SchedulerRow.tsx +++ b/src/components/SchedulerRow.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent, useEffect, useState } from 'react'; +import React, { MouseEvent, useState } from 'react'; import { Group, GroupType } from '../types'; import styled from 'styled-components/macro'; import Popover from '@material-ui/core/Popover'; @@ -18,44 +18,41 @@ const useStyles = makeStyles((theme: Theme) => }), ); -interface SchedulerEventProps { +interface ClassesWrapperProps { eventIndex: number; cellTop: number; cellWidth: number; cellHeight: number; } -const SchedulerEvent = styled.div` +const ClassesWrapper = styled.div` position: absolute; display: flex; top: ${({ cellTop }) => cellTop}px; - left: ${({ cellWidth, eventIndex }) => cellWidth + 5 + cellWidth * eventIndex}px; - width: ${({ cellWidth }) => (cellWidth * 2.5) / 3}px; - height: ${({ cellHeight }) => (cellHeight * 2 * 3) / 4}px; + left: ${({ cellWidth, eventIndex }) => (cellWidth * 1) / 5 + 15 + cellWidth * eventIndex}px; + width: ${({ cellWidth }) => cellWidth - 10}px; + height: ${({ cellHeight }) => cellHeight * 3}px; z-index: 2; + padding-left: 10px; `; -interface ClassesProps{ - cellWidth: number; +interface ClassesProps { cellHeight: number; groupType: GroupType; } const Classes = styled.div` display: flex; + flex: 1; justify-content: center; align-items: center; z-index: 2; border-radius: 10px; - - font-size:0.90vw; - /* background-color: rgb(100, 181, 246); */ - width: ${({ cellWidth }) => (cellWidth * 2.5) / 3}px; - height: ${({ cellHeight }) => (cellHeight * 2 * 3) / 4}px; + height: ${({ cellHeight }) => cellHeight * 3}px; margin-right: 5px; - text-align: center; - background-color:${({groupType})=>groupType === "CLASS" ? "#FFDC61" : "#A68820"}; - box-shadow: 9px 9px 8px -2px rgba(0,0,0,0.59); + text-align: left; + background-color: ${({ groupType }) => (groupType === 'CLASS' ? '#FFDC61' : '#9ed3ff')}; + box-shadow: 9px 9px 8px -2px rgba(0, 0, 0, 0.59); `; interface SchedulerRowProps { @@ -85,9 +82,9 @@ export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth, cellHeight const open = Boolean(anchorEl); return ( - <> +
{[...Array(5)].map((_, eventIndex) => ( - { + console.log('group: ', group); + }} groupType={group.type} - cellWidth={cellWidth} cellHeight={cellHeight} id={`eventRow${indexRow}eventCol${eventIndex}${index}`} key={index} @@ -110,11 +109,10 @@ export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth, cellHeight onMouseEnter={(e) => handlePopoverOpen(e)} onMouseLeave={handlePopoverClose} > -

- {groups[index].name} -

- {groups[index].room} -

+
+

{groups[index].name}

+

{groups[index].room}

+
), )} -
+ ))} - +
); }; diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx index 1b2f0f5..a7789ff 100644 --- a/src/components/Topbar.tsx +++ b/src/components/Topbar.tsx @@ -1,80 +1,111 @@ -import React, { useState, MouseEvent } from 'react'; +import React, { useState, MouseEvent, ChangeEvent, useEffect } from 'react'; import Transfer from '../assets/transfer.png'; import Search from '../assets/search.svg'; -import UK from '../assets/UK.png'; -import PL from '../assets/PL.png'; -import User from '../assets/user.png'; -import CloseIcon from '../assets/close.svg'; +import { ReactComponent as Close } from '../assets/close.svg'; +import ProfileIcon from '../assets/account.svg'; import { Profile } from './Profile'; import { Dropdown } from './Dropdown'; -import styled from 'styled-components'; - -const TopbarTextStyled = styled.div` - @media only screen and (max-width: 670px) { - display: none; - } -`; +import PolishIcon from '../assets/poland.svg'; +import EnglishIcon from '../assets/united-kingdom.svg'; +import styled from 'styled-components/macro'; +import ClickAwayListener from 'react-click-away-listener'; const Topbar = styled.div` - background-color: #ffdc61; + background-color: #E3E5ED; height: 80px; padding: 5px; - font-family: comic sans MS; font-size: 24px; font-weight: bold; display: flex; justify-content: space-between; `; -const TopbarLogoWrapperStyled = styled.div` +const LogoWrapper = styled.div` display: flex; - align-items: center; - flex-grow: 0.5; justify-content: flex-start; + align-items: center; + flex: 2; + margin-left: 10px; `; -const TopbarLogoStyled = styled.img` - width: 80px; - height: 80px; +const Logo = styled.img` + width: 70px; + height: 70px; @media only screen and (max-width: 670px) { width: 60px; height: 60px; } `; -const TopbarInputStyled = styled.div` - width: 70%; +const Text = styled.div` + margin-left: 10px; + font-size: 1.4rem; + user-select: none; + @media only screen and (max-width: 670px) { + display: none; + } +`; + +const FlexboxColumn = styled.div` display: flex; - flex-grow: 3; + flex-direction: column; + flex: 12; `; -const TopbarInputFieldStyled = styled.div` - width: 96%; - margin-top: 10px; +const InputWrapper = styled.div` + display: flex; + margin-top: 15px; + background-color: #f2f4f7; + border-radius: 6px; + align-items: center; `; -const TopbarInputIconStyled = styled.img` - width: 35px; +const Input = styled.input` + background-color: #f1f2f5; + font-size: 20px; + height: 40px; + width: 100%; + border: none; + margin-left: 5px; + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + &:focus { + outline: none; + } +`; + +const CloseIcon = styled(Close)` + width: 30px; + height: 30px; + margin-right: 5px; @media only screen and (max-width: 670px) { width: 25px; } cursor: pointer; + :hover { + fill: grey; + } `; -const TopbarIcon = styled.img` - width: 50px; +const IconWrapper = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + width: 335px; +`; + +const Icon = styled.img` + width: 40px; + margin: 5px; cursor: pointer; @media only screen and (max-width: 670px) { width: 35px; } `; -const TopbarIconBox = styled.div` - display: flex; - align-items: center; - justify-content: space-around; - flex-grow: 1.5; -`; + + + interface TopbarProps { handleTransfer: (e: MouseEvent) => void; @@ -84,6 +115,8 @@ export default function ({ handleTransfer }: TopbarProps) { const [clearInput, setClearInput] = useState(false); const [isPolish, setIsPolish] = useState(false); const [anchorEl, setAnchorEl] = useState(null); + const [open, setOpen] = useState(false); + const [input, setInput] = useState(''); const onLangChange = () => setIsPolish(!isPolish); @@ -93,25 +126,41 @@ export default function ({ handleTransfer }: TopbarProps) { const handleClearInput = () => setClearInput(!clearInput); + const handleChange = (event: ChangeEvent) => setInput(event.target.value); + + const handleClick = () => setOpen(true); + + const handleCloseDropdown = () => setOpen(false); + + const handleClickAway = () => setOpen(false); + + useEffect(() => { + clearInput && (setInput(''), handleClearInput()); + }, [clearInput]); + return ( - - - plan na plan - - - - - - - - - - - - + + + plan na plan + + + + + + + + + + + + + {/* Maciej Głowacki */} + {/* */} + + - + ); } diff --git a/src/components/Transfer.tsx b/src/components/Transfer.tsx index 58442b4..4c542b4 100644 --- a/src/components/Transfer.tsx +++ b/src/components/Transfer.tsx @@ -53,7 +53,6 @@ const TransferReceiveStyled = styled.div` `; const TransferTextStyled = styled.div` - font-family: Lato; font-size: 30px; margin-bottom: 10px; `; diff --git a/src/constants/index.ts b/src/constants/index.ts index cd6753a..7898ca9 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -8,15 +8,30 @@ export const days = [ ]; export const hours = [ "8:00", + "", "9:00", + "", "10:00", + "", "11:00", + "", "12:00", + "", "13:00", + "", "14:00", + "", "15:00", + "", "16:00", + "", "17:00", + "", "18:00", + "", "19:00", + "", ]; + + +export const MONDAY_TO_FRIDAY = 5; \ No newline at end of file diff --git a/src/contexts/CoursesProvider.tsx b/src/contexts/CoursesProvider.tsx index 56d7de7..29f2650 100644 --- a/src/contexts/CoursesProvider.tsx +++ b/src/contexts/CoursesProvider.tsx @@ -1,7 +1,8 @@ import React, { useState, createContext, useEffect, ReactNode, useContext } from 'react'; import { Course, Group, Basket, GroupType } from '../types'; import axios from 'axios'; -import { CASContext, CASProvider } from './CASProvider'; +import { CASContext } from './CASProvider'; +import { useSnackbar } from 'notistack'; interface CourseContext { courses: Array; @@ -22,44 +23,73 @@ export const CoursesProvider = ({ children }: CoursesProviderProps) => { const [courses, setCourses] = useState>([]); const [basket, setBasket] = useState>([]); + const { enqueueSnackbar } = useSnackbar(); + const { closeSnackbar } = useSnackbar(); + const CAS = useContext(CASContext)!; const token = CAS?.user?.token; + const selectBasketIds = (basket: Array) => { + const classesIds = basket.map((course) => course.classes.id); + const lecturesIds = basket.map((course) => course?.lecture?.id); + + return lecturesIds[0] === undefined ? classesIds : [...classesIds, ...lecturesIds]; + }; + const addToBasket = (course: Course) => { - const courseToBasket = { + const courseToBasket: Basket = { name: course.name, id: course.id, classes: course.classes[0], lecture: course.lectures !== undefined ? course.lectures[0] : undefined, - } as Basket; + }; setBasket([...basket, courseToBasket]); }; + const deleteFromBasket = (id: number) => setBasket(basket.filter((course) => course.id !== id)); const saveBasket = async () => { + const basketIds = selectBasketIds(basket); + + const config = { + method: 'post' as const, + url: `${process.env.REACT_APP_API_URL}/api/v1/commisions/add?`, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + data: JSON.stringify(basketIds), + }; + + const action = (key: any) => ( + <> + + + ); + try { - let data = [7, 43, 54]; - let json = JSON.stringify(data); - let post_data = { json_data: json }; - const ech = await axios.post>( - `${process.env.REACT_APP_API_URL}/api/v1/commisions/add?`, - [7, 43, 54], - { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - ); - console.log('api response;', ech); + await axios.request(config); + enqueueSnackbar('Plan został zapisany', { + variant: 'success', + action, + }); } catch (e) { - console.log(e); + enqueueSnackbar('Zapisywanie planu nie powiodło się', { + variant: 'error', + action, + }); } - console.log('saving to basket'); }; const addGroup = (choosenGroup: Group, id: number) => { const basketCourse = basket.filter((course) => course.id === id)[0]; - const type = choosenGroup.type; + const { type } = choosenGroup; if (type === GroupType.CLASS) { setBasket( basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, classes: choosenGroup } : basket)), @@ -71,23 +101,43 @@ export const CoursesProvider = ({ children }: CoursesProviderProps) => { } }; - useEffect(() => { - const fetchData = async () => { - const { data } = await axios.get }>>( + const getNewestTimetable = async () => { + const config = { + method: 'get' as const, + url: `${process.env.REACT_APP_API_URL}/api/v1/assignments/getCurrentAssignments`, + headers: { + Authorization: `Bearer ${token}`, + }, + }; + try { + let { data: basket } = await axios.request(config); + if (basket === '') { + basket = []; + } + setBasket(basket); + } catch (e) { + console.log(e); + } + }; + + const fetchClasses = async () => { + try { + const { data: courses } = await axios.get>( `${process.env.REACT_APP_API_URL}/api/v1/courses/getCoursesWithGroups`, ); - const courses = data.map(({ id, name, groups }) => ({ - id: parseInt(id), - name, - lectures: groups.filter(({ type }) => type === GroupType.LECTURE), - classes: groups.filter(({ type }) => type === GroupType.CLASS), - })) as Array; - courses.sort((a: Course, b: Course) => (a.name > b.name ? 1 : -1)); - + courses.sort((a, b) => (a.name > b.name ? 1 : -1)); setCourses(courses); - }; - fetchData(); - }, []); + } catch (e) { + console.log(e); + } + }; + + useEffect(() => { + fetchClasses(); + if (token) { + getNewestTimetable(); + } + }, [token]); return ( diff --git a/src/index.tsx b/src/index.tsx index d3ecd06..0e9ee27 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,15 +4,24 @@ import { App } from './components/App'; import { CASProvider } from './contexts/CASProvider'; import { CoursesProvider } from './contexts/CoursesProvider'; import { GlobalStyles } from './styles/GlobalStyles'; +import { SnackbarProvider } from 'notistack'; ReactDOM.render( <> - - - - - - + + + + + + + + , document.getElementById('root'), ); diff --git a/src/styles/GlobalStyles.ts b/src/styles/GlobalStyles.ts index 4f68eaa..91281b6 100644 --- a/src/styles/GlobalStyles.ts +++ b/src/styles/GlobalStyles.ts @@ -11,7 +11,7 @@ export const GlobalStyles = createGlobalStyle` margin: 0; padding: 0; line-height: 24px; - + font-family: 'Roboto', sans-serif; } body::-webkit-scrollbar {