hihihi
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
28
.eslintrc.js
Normal file
@ -0,0 +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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
7
.prettierrc.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
semi: true,
|
||||||
|
trailingComma: 'all',
|
||||||
|
singleQuote: true,
|
||||||
|
printWidth: 120,
|
||||||
|
tabWidth: 2,
|
||||||
|
};
|
14443
package-lock.json
generated
Normal file
46
package.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "plannaplan",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@material-ui/core": "^4.10.0",
|
||||||
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
|
"@testing-library/react": "^9.5.0",
|
||||||
|
"@testing-library/user-event": "^7.2.1",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"react": "^16.13.1",
|
||||||
|
"react-dom": "^16.13.1",
|
||||||
|
"react-scripts": "3.4.1",
|
||||||
|
"styled-components": "^5.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^24.9.1",
|
||||||
|
"@types/node": "^12.12.54",
|
||||||
|
"@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"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"lint": "eslint src/*.{js,ts,tsx} --quiet --fix"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
13
public/manifest.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"short_name": "PlanNaPlan",
|
||||||
|
"name": "PlanNaPlan",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "logo.svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
BIN
src/assets/PL.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/UK.png
Normal file
After Width: | Height: | Size: 11 KiB |
1
src/assets/close.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg height="48" viewBox="0 0 48 48" width="48" xmlns="http://www.w3.org/2000/svg"><path d="M38 12.83l-2.83-2.83-11.17 11.17-11.17-11.17-2.83 2.83 11.17 11.17-11.17 11.17 2.83 2.83 11.17-11.17 11.17 11.17 2.83-2.83-11.17-11.17z"/><path d="M0 0h48v48h-48z" fill="none"/></svg>
|
After Width: | Height: | Size: 297 B |
BIN
src/assets/expand.png
Normal file
After Width: | Height: | Size: 535 B |
1
src/assets/search.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M344.5,298c15-23.6,23.8-51.6,23.8-81.7c0-84.1-68.1-152.3-152.1-152.3C132.1,64,64,132.2,64,216.3 c0,84.1,68.1,152.3,152.1,152.3c30.5,0,58.9-9,82.7-24.4l6.9-4.8L414.3,448l33.7-34.3L339.5,305.1L344.5,298z M301.4,131.2 c22.7,22.7,35.2,52.9,35.2,85c0,32.1-12.5,62.3-35.2,85c-22.7,22.7-52.9,35.2-85,35.2c-32.1,0-62.3-12.5-85-35.2 c-22.7-22.7-35.2-52.9-35.2-85c0-32.1,12.5-62.3,35.2-85c22.7-22.7,52.9-35.2,85-35.2C248.5,96,278.7,108.5,301.4,131.2z"/></svg>
|
After Width: | Height: | Size: 808 B |
BIN
src/assets/transfer.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
src/assets/user.png
Normal file
After Width: | Height: | Size: 18 KiB |
30
src/components/App.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React, { useState, useContext } from 'react';
|
||||||
|
import Topbar from './Topbar';
|
||||||
|
import { Transfer } from './Transfer';
|
||||||
|
import { Scheduler } from './Scheduler';
|
||||||
|
import { Rightbar } from './Rightbar';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const [isOpenTransfer, setOpenTransfer] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const handleTransfer = () => {
|
||||||
|
setOpenTransfer(!isOpenTransfer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Topbar handleTransfer={handleTransfer} />
|
||||||
|
<Transfer isOpen={isOpenTransfer} handleClose={handleTransfer} />
|
||||||
|
<Wrapper>
|
||||||
|
<Scheduler />
|
||||||
|
<Rightbar />
|
||||||
|
</Wrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
103
src/components/CourseCard.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import React, { useContext, MouseEvent } from 'react';
|
||||||
|
import Collapse from '@material-ui/core/Collapse';
|
||||||
|
import ExpandIcon from '../assets/expand.png';
|
||||||
|
import { Course, Group } from '../types/index';
|
||||||
|
import { coursesContext } from '../contexts/CoursesProvider';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
interface ClassExandIconProps {
|
||||||
|
isSelected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CourseStyled = styled.div`
|
||||||
|
display: flex;
|
||||||
|
min-height: 50px;
|
||||||
|
background-color: rgb(100, 181, 246) !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: stretch;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CourseNameStyled = styled.div`
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ClassGroupStyled = styled.div`
|
||||||
|
padding-top: 1px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 1s;
|
||||||
|
background-color: #8bc8fb;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ClassExandIconStyled = styled.img<ClassExandIconProps>`
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 20px;
|
||||||
|
transition: 0.2s;
|
||||||
|
transform: ${(props) => (props.isSelected ? 'scaleY(-1);' : 'scaleY(1);')};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
expanded: {
|
||||||
|
maxHeight: '244px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
},
|
||||||
|
'@global': {
|
||||||
|
'*::-webkit-scrollbar': {
|
||||||
|
width: '0.4em',
|
||||||
|
},
|
||||||
|
'*::-webkit-scrollbar-track': {
|
||||||
|
'-webkit-box-shadow': 'inset 0 0 6px rgba(1,0,0,0.1)',
|
||||||
|
},
|
||||||
|
'*::-webkit-scrollbar-thumb': {
|
||||||
|
borderRadius: '10px',
|
||||||
|
backgroundColor: '#d4b851',
|
||||||
|
outline: '1px solid slategrey',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface CourseCardProps {
|
||||||
|
onCardClick: (event: MouseEvent) => void;
|
||||||
|
course: Course;
|
||||||
|
id: string;
|
||||||
|
isSelected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CourseCard = ({ onCardClick, course, id, isSelected }: CourseCardProps) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const { addGroup } = useContext(coursesContext)!;
|
||||||
|
|
||||||
|
console.log(`course`);
|
||||||
|
console.log(course);
|
||||||
|
const onGroupClick = (group: Group, id: number) => addGroup(group, id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CourseStyled onClick={onCardClick} id={id}>
|
||||||
|
<CourseNameStyled>{course.name}</CourseNameStyled>
|
||||||
|
<Collapse className={classes.expanded} in={isSelected} timeout="auto" unmountOnExit>
|
||||||
|
{course.groups.map((group, index) => (
|
||||||
|
<ClassGroupStyled key={index} onClick={() => onGroupClick(group, course.id)}>
|
||||||
|
<p>
|
||||||
|
{group.time} {group.room} <br></br> {group.lecturer}
|
||||||
|
</p>
|
||||||
|
</ClassGroupStyled>
|
||||||
|
))}
|
||||||
|
</Collapse>
|
||||||
|
<div onClick={onCardClick} id={id}>
|
||||||
|
<ClassExandIconStyled isSelected={isSelected} alt="expand" src={ExpandIcon} />
|
||||||
|
</div>
|
||||||
|
</CourseStyled>
|
||||||
|
);
|
||||||
|
};
|
116
src/components/Dropdown.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import React, { useState, useContext, useEffect, MouseEvent } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { Input } from '@material-ui/core';
|
||||||
|
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
|
||||||
|
import { coursesContext } from '../contexts/CoursesProvider';
|
||||||
|
import { Course, Basket } from '../types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
|
||||||
|
const CourseStyled = styled.div`
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 5px;
|
||||||
|
padding-left: 20px;
|
||||||
|
background-color: #e6c759;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: Lato;
|
||||||
|
:hover {
|
||||||
|
background-color: #d4b851;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DropdownStyled = styled.div`
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
topbarInput: {
|
||||||
|
marginTop: '8px',
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DropdownProps {
|
||||||
|
clearInput: boolean;
|
||||||
|
handleClearInput: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Dropdown = ({ clearInput, handleClearInput }: DropdownProps) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [input, setInput] = useState<string>('');
|
||||||
|
|
||||||
|
//courses - choosenCourses
|
||||||
|
const [filteredCourses, setFilteredCourses] = useState<Array<Course>>([]);
|
||||||
|
|
||||||
|
const { courses, basket, addToBasket } = useContext(coursesContext)!;
|
||||||
|
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
setFilteredCourses(filteredCourses);
|
||||||
|
};
|
||||||
|
filterCourses(input);
|
||||||
|
}, [input, open, basket]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (clearInput) {
|
||||||
|
setInput('');
|
||||||
|
handleClearInput();
|
||||||
|
}
|
||||||
|
}, [clearInput]);
|
||||||
|
|
||||||
|
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setInput(event.target.value);
|
||||||
|
|
||||||
|
const handleClick = () => setOpen(true);
|
||||||
|
|
||||||
|
const handleClickAway = () => setOpen(false);
|
||||||
|
|
||||||
|
const onCourseClick = async (event: MouseEvent) => {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
if (target.id && target.textContent) {
|
||||||
|
const id = target.id;
|
||||||
|
const name = target.textContent;
|
||||||
|
|
||||||
|
//porozmawiać z Filipem, żeby odrobinę przerobił endpoint
|
||||||
|
const course: Basket = { name: name, id: parseInt(id), lecture: null, classes: null };
|
||||||
|
|
||||||
|
addToBasket(course);
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClickAwayListener onClickAway={handleClickAway}>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="Wyszukaj..."
|
||||||
|
inputProps={{ 'aria-label': 'description' }}
|
||||||
|
className={classes.topbarInput}
|
||||||
|
onChange={handleChange}
|
||||||
|
onClick={handleClick}
|
||||||
|
value={input}
|
||||||
|
/>
|
||||||
|
{open && (
|
||||||
|
<DropdownStyled>
|
||||||
|
{filteredCourses.map(({ name, id }, index) => (
|
||||||
|
<CourseStyled key={index} id={id.toString()} onClick={onCourseClick}>
|
||||||
|
<p>{name} </p>
|
||||||
|
</CourseStyled>
|
||||||
|
))}
|
||||||
|
</DropdownStyled>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ClickAwayListener>
|
||||||
|
);
|
||||||
|
};
|
20
src/components/Profile.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Menu, MenuItem } from '@material-ui/core';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { CASContext } from '../contexts/CASProvider';
|
||||||
|
|
||||||
|
interface ProfileProps {
|
||||||
|
anchorEl: HTMLElement | null;
|
||||||
|
handleClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Profile = ({ anchorEl, handleClose }: ProfileProps) => {
|
||||||
|
const { logout } = useContext(CASContext)!;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||||
|
<MenuItem>Profile</MenuItem>
|
||||||
|
<MenuItem>My account</MenuItem>
|
||||||
|
<MenuItem onClick={logout}>Logout</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
69
src/components/Rightbar.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React, { useState, useContext, MouseEvent } from 'react';
|
||||||
|
import { CourseCard } from './CourseCard';
|
||||||
|
import { coursesContext } from '../contexts/CoursesProvider';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const RightbarStyled = styled.div`
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: Lato;
|
||||||
|
width: 300px;
|
||||||
|
height: 85vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #d4b851;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const RightbarTextStyled = styled.div`
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Rightbar = () => {
|
||||||
|
const [selectedCardId, setSelectedCardId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { courses, basket } = useContext(coursesContext)!;
|
||||||
|
|
||||||
|
const getBasketGroups = () => {
|
||||||
|
const ids = basket.map(({ id }) => id);
|
||||||
|
return courses.filter(({ id }) => ids.includes(id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredCourses = getBasketGroups();
|
||||||
|
|
||||||
|
//działa clunky
|
||||||
|
const onCardClick = (event: MouseEvent) => {
|
||||||
|
const target = event.currentTarget;
|
||||||
|
selectedCardId === target.id ? setSelectedCardId(null) : setSelectedCardId(target.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
//need to insert student name from db and course maybe based on current time or from db too
|
||||||
|
return (
|
||||||
|
<RightbarStyled>
|
||||||
|
<RightbarTextStyled>
|
||||||
|
Hubert Wrzesiński<br></br>
|
||||||
|
Semestr zimowy 2020/2021
|
||||||
|
</RightbarTextStyled>
|
||||||
|
{filteredCourses.map((course, index) => (
|
||||||
|
<CourseCard
|
||||||
|
course={course}
|
||||||
|
key={index}
|
||||||
|
id={index.toString()}
|
||||||
|
onCardClick={onCardClick}
|
||||||
|
isSelected={selectedCardId === index.toString()}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RightbarStyled>
|
||||||
|
);
|
||||||
|
};
|
107
src/components/Scheduler.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { SchedulerEvents } from './SchedulerEvents';
|
||||||
|
import { days, hours } from '../constants/index';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const SchedulerWrapper = styled.div`
|
||||||
|
flex-grow: 3;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-collapse: collapse;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableBody = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableCell = styled.div`
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableHead = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TableHeadCell = styled.div`
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Scheduler = () => {
|
||||||
|
const [currentEventsIds, setCurrentEventsIds] = useState<Array<string>>([]);
|
||||||
|
const cellRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [cellWidth, setCellWidth] = useState(0);
|
||||||
|
const [cellTop, setCellTop] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (cellRef.current) {
|
||||||
|
setCellWidth(cellRef.current.getBoundingClientRect().width);
|
||||||
|
setCellTop(cellRef.current.getBoundingClientRect().top);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const displayEvents = () => {
|
||||||
|
currentEventsIds.map((eventId: string) => {
|
||||||
|
const event = document.getElementById(eventId);
|
||||||
|
if (event) {
|
||||||
|
event.style.display = 'block';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
displayEvents();
|
||||||
|
}, [currentEventsIds]);
|
||||||
|
|
||||||
|
// const handleClick = (e: React.MouseEvent) => {
|
||||||
|
// const cellId = e.currentTarget.id;
|
||||||
|
// const column = cellId.slice(0, 1);
|
||||||
|
// const row = cellId.slice(1);
|
||||||
|
// const eventId = `eventCol${column}eventRow${Math.floor(parseInt(row) / 2)}`;
|
||||||
|
|
||||||
|
// setCurrentEventsIds((currentEventsIds) => [...currentEventsIds, eventId]);
|
||||||
|
// };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SchedulerWrapper>
|
||||||
|
<TableHead>
|
||||||
|
{days.map((day, index) => (
|
||||||
|
<TableHeadCell key={index}>{day}</TableHeadCell>
|
||||||
|
))}
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{hours.map((hour, indexRow) => (
|
||||||
|
<TableRow key={indexRow}>
|
||||||
|
{[hour, '', '', '', '', ''].map((value, indexCell) =>
|
||||||
|
indexRow === 0 && indexCell === 1 ? (
|
||||||
|
<TableCell key={`${indexRow}${indexCell}`} ref={cellRef}></TableCell>
|
||||||
|
) : (
|
||||||
|
<TableCell key={`${indexRow}${indexCell}`}>{value}</TableCell>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<SchedulerEvents cellTop={cellTop} cellWidth={cellWidth} />
|
||||||
|
</SchedulerWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
72
src/components/SchedulerEvents.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import { SchedulerRow } from './SchedulerRow';
|
||||||
|
import { coursesContext } from '../contexts/CoursesProvider';
|
||||||
|
import { Group, Basket } from '../types';
|
||||||
|
|
||||||
|
interface SchedulerEventsProps {
|
||||||
|
cellTop: number;
|
||||||
|
cellWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SchedulerEvents = ({ cellTop, cellWidth }: SchedulerEventsProps) => {
|
||||||
|
const { basket } = useContext(coursesContext)!;
|
||||||
|
|
||||||
|
const [choosenGroupsMappedToEvents, setChoosenGroupsMappedToEvents] = useState<any>([]);
|
||||||
|
|
||||||
|
interface GroupTimeToEventRowMapping {
|
||||||
|
[time: string]: number;
|
||||||
|
}
|
||||||
|
const groupTimeToEventRowMapping: GroupTimeToEventRowMapping = {
|
||||||
|
'8.15': 0,
|
||||||
|
'10.00': 1,
|
||||||
|
'11.45': 2,
|
||||||
|
'13.45': 3,
|
||||||
|
'15.30': 4,
|
||||||
|
'17.15': 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function mapGroupTimeToEventRow(basket: Array<Basket>) {
|
||||||
|
const basketGroups = basket.map(({ classes, lecture }) => ({
|
||||||
|
...classes,
|
||||||
|
...lecture,
|
||||||
|
})) as Array<Group>;
|
||||||
|
|
||||||
|
console.log('passed basket');
|
||||||
|
console.log(basket);
|
||||||
|
console.log(`basketgroups`);
|
||||||
|
console.log(basketGroups);
|
||||||
|
const groupsMapped = basketGroups.map(({ id, day, lecturer, room, time }) => ({
|
||||||
|
id,
|
||||||
|
day,
|
||||||
|
lecturer,
|
||||||
|
room,
|
||||||
|
eventRow: groupTimeToEventRowMapping[time],
|
||||||
|
}));
|
||||||
|
setChoosenGroupsMappedToEvents(groupsMapped);
|
||||||
|
}
|
||||||
|
mapGroupTimeToEventRow(basket);
|
||||||
|
}, [basket]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{[...Array(6)].map((_, index) => (
|
||||||
|
<SchedulerRow
|
||||||
|
key={index}
|
||||||
|
groups={choosenGroupsMappedToEvents.filter((group: any) => {
|
||||||
|
return group.eventRow === index;
|
||||||
|
})}
|
||||||
|
indexRow={index}
|
||||||
|
cellTop={
|
||||||
|
index == 3
|
||||||
|
? cellTop + (25 + 80 * index)
|
||||||
|
: index < 3
|
||||||
|
? cellTop + (12 + 80 * index)
|
||||||
|
: cellTop + (25 + 80 * index)
|
||||||
|
}
|
||||||
|
cellWidth={cellWidth}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
48
src/components/SchedulerRow.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Group } from '../types';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface SchedulerEventProps {
|
||||||
|
eventIndex: number;
|
||||||
|
cellTop: number;
|
||||||
|
cellWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SchedulerEvent = styled.div<SchedulerEventProps>`
|
||||||
|
position: absolute;
|
||||||
|
top: ${(props) => props.cellTop}px;
|
||||||
|
left: ${(props) => props.cellWidth + 5 + props.cellWidth * props.eventIndex}px;
|
||||||
|
width: ${(props) => (props.cellWidth * 2) / 3}px;
|
||||||
|
height: 69px;
|
||||||
|
background-color: lightblue;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface SchedulerRowProps {
|
||||||
|
groups: Array<Group>;
|
||||||
|
indexRow: number;
|
||||||
|
cellTop: number;
|
||||||
|
cellWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth }: SchedulerRowProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...Array(5)].map((_, eventIndex) => (
|
||||||
|
<SchedulerEvent
|
||||||
|
eventIndex={eventIndex}
|
||||||
|
cellTop={cellTop}
|
||||||
|
cellWidth={cellWidth}
|
||||||
|
key={eventIndex}
|
||||||
|
id={`eventRow${indexRow}eventCol${eventIndex}`}
|
||||||
|
>
|
||||||
|
{groups.map((group, index) =>
|
||||||
|
group.day === eventIndex && <div key={index}>{groups[index]?.lecturer}</div>,
|
||||||
|
)}
|
||||||
|
</SchedulerEvent>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
117
src/components/Topbar.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React, { useState, MouseEvent } 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 { 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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Topbar = styled.div`
|
||||||
|
background-color: #ffdc61;
|
||||||
|
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`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 0.5;
|
||||||
|
justify-content: flex-start;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TopbarLogoStyled = styled.img`
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
@media only screen and (max-width: 670px) {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TopbarInputStyled = styled.div`
|
||||||
|
width: 70%;
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 3;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TopbarInputFieldStyled = styled.div`
|
||||||
|
width: 96%;
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TopbarInputIconStyled = styled.img`
|
||||||
|
width: 35px;
|
||||||
|
@media only screen and (max-width: 670px) {
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TopbarIcon = styled.img`
|
||||||
|
width: 50px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ({ handleTransfer }: TopbarProps) {
|
||||||
|
const [clearInput, setClearInput] = useState(false);
|
||||||
|
const [isPolish, setIsPolish] = useState(false);
|
||||||
|
const [anchorEl, setAnchorEl] = useState<HTMLImageElement | null>(null);
|
||||||
|
|
||||||
|
const onLangChange = () => setIsPolish(!isPolish);
|
||||||
|
|
||||||
|
const handleProfile = (event: MouseEvent<HTMLImageElement>) => setAnchorEl(event.currentTarget);
|
||||||
|
|
||||||
|
const handleClose = () => setAnchorEl(null);
|
||||||
|
|
||||||
|
const handleClearInput = () => setClearInput(!clearInput);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Topbar>
|
||||||
|
<TopbarLogoWrapperStyled>
|
||||||
|
<TopbarLogoStyled alt="logo" src="https://plannaplan.pl/img/logo.svg" />
|
||||||
|
<TopbarTextStyled> plan na plan </TopbarTextStyled>
|
||||||
|
</TopbarLogoWrapperStyled>
|
||||||
|
<TopbarInputStyled>
|
||||||
|
<TopbarInputIconStyled alt="search" src={Search} />
|
||||||
|
<TopbarInputFieldStyled>
|
||||||
|
<Dropdown clearInput={clearInput} handleClearInput={handleClearInput}/>
|
||||||
|
</TopbarInputFieldStyled>
|
||||||
|
<TopbarInputIconStyled alt="close" src={CloseIcon} onClick={handleClearInput}/>
|
||||||
|
</TopbarInputStyled>
|
||||||
|
<TopbarIconBox>
|
||||||
|
<TopbarIcon alt="transfer" src={Transfer} onClick={handleTransfer} />
|
||||||
|
<TopbarIcon alt="change_language" src={isPolish ? UK : PL} onClick={onLangChange} />
|
||||||
|
<TopbarIcon alt="profile" src={User} onClick={handleProfile} />
|
||||||
|
<Profile anchorEl={anchorEl} handleClose={handleClose} />
|
||||||
|
</TopbarIconBox>
|
||||||
|
</Topbar>
|
||||||
|
);
|
||||||
|
};
|
115
src/components/Transfer.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from '@material-ui/core/Modal';
|
||||||
|
import Fade from '@material-ui/core/Fade';
|
||||||
|
import Input from '@material-ui/core/Input';
|
||||||
|
import { makeStyles } from '@material-ui/core/styles';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
interface TransferProps {
|
||||||
|
handleClose: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||||
|
isOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
wrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
textAlign: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const TransferStyled = styled.div`
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
min-width: 35%;
|
||||||
|
height: 70%;
|
||||||
|
padding-top: 40px;
|
||||||
|
background: #006b96;
|
||||||
|
box-shadow: 0px 0px 0px 4px #006b96;
|
||||||
|
border: 4px solid #ffc400;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3ch;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransferGiveStyled = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransferReceiveStyled = styled.div`
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransferTextStyled = styled.div`
|
||||||
|
font-family: Lato;
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TransferInputStyled = styled.div`
|
||||||
|
width: 250px;
|
||||||
|
height: 25px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
transition-duration: 0.3s;
|
||||||
|
input::placeholder {
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Transfer = ({ handleClose, isOpen }: TransferProps) => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
className={classes.wrapper}
|
||||||
|
open={isOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
aria-labelledby="simple-modal-title"
|
||||||
|
aria-describedby="simple-modal-description"
|
||||||
|
>
|
||||||
|
<Fade in={isOpen}>
|
||||||
|
<TransferStyled>
|
||||||
|
<TransferGiveStyled>
|
||||||
|
<TransferTextStyled>Oddam</TransferTextStyled>
|
||||||
|
<TransferInputStyled>
|
||||||
|
{' '}
|
||||||
|
<Input
|
||||||
|
placeholder="Wyszukaj..."
|
||||||
|
inputProps={{ 'aria-label': 'description' }}
|
||||||
|
className="top-bar__input-field"
|
||||||
|
/>
|
||||||
|
</TransferInputStyled>
|
||||||
|
</TransferGiveStyled>
|
||||||
|
<TransferReceiveStyled>
|
||||||
|
<TransferTextStyled>Przyjmę</TransferTextStyled>
|
||||||
|
<TransferInputStyled>
|
||||||
|
{' '}
|
||||||
|
<Input
|
||||||
|
placeholder="Wyszukaj..."
|
||||||
|
inputProps={{ 'aria-label': 'description' }}
|
||||||
|
className="top-bar__input-field"
|
||||||
|
/>
|
||||||
|
</TransferInputStyled>
|
||||||
|
</TransferReceiveStyled>
|
||||||
|
</TransferStyled>
|
||||||
|
</Fade>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
22
src/constants/index.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export const days = [
|
||||||
|
"",
|
||||||
|
"poniedziałek",
|
||||||
|
"wtorek",
|
||||||
|
"środa",
|
||||||
|
"czwartek",
|
||||||
|
"piątek",
|
||||||
|
];
|
||||||
|
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",
|
||||||
|
];
|
47
src/contexts/CASProvider.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { User } from '../types';
|
||||||
|
|
||||||
|
export interface CASContext {
|
||||||
|
user: User | null;
|
||||||
|
logout: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CASContext = React.createContext<CASContext | null>(null);
|
||||||
|
|
||||||
|
export interface CASProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CASProvider = ({ children }: CASProviderProps) => {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
login();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const ticket = urlParams.get('ticket');
|
||||||
|
|
||||||
|
if (!ticket) {
|
||||||
|
redirectToCASLoginService();
|
||||||
|
}
|
||||||
|
if (ticket) {
|
||||||
|
setUser({ ...user, ticket: ticket });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
redirectToCASLogoutService();
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToCASLogoutService() {
|
||||||
|
window.location.replace(`https://cas.amu.edu.pl/cas/logout?service=${window.origin}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToCASLoginService() {
|
||||||
|
window.location.replace(`https://cas.amu.edu.pl/cas/login?service=${window.origin}&locale=pl`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <CASContext.Provider value={{ user, logout }}>{children}</CASContext.Provider>;
|
||||||
|
};
|
63
src/contexts/CoursesProvider.tsx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import React, { useState, createContext, useEffect } from 'react';
|
||||||
|
import { Course, Group, Basket, GroupType } from '../types';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
interface CourseContext {
|
||||||
|
courses: Array<Course>;
|
||||||
|
basket: Array<Basket>;
|
||||||
|
addToBasket: (courses: Basket) => void;
|
||||||
|
addGroup: (group: Group, id: number) => void;
|
||||||
|
}
|
||||||
|
export const coursesContext = createContext<CourseContext | null>(null);
|
||||||
|
|
||||||
|
interface CoursesProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoursesProvider = ({ children }: CoursesProviderProps) => {
|
||||||
|
//fetch courses with groups
|
||||||
|
const [courses, setCourses] = useState<Array<Course>>([]);
|
||||||
|
const [basket, setBasket] = useState<Array<Basket>>([]);
|
||||||
|
|
||||||
|
const addToBasket = (course: Basket) => setBasket([...basket, course]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('BASKET');
|
||||||
|
console.log(basket);
|
||||||
|
}, [basket]);
|
||||||
|
|
||||||
|
//immutability
|
||||||
|
|
||||||
|
const addGroup = (choosenGroup: Group, id: number) => {
|
||||||
|
const basketCourse = basket.filter((course) => course.id === id)[0];
|
||||||
|
const type = choosenGroup.type;
|
||||||
|
if (type === GroupType.CLASS) {
|
||||||
|
setBasket(
|
||||||
|
basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, classes: choosenGroup } : basket)),
|
||||||
|
);
|
||||||
|
} else if (type === GroupType.LECTURE) {
|
||||||
|
setBasket(
|
||||||
|
basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, lecture: choosenGroup } : basket)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const { data: courses } = await axios.get(`${process.env.REACT_APP_API_URL}/getCourses`);
|
||||||
|
|
||||||
|
for (const course of courses) {
|
||||||
|
const { data: groups } = await axios.get(`${process.env.REACT_APP_API_URL}/getCourseGroups?id=${course.id}`);
|
||||||
|
//porozmawiać z Filipem, żeby odrobinę przerobił endpoint
|
||||||
|
course.groups = groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCourses(courses);
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<coursesContext.Provider value={{ courses, basket, addToBasket, addGroup }}>{children}</coursesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
29
src/contexts/reducers.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// import { Group, Course } from '../types';
|
||||||
|
|
||||||
|
export enum Types {
|
||||||
|
addToBasket = 'ADD_CHOOSEN_COURSE',
|
||||||
|
removeChoosenCourse = 'REMOVE_CHOOSEN_COURSE',
|
||||||
|
addGroup = 'ADD_CHOOSEN_GROUP',
|
||||||
|
removeChoosenGroup = 'REMOVE_CHOOSEN_GROUP',
|
||||||
|
}
|
||||||
|
|
||||||
|
// type ChoosenCoursesPayload = {
|
||||||
|
// [Types.addToBasket]: {};
|
||||||
|
// };
|
||||||
|
|
||||||
|
// type ChoosenGroupsPayload = {
|
||||||
|
// [Types.Create]: {
|
||||||
|
// id: number;
|
||||||
|
// name: string;
|
||||||
|
// price: number;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const choosenGroupsReducer = (state, action) => {
|
||||||
|
// switch (action.type) {
|
||||||
|
// case Types.addGroup:
|
||||||
|
// return add;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
//https://dev.to/elisealcala/react-context-with-usereducer-and-typescript-4obm
|
18
src/index.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { App } from './components/App';
|
||||||
|
import { CASProvider } from './contexts/CASProvider';
|
||||||
|
import { CoursesProvider } from './contexts/CoursesProvider';
|
||||||
|
import { GlobalStyles } from './styles/GlobalStyles';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<>
|
||||||
|
<CoursesProvider>
|
||||||
|
<CASProvider>
|
||||||
|
<GlobalStyles />
|
||||||
|
<App />
|
||||||
|
</CASProvider>
|
||||||
|
</CoursesProvider>
|
||||||
|
</>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
20
src/styles/GlobalStyles.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
|
export const GlobalStyles = createGlobalStyle`
|
||||||
|
*, *::before, *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 24px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
body::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
33
src/types/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export enum GroupType {
|
||||||
|
LECTURE = 'LECTURE',
|
||||||
|
CLASS = 'CLASS',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Basket {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
lecture: Group | null;
|
||||||
|
classes: Group | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Group {
|
||||||
|
id: number;
|
||||||
|
day: number;
|
||||||
|
time: string;
|
||||||
|
lecturer: string;
|
||||||
|
room: string;
|
||||||
|
type: GroupType;
|
||||||
|
capacity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Course {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
groups: Array<Group>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
name?: string;
|
||||||
|
surname?: string;
|
||||||
|
ticket: string | null;
|
||||||
|
}
|
19
tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|