Merge pull request 'admin-panel' (#33) from admin-panel into master
Reviewed-on: http://git.plannaplan.pl/y0rune/frontend/pulls/33 Reviewed-by: Maciej <glowackimaciej97@gmail.com>
@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
build
|
28
.eslintrc.js
@ -1,28 +0,0 @@
|
||||
// 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',
|
||||
// },
|
||||
// },
|
||||
// };
|
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
node_modules
|
||||
.env
|
||||
build
|
15
.vscode/launch.json
vendored
@ -1,15 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"files": {
|
||||
"main.js": "/static/js/main.993a2e72.chunk.js",
|
||||
"main.js.map": "/static/js/main.993a2e72.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.38573804.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.38573804.js.map",
|
||||
"static/js/2.f941ed96.chunk.js": "/static/js/2.f941ed96.chunk.js",
|
||||
"static/js/2.f941ed96.chunk.js.map": "/static/js/2.f941ed96.chunk.js.map",
|
||||
"index.html": "/index.html",
|
||||
"precache-manifest.36f396009b702c50e7d07732240c69e5.js": "/precache-manifest.36f396009b702c50e7d07732240c69e5.js",
|
||||
"service-worker.js": "/service-worker.js",
|
||||
"static/js/2.f941ed96.chunk.js.LICENSE.txt": "/static/js/2.f941ed96.chunk.js.LICENSE.txt",
|
||||
"static/media/PL.png": "/static/media/PL.6e9ee893.png",
|
||||
"static/media/UK.png": "/static/media/UK.b4dad475.png",
|
||||
"static/media/close.svg": "/static/media/close.464128e7.svg",
|
||||
"static/media/search.svg": "/static/media/search.fa0d12ae.svg",
|
||||
"static/media/user.png": "/static/media/user.4ba6e2a4.png"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.38573804.js",
|
||||
"static/js/2.f941ed96.chunk.js",
|
||||
"static/js/main.993a2e72.chunk.js"
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="pl"><head><meta charset="utf-8"/><link rel="icon" href="/logo.svg"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo.svg"/><link rel="manifest" href="/manifest.json"/><title>PlanNaPlan</title></head><body><noscript>Potrzebujesz włączyć JavaScript, żeby otworzyć tę aplikację<br>You need to enable JavaScript to run this app.</noscript><div id="root"></div><script>!function(e){function r(r){for(var n,l,a=r[0],p=r[1],f=r[2],c=0,s=[];c<a.length;c++)l=a[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);for(i&&i(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var p=t[a];0!==o[p]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this.webpackJsonpplannaplan=this.webpackJsonpplannaplan||[],p=a.push.bind(a);a.push=r,a=a.slice();for(var f=0;f<a.length;f++)r(a[f]);var i=p;t()}([])</script><script src="/static/js/2.f941ed96.chunk.js"></script><script src="/static/js/main.993a2e72.chunk.js"></script></body></html>
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"short_name": "PlanNaPlan",
|
||||
"name": "PlanNaPlan",
|
||||
"icons": [
|
||||
{
|
||||
"src": "logo.svg"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
{
|
||||
"revision": "53d5f0388bed11f07f899035cb978454",
|
||||
"url": "/index.html"
|
||||
},
|
||||
{
|
||||
"revision": "4d480560b5cfff1aa5a8",
|
||||
"url": "/static/js/2.f941ed96.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "68ba00d5eb083746d913686923a3d904",
|
||||
"url": "/static/js/2.f941ed96.chunk.js.LICENSE.txt"
|
||||
},
|
||||
{
|
||||
"revision": "209120e09e641f02e991",
|
||||
"url": "/static/js/main.993a2e72.chunk.js"
|
||||
},
|
||||
{
|
||||
"revision": "c20b5bb64d57e939de77",
|
||||
"url": "/static/js/runtime-main.38573804.js"
|
||||
},
|
||||
{
|
||||
"revision": "6e9ee893741bee46177f72398176ad9e",
|
||||
"url": "/static/media/PL.6e9ee893.png"
|
||||
},
|
||||
{
|
||||
"revision": "b4dad47599118a028176ceb8bbbc7a02",
|
||||
"url": "/static/media/UK.b4dad475.png"
|
||||
},
|
||||
{
|
||||
"revision": "464128e7e5f72a712b7c752996e86b58",
|
||||
"url": "/static/media/close.464128e7.svg"
|
||||
},
|
||||
{
|
||||
"revision": "fa0d12ae1b4380d1515aed6c5e5cbc07",
|
||||
"url": "/static/media/search.fa0d12ae.svg"
|
||||
},
|
||||
{
|
||||
"revision": "4ba6e2a4136a8a9ce072ee9c1e403f3a",
|
||||
"url": "/static/media/user.4ba6e2a4.png"
|
||||
}
|
||||
]);
|
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,39 +0,0 @@
|
||||
/**
|
||||
* Welcome to your Workbox-powered service worker!
|
||||
*
|
||||
* You'll need to register this file in your web app and you should
|
||||
* disable HTTP caching for this file too.
|
||||
* See https://goo.gl/nhQhGp
|
||||
*
|
||||
* The rest of the code is auto-generated. Please don't update this file
|
||||
* directly; instead, make changes to your Workbox build configuration
|
||||
* and re-run your build process.
|
||||
* See https://goo.gl/2aRDsh
|
||||
*/
|
||||
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"/precache-manifest.36f396009b702c50e7d07732240c69e5.js"
|
||||
);
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
workbox.core.clientsClaim();
|
||||
|
||||
/**
|
||||
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
||||
|
||||
workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), {
|
||||
|
||||
blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/],
|
||||
});
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Lodash <https://lodash.com/>
|
||||
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
|
||||
/**
|
||||
* A better abstraction over CSS.
|
||||
*
|
||||
* @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present
|
||||
* @website https://github.com/cssinjs/jss
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/** @license React v0.19.1
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
@ -1,2 +0,0 @@
|
||||
!function(e){function r(r){for(var n,l,a=r[0],p=r[1],f=r[2],c=0,s=[];c<a.length;c++)l=a[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in p)Object.prototype.hasOwnProperty.call(p,n)&&(e[n]=p[n]);for(i&&i(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var p=t[a];0!==o[p]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this.webpackJsonpplannaplan=this.webpackJsonpplannaplan||[],p=a.push.bind(a);a.push=r,a=a.slice();for(var f=0;f<a.length;f++)r(a[f]);var i=p;t()}([]);
|
||||
//# sourceMappingURL=runtime-main.38573804.js.map
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB |
@ -1 +0,0 @@
|
||||
<?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>
|
Before Width: | Height: | Size: 297 B |
@ -1 +0,0 @@
|
||||
<?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>
|
Before Width: | Height: | Size: 808 B |
Before Width: | Height: | Size: 18 KiB |
4777
package-lock.json
generated
28
package.json
@ -4,35 +4,41 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.10.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.56",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@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": "16.8.0",
|
||||
"react-click-away-listener": "^1.4.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-scripts": "3.4.1",
|
||||
"styled-components": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^24.9.1",
|
||||
"@types/lodash": "^4.14.162",
|
||||
"@types/node": "^12.12.54",
|
||||
"@types/react": "^16.9.46",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/styled-components": "^5.1.2",
|
||||
"prettier": "^2.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.8.1",
|
||||
"@typescript-eslint/parser": "^4.8.1",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-config-react-app": "^6.0.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"prettier": "^2.2.0",
|
||||
"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"
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
@ -1,24 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pl">
|
||||
|
||||
<head>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/logo.svg" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using create-react-app" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo.svg" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
|
||||
<title>PlanNaPlan</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>Potrzebujesz włączyć JavaScript, żeby otworzyć tę aplikację</br>You need to enable JavaScript to run this
|
||||
app.</noscript>
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -1,25 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" width="200" height="200" viewBox="0 0 200 200">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #6d6e71;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3 {
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #f9ca24;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #1761a0;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path d="M22.000,156.000 L23.000,154.000 L24.000,153.000 L25.000,152.000 L26.000,151.000 L28.000,150.000 L33.000,150.000 L35.000,151.000 L36.000,152.000 L37.000,153.000 L38.000,154.000 L39.000,156.000 L39.000,161.000 L38.000,163.000 L37.000,164.000 L36.000,165.000 L35.000,166.000 L33.000,167.000 L28.000,167.000 L26.000,166.000 L25.000,165.000 L24.000,164.000 L23.000,163.000 L22.000,161.000 L22.000,156.000 ZM26.000,156.000 L27.000,155.000 L28.000,154.000 L33.000,154.000 L34.000,155.000 L35.000,156.000 L35.000,161.000 L34.000,162.000 L33.000,163.000 L28.000,163.000 L27.000,162.000 L26.000,161.000 L26.000,156.000 Z" class="cls-1"/>
|
||||
<path d="M10.000,75.000 L100.000,131.000 L190.000,75.000 L100.000,20.000 L10.000,75.000 Z" class="cls-2"/>
|
||||
<path d="M84.000,52.000 L86.000,54.000 L32.000,89.000 L32.000,153.000 L29.000,153.000 L29.000,88.000 L84.000,52.000 Z" class="cls-1"/>
|
||||
<path d="M45.000,102.000 L45.000,143.000 L100.000,180.000 L155.000,143.000 L155.000,102.000 L100.000,136.000 L45.000,102.000 Z" class="cls-3"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,13 +0,0 @@
|
||||
{
|
||||
"short_name": "PlanNaPlan",
|
||||
"name": "PlanNaPlan",
|
||||
"icons": [
|
||||
{
|
||||
"src": "logo.svg"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
1
src/assets/history.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Capa_1" enable-background="new 0 0 551.13 551.13" height="512" viewBox="0 0 551.13 551.13" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m275.531 172.228-.05 120.493c0 4.575 1.816 8.948 5.046 12.177l86.198 86.181 24.354-24.354-81.153-81.136.05-113.361z"/><path d="m310.011 34.445c-121.23 0-221.563 90.033-238.367 206.674h-71.644l86.114 86.114 86.114-86.114h-65.78c16.477-97.589 101.355-172.228 203.563-172.228 113.966 0 206.674 92.707 206.674 206.674s-92.707 206.674-206.674 206.674c-64.064 0-123.469-28.996-162.978-79.555l-27.146 21.192c46.084 58.968 115.379 92.808 190.124 92.808 132.955 0 241.119-108.181 241.119-241.119s-108.164-241.119-241.119-241.12z"/></svg>
|
After Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
1
src/assets/plan.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m6 0h-5c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1h5c.552 0 1-.448 1-1v-5c0-.552-.448-1-1-1z"/><path d="m15.5 6v-5c0-.552-.448-1-1-1h-5c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1h5c.552 0 1-.448 1-1z"/><path d="m15.5 9.5c0-.552-.448-1-1-1h-6-1.5-6c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1h13.5c.552 0 1-.448 1-1z"/><path d="m23 0h-5c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1h5c.552 0 1-.448 1-1v-5c0-.552-.448-1-1-1z"/><path d="m0 18v5c0 .552.448 1 1 1h13.5c.552 0 1-.448 1-1v-5c0-.552-.448-1-1-1h-13.5c-.552 0-1 .448-1 1z"/><path d="m18 15.5h5c.552 0 1-.448 1-1v-5c0-.552-.448-1-1-1h-5c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1z"/><path d="m18 24h5c.552 0 1-.448 1-1v-5c0-.552-.448-1-1-1h-5c-.552 0-1 .448-1 1v5c0 .552.448 1 1 1z"/></svg>
|
After Width: | Height: | Size: 846 B |
53
src/assets/statistics.svg
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 478 478" style="enable-background:new 0 0 478 478;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M119.5,187.75H17.1c-9.4,0-17,7.6-17.1,17.1v256c0,9.5,7.7,17.1,17.1,17.1h102.4c9.5,0,17.1-7.7,17.1-17.1v-256
|
||||
C136.6,195.35,128.9,187.75,119.5,187.75z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M290.2,0.05H187.8c-9.4,0-17.1,7.6-17.1,17v443.8c0,9.5,7.7,17.1,17.1,17.1h102.4c9.5,0,17.1-7.7,17.1-17.1V17.15
|
||||
C307.3,7.65,299.6,0.05,290.2,0.05z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M460.9,136.55H358.5c-9.5,0-17.1,7.6-17.1,17.1v307.2c0,9.5,7.7,17.1,17.1,17.1h102.4c9.5,0,17.1-7.7,17.1-17.1v-307.2
|
||||
C478,144.15,470.3,136.55,460.9,136.55z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
133
src/components/Admin.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import React, { useState, MouseEvent } from 'react';
|
||||
import styled from 'styled-components/macro';
|
||||
import Plan from '../assets/plan.svg';
|
||||
import History from '../assets/history.svg';
|
||||
import Statistics from '../assets/statistics.svg';
|
||||
import { Scheduler } from './Scheduler';
|
||||
import { Rightbar } from './Rightbar';
|
||||
|
||||
const LeftSide = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
background-color: white;
|
||||
`;
|
||||
|
||||
const Wrap = styled.div`
|
||||
display: flex;
|
||||
height: calc(100vh - 120px);
|
||||
background-color: #eceef4;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
flex: 12;
|
||||
display: flex;
|
||||
height: calc(100vh - 120px);
|
||||
background-color: #eceef4;
|
||||
`;
|
||||
|
||||
interface LeftPanelElement {
|
||||
isCurrentTab: boolean;
|
||||
}
|
||||
|
||||
const LeftPanelElement = styled.div<LeftPanelElement>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
//box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.75);
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
box-shadow: ${({ isCurrentTab }) => (isCurrentTab === true ? `inset 0px 0px 26px 0px rgba(0,0,0,0.55)` : '')};
|
||||
border-bottom: 1px solid #979797;
|
||||
`;
|
||||
|
||||
const HistoryDiv = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
border-radius: 5px;
|
||||
height: calc(100vh - 120px);
|
||||
background-color: red;
|
||||
`;
|
||||
|
||||
const StatsDiv = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-left: 20px;
|
||||
border-radius: 5px;
|
||||
height: calc(100vh - 120px);
|
||||
background-color: blue;
|
||||
`;
|
||||
|
||||
const LogoWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 2;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
const Text = styled.div`
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 5rem;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 40px;
|
||||
margin: 5px;
|
||||
`;
|
||||
|
||||
export const Admin = () => {
|
||||
const [currentTab, setCurrentTab] = useState<null | number>(null);
|
||||
|
||||
const handleClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
setCurrentTab(Number(e.currentTarget.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrap>
|
||||
<LeftSide>
|
||||
<LeftPanelElement id={'1'} isCurrentTab={currentTab === 1} onClick={handleClick}>
|
||||
<Icon alt="profile" src={Plan} />
|
||||
Pokaż plan
|
||||
</LeftPanelElement>
|
||||
<LeftPanelElement id={'2'} isCurrentTab={currentTab === 2} onClick={handleClick}>
|
||||
<Icon alt="history" src={History} />
|
||||
Historia Zmian
|
||||
</LeftPanelElement>
|
||||
<LeftPanelElement id={'3'} isCurrentTab={currentTab === 3} onClick={handleClick}>
|
||||
<Icon alt="statistics" src={Statistics} />
|
||||
Statystyki
|
||||
</LeftPanelElement>
|
||||
</LeftSide>
|
||||
<Wrapper>
|
||||
{currentTab === 1 ? (
|
||||
<>
|
||||
<Scheduler />
|
||||
<Rightbar />
|
||||
</>
|
||||
) : currentTab === 2 ? (
|
||||
<HistoryDiv />
|
||||
) : currentTab === 3 ? (
|
||||
<StatsDiv />
|
||||
) : (
|
||||
<LogoWrapper>
|
||||
<Logo alt="logo" src="https://plannaplan.pl/img/logo.svg" />
|
||||
<Text> plan na plan </Text>
|
||||
</LogoWrapper>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Wrap>
|
||||
);
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Topbar from './Topbar';
|
||||
import { Transfer } from './Transfer';
|
||||
import { Admin } from './Admin';
|
||||
import { Scheduler } from './Scheduler';
|
||||
import { Rightbar } from './Rightbar';
|
||||
import styled from 'styled-components';
|
||||
@ -8,8 +9,10 @@ import styled from 'styled-components';
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
height: calc(100vh - 80px);
|
||||
background-color: #ECEEF4;
|
||||
padding: 20px;
|
||||
background-color: #eceef4;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 20px;
|
||||
`;
|
||||
|
||||
export const App = () => {
|
||||
@ -24,6 +27,7 @@ export const App = () => {
|
||||
<Topbar handleTransfer={handleTransfer} />
|
||||
<Transfer isOpen={isOpenTransfer} handleClose={handleTransfer} />
|
||||
<Wrapper>
|
||||
{/* <Admin/> */}
|
||||
<Scheduler />
|
||||
<Rightbar />
|
||||
</Wrapper>
|
||||
|
@ -1,17 +1,18 @@
|
||||
import React, { useState, useContext, MouseEvent } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
import { ReactComponent as Expand } from '../assets/expand.svg';
|
||||
import { Course, Group } from '../types/index';
|
||||
import { Course, Group, GroupType } from '../types/index';
|
||||
import { coursesContext } from '../contexts/CoursesProvider';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { ReactComponent as Bin } from '../assets/bin.svg';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
const CourseCardWrapper = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 40px;
|
||||
background-color: rgb(100, 181, 246);
|
||||
background-color: #b5d2e0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
@ -22,19 +23,18 @@ const CourseCardWrapper = styled.div`
|
||||
box-shadow: 9px 9px 8px -2px rgba(0, 0, 0, 0.59);
|
||||
`;
|
||||
|
||||
|
||||
const TitleWrapper = styled.div`
|
||||
font-size: 14px;
|
||||
font-weight: 550;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
`
|
||||
padding: 10px 10px 10px 2px;
|
||||
`;
|
||||
|
||||
const BinIcon = styled(Bin)`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
max-width: 20px;
|
||||
min-width: 20px;
|
||||
const BinIcon = styled(DeleteIcon)`
|
||||
max-width: 30px;
|
||||
min-width: 30px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
fill: white;
|
||||
@ -51,37 +51,63 @@ const CourseName = styled.div`
|
||||
const ClassGroupStyled = styled.div`
|
||||
position: relative;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
padding-bottom: 5px;
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
background-color: #9ed3ff;
|
||||
}
|
||||
:last-child {
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ExpandIconProps {
|
||||
isSelected: boolean;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
const ExpandIcon = styled(Expand) <ExpandIconProps>`
|
||||
export const ExpandIcon = styled(Expand)<ExpandIconProps>`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
max-width: 20px;
|
||||
min-width: 20px;
|
||||
transition: 0.2s;
|
||||
transform: ${({ isSelected }) => (isSelected ? 'scaleY(-1);' : 'scaleY(1);')};
|
||||
transform: ${({ selected }) => (selected ? 'scaleY(-1);' : 'scaleY(1);')};
|
||||
`;
|
||||
|
||||
const TypeClass = styled.div`
|
||||
type StyledGroupTypeProps = {
|
||||
groupType: GroupType;
|
||||
};
|
||||
|
||||
const StyledGroupType = styled.div<StyledGroupTypeProps>`
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
border-radius: 15px;
|
||||
background-color: #00506b;
|
||||
border: 2px solid;
|
||||
background-color: ${({ groupType }) => (groupType === 'CLASS' ? '#FFDC61' : '#9ed3ff')};
|
||||
border: 2px solid white;
|
||||
min-width: 45px;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
`;
|
||||
|
||||
const FlexboxWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
type FlexItemProps = {
|
||||
justifyContent?: string;
|
||||
};
|
||||
|
||||
const FlexItem = styled.div<FlexItemProps>`
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
${({ justifyContent }) =>
|
||||
justifyContent &&
|
||||
css`
|
||||
justify-content: ${justifyContent};
|
||||
`}
|
||||
`;
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@ -89,51 +115,100 @@ const useStyles = makeStyles({
|
||||
maxHeight: '244px',
|
||||
overflowY: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: '0.4em',
|
||||
width: '0.3em',
|
||||
borderStyle: 'none',
|
||||
},
|
||||
'&::-webkit-scrollbar-track': {
|
||||
'-webkit-box-shadow': 'inset 0 0 6px rgba(1,0,0,0.1)',
|
||||
borderRadius: '10px',
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
borderRadius: '10px',
|
||||
backgroundColor: '#d4b851',
|
||||
outline: '1px solid slategrey',
|
||||
backgroundColor: '#4b4b4b',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
interface CourseCardProps {
|
||||
course: Course;
|
||||
}
|
||||
|
||||
export const CourseCard = ({ course }: CourseCardProps) => {
|
||||
const classes = useStyles();
|
||||
const { addGroup, deleteFromBasket } = useContext(coursesContext)!;
|
||||
const {
|
||||
hoveredGroup,
|
||||
changeGroupInBasket,
|
||||
deleteFromBasket,
|
||||
selectBasketCourseGroups,
|
||||
changeHoveredGroup,
|
||||
} = useContext(coursesContext)!;
|
||||
const [isSelected, setSelected] = useState(false);
|
||||
const groups = course.lectures === undefined ? course.classes : [...course.lectures, ...course.classes];
|
||||
|
||||
const onGroupClick = (group: Group, id: number) => addGroup(group, id);
|
||||
const groups = [...course.lectures!, ...course.classes!];
|
||||
const basketCourseGroups = useMemo(() => selectBasketCourseGroups(course.id), []);
|
||||
const [previous, setPrevious] = useState(basketCourseGroups);
|
||||
// console.log('lecture is: ', courseLecture);
|
||||
// console.log('class is: ', courseClasses);
|
||||
const onGroupClick = (group: Group, courseId: number) => {
|
||||
setPrevious((prev) => (group.type === GroupType.CLASS ? { ...prev, classes: group } : { ...prev, lecture: group }));
|
||||
changeGroupInBasket(group, courseId);
|
||||
};
|
||||
|
||||
return (
|
||||
<CourseCardWrapper>
|
||||
<TitleWrapper>
|
||||
<BinIcon onClick={() => deleteFromBasket(course.id)}></BinIcon>
|
||||
<TitleWrapper onClick={() => setSelected(!isSelected)}>
|
||||
<BinIcon
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteFromBasket(course.id);
|
||||
setSelected(false);
|
||||
}}
|
||||
></BinIcon>
|
||||
<CourseName onClick={() => setSelected(!isSelected)}>{course.name}</CourseName>
|
||||
<ExpandIcon onClick={() => setSelected(!isSelected)} isSelected={isSelected} />
|
||||
<ExpandIcon onClick={() => setSelected(!isSelected)} selected={isSelected} />
|
||||
</TitleWrapper>
|
||||
<Collapse className={classes.expanded} in={isSelected} timeout="auto" unmountOnExit>
|
||||
{groups
|
||||
.sort((a, b) => b.type.localeCompare(a.type))
|
||||
.map((group, index) => (
|
||||
<ClassGroupStyled key={index} onClick={() => onGroupClick(group, course.id)}>
|
||||
<TypeClass>{group.type === 'CLASS' ? 'Ćw.' : 'Wyk.'}</TypeClass>
|
||||
<p>
|
||||
{group.time} {group.room} <br></br> {group.lecturer}
|
||||
</p>
|
||||
</ClassGroupStyled>
|
||||
))}
|
||||
{groups.map((group: Group, index) => (
|
||||
<ClassGroupStyled
|
||||
key={index}
|
||||
onClick={() => onGroupClick(group, course.id)}
|
||||
onMouseEnter={() => {
|
||||
if (group.type === GroupType.CLASS) {
|
||||
changeGroupInBasket(group, course.id);
|
||||
// setTimeout(()=> { changeHoveredGroup(courseClasses)},[500])
|
||||
}
|
||||
if (group.type === GroupType.LECTURE) {
|
||||
changeGroupInBasket(group, course.id);
|
||||
// setTimeout(()=> { changeHoveredGroup(courseLecture)},[500])
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (hoveredGroup) {
|
||||
if (hoveredGroup.type === GroupType.CLASS && previous.classes !== undefined) {
|
||||
changeGroupInBasket(previous.classes, course.id);
|
||||
}
|
||||
if (hoveredGroup.type === GroupType.LECTURE && previous.lecture !== undefined) {
|
||||
changeGroupInBasket(previous.lecture, course.id);
|
||||
}
|
||||
changeHoveredGroup(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StyledGroupType groupType={group.type}>{group.type === 'CLASS' ? 'ĆW' : 'WYK'}</StyledGroupType>
|
||||
<FlexboxWrapper>
|
||||
{group.lecturer.replace('UAM', '').length >= 32 ? (
|
||||
<FlexItem style={{ justifyContent: 'center', marginLeft: '40px' }}>
|
||||
{group.lecturer.replace('UAM', '')}
|
||||
</FlexItem>
|
||||
) : (
|
||||
<FlexItem style={{ justifyContent: 'center', marginLeft: '10px' }}>
|
||||
{group.lecturer.replace('UAM', '')}
|
||||
</FlexItem>
|
||||
)}
|
||||
<FlexItem style={{ justifyContent: 'center', margin: '0 50px' }}>
|
||||
<span>{/*group.time*/}</span> <span> Sala: {group.room}</span>
|
||||
</FlexItem>
|
||||
</FlexboxWrapper>
|
||||
</ClassGroupStyled>
|
||||
))}
|
||||
</Collapse>
|
||||
</CourseCardWrapper>
|
||||
);
|
||||
|
@ -1,31 +1,30 @@
|
||||
import React, { useState, useContext, useEffect, MouseEvent, forwardRef } from 'react';
|
||||
import React, { useState, useContext, useEffect, MouseEvent, useMemo } from 'react';
|
||||
import { coursesContext } from '../contexts/CoursesProvider';
|
||||
import { Course } from '../types';
|
||||
import styled from 'styled-components';
|
||||
|
||||
|
||||
|
||||
const DropdownContainer = styled.div`
|
||||
position: relative;
|
||||
z-index: 99999999;
|
||||
max-height: 420px;
|
||||
max-height: 396px;
|
||||
border-radius: 3px;
|
||||
overflow-y: auto;
|
||||
opacity: 0.97;
|
||||
box-shadow: 0.05em 0.2em 0.6em rgba(0, 0, 0, 0.2);
|
||||
scroll-snap-type: y mandatory;
|
||||
scroll-behavior: smooth;
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f2f4f7;
|
||||
border-radius: 10px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #f2f4f7;
|
||||
width: 5px;
|
||||
border-style: none;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background-color: black;
|
||||
border: 1px solid;
|
||||
background-color: #4b4b4b;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -33,7 +32,7 @@ const CourseContainer = styled.div`
|
||||
padding: 5px;
|
||||
padding-left: 20px;
|
||||
background-color: #f2f4f7;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
scroll-snap-align: end;
|
||||
:hover {
|
||||
@ -48,27 +47,22 @@ interface DropdownProps {
|
||||
handleCloseDropdown: () => void;
|
||||
}
|
||||
|
||||
export const Dropdown = forwardRef(({ open, input, handleCloseDropdown }: DropdownProps, ref: any) => {
|
||||
//courses - choosenCourses
|
||||
export const Dropdown = ({ open, input, handleCloseDropdown }: DropdownProps) => {
|
||||
const { courses, selectBasketNames, addCourseToBasket } = useContext(coursesContext)!;
|
||||
const basketNames = useMemo(() => selectBasketNames(), [selectBasketNames]);
|
||||
const [filteredCourses, setFilteredCourses] = useState<Array<Course>>([]);
|
||||
|
||||
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]);
|
||||
const onCourseClick = (event: MouseEvent) => {
|
||||
const target = event.currentTarget;
|
||||
if (target.id && target.textContent) {
|
||||
const course = filteredCourses.find(({ id }) => id.toString() === target.id)!;
|
||||
addCourseToBasket(course);
|
||||
handleCloseDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const filterCourses = (input: string) => {
|
||||
const choosenCoursesNames = basket.map(({ name }) => name.trim());
|
||||
const filteredCourses = courses.filter(
|
||||
({ name }) =>
|
||||
name
|
||||
@ -80,24 +74,12 @@ export const Dropdown = forwardRef(({ open, input, handleCloseDropdown }: Dropdo
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, ''),
|
||||
) && !choosenCoursesNames.includes(name),
|
||||
) && !basketNames.includes(name),
|
||||
);
|
||||
setFilteredCourses(filteredCourses);
|
||||
};
|
||||
console.log("filtering courses");
|
||||
filterCourses(input);
|
||||
}, [open, input, basket]);
|
||||
|
||||
const onCourseClick = async (event: MouseEvent) => {
|
||||
const target = event.currentTarget;
|
||||
if (target.id && target.textContent) {
|
||||
const course = filteredCourses.find(({ id }) => id.toString() === target.id)!;
|
||||
console.log('added course is');
|
||||
console.log(course);
|
||||
addToBasket(course);
|
||||
handleCloseDropdown();
|
||||
}
|
||||
};
|
||||
}, [basketNames, courses, input]);
|
||||
|
||||
return (
|
||||
<DropdownContainer>
|
||||
@ -112,4 +94,4 @@ export const Dropdown = forwardRef(({ open, input, handleCloseDropdown }: Dropdo
|
||||
)}
|
||||
</DropdownContainer>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -12,7 +12,7 @@ export const Profile = ({ anchorEl, handleClose }: ProfileProps) => {
|
||||
|
||||
return (
|
||||
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||
<MenuItem>Profil</MenuItem>
|
||||
{/* <MenuItem>Profil</MenuItem> */}
|
||||
<MenuItem onClick={logout}>Wyloguj</MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
|
@ -2,28 +2,24 @@ import React, { useContext } from 'react';
|
||||
import { CourseCard } from './CourseCard';
|
||||
import { coursesContext } from '../contexts/CoursesProvider';
|
||||
import styled from 'styled-components';
|
||||
import { debounce } from 'lodash';
|
||||
import { debounce } from '../utils/index';
|
||||
|
||||
const RightbarStyled = styled.div`
|
||||
padding-top: 10px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
const RightbarWrapper = styled.div`
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
width: 350px;
|
||||
overflow-y: scroll;
|
||||
::-webkit-scrollbar-track {
|
||||
border-radius: 10px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
background-color: #f5f5f5;
|
||||
width: 5px;
|
||||
border-style: none;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 10px;
|
||||
background-color: black;
|
||||
border: 1px solid;
|
||||
background-color: #4b4b4b;
|
||||
}
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
@ -34,36 +30,34 @@ const SaveButton = styled.div`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
background-color: #417cab;
|
||||
background-color: #43a047;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
margin-bottom: 10px;
|
||||
&:hover {
|
||||
color: white;
|
||||
color: #ffffff;
|
||||
box-shadow: 0px 5px 4px 0px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
box-shadow: 6px 6px 6px -2px rgba(0, 0, 0, 0.59);
|
||||
|
||||
&:active {
|
||||
background-color: #54c457;
|
||||
}
|
||||
|
||||
box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.24);
|
||||
`;
|
||||
|
||||
export const Rightbar = () => {
|
||||
const { courses, basket, saveBasket } = useContext(coursesContext)!;
|
||||
|
||||
const getBasketGroups = () => {
|
||||
const names = basket.map(({ name }) => name);
|
||||
return courses.filter(({ name }) => names.includes(name));
|
||||
};
|
||||
|
||||
const filteredCourses = getBasketGroups();
|
||||
const { selectBasketCourses, saveBasket } = useContext(coursesContext)!;
|
||||
|
||||
const basketCourses = selectBasketCourses();
|
||||
const handleSave = debounce(() => saveBasket(), 500);
|
||||
|
||||
//need to insert student name from db and course maybe based on current time or from db too
|
||||
return (
|
||||
<RightbarStyled>
|
||||
<RightbarWrapper>
|
||||
<SaveButton onClick={handleSave}>ZAPISZ</SaveButton>
|
||||
{filteredCourses.map((course, index) => (
|
||||
<CourseCard course={course} key={index} />
|
||||
{basketCourses.map((course) => (
|
||||
<CourseCard course={course} key={course.id} />
|
||||
))}
|
||||
</RightbarStyled>
|
||||
</RightbarWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, MouseEvent, useRef, useCallback, useLayoutEffect } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { SchedulerEvents } from './SchedulerEvents';
|
||||
import { days, hours } from '../constants/index';
|
||||
@ -11,6 +11,7 @@ const SchedulerWrapper = styled.div`
|
||||
padding: 10px 40px 25px 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -42,9 +43,9 @@ interface TableCellProps {
|
||||
}
|
||||
|
||||
const TableCell = styled.div<TableCellProps>`
|
||||
border-width: ${({ isHourColumn }) => !isHourColumn && '2px'};
|
||||
border-width: ${({ isHourColumn }) => !isHourColumn && '1px'};
|
||||
border-style: ${({ isHourColumn }) => !isHourColumn && 'none solid dotted none'};
|
||||
border-color: rgb(242, 243, 245);
|
||||
border-color: rgb(235, 235, 235);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: ${({ isHourColumn }) => (isHourColumn ? 'flex-end' : 'center')};
|
||||
@ -55,7 +56,7 @@ const TableCell = styled.div<TableCellProps>`
|
||||
user-select: none;
|
||||
border-collapse: collapse;
|
||||
:nth-child(2) {
|
||||
border-left: 2px solid rgb(242, 243, 245);
|
||||
border-left: 1px solid rgb(235, 235, 235);
|
||||
}
|
||||
font-weight: bold;
|
||||
`;
|
||||
@ -63,18 +64,13 @@ const TableCell = styled.div<TableCellProps>`
|
||||
export const Scheduler = () => {
|
||||
const cellRef = useRef<HTMLDivElement>(null);
|
||||
const [cellWidth, setCellWidth] = useState(0);
|
||||
const [cellTop, setCellTop] = useState(0);
|
||||
const [cellHeight, setCellHeight] = useState(0);
|
||||
|
||||
console.log('cell height: ', cellHeight);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (cellRef.current) {
|
||||
setCellWidth(cellRef.current.getBoundingClientRect().width);
|
||||
setCellTop(cellRef.current.getBoundingClientRect().top);
|
||||
setCellHeight(cellRef.current.getBoundingClientRect().height);
|
||||
cellRef.current.style.backgroundColor = 'blue';
|
||||
}
|
||||
};
|
||||
handleResize();
|
||||
@ -83,52 +79,52 @@ export const Scheduler = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SchedulerWrapper>
|
||||
<TableHead>
|
||||
{days.map((day, indexCell) =>
|
||||
indexCell === 0 ? (
|
||||
<TableCell isHourColumn={true} key={indexCell}>
|
||||
{day}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell style={{ borderStyle: 'none none solid none' }} key={indexCell}>
|
||||
{day}
|
||||
</TableCell>
|
||||
),
|
||||
)}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{hours.map((hour, indexRow) => (
|
||||
<TableRow key={indexRow}>
|
||||
{[hour, '', '', '', '', ''].map((value, indexCell) =>
|
||||
indexCell === 0 ? (
|
||||
<TableCell isHourColumn={true} cellHeight={cellHeight} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow === 0 && indexCell === 1 ? (
|
||||
<TableCell ref={cellRef} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow === 23 ? (
|
||||
<TableCell style={{ borderBottom: '2px solid rgb(242, 243, 245)' }} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexCell === 5 ? (
|
||||
<TableCell key={`${indexRow}${indexCell}`}>{value}</TableCell>
|
||||
) : indexRow % 2 !== 0 ? (
|
||||
<TableCell style={{ borderBottom: '2px solid rgb(242, 243, 245)' }} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell key={`${indexRow}${indexCell}`}>{value}</TableCell>
|
||||
),
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
<SchedulerEvents cellTop={cellTop} cellWidth={cellWidth} cellHeight={cellHeight} />
|
||||
</TableBody>
|
||||
</SchedulerWrapper>
|
||||
</>
|
||||
<SchedulerWrapper>
|
||||
<TableHead>
|
||||
{days.map((day, indexCell) =>
|
||||
indexCell === 0 ? (
|
||||
<TableCell isHourColumn={true} key={indexCell}>
|
||||
{day}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell style={{ borderStyle: 'none none solid none' }} key={indexCell}>
|
||||
{day}
|
||||
</TableCell>
|
||||
),
|
||||
)}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{hours.map((hour, indexRow) => (
|
||||
<TableRow key={indexRow}>
|
||||
{[hour, '', '', '', '', ''].map((value, indexCell) =>
|
||||
indexCell === 0 ? (
|
||||
<TableCell isHourColumn={true} cellHeight={cellHeight} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow === 0 && indexCell === 1 ? (
|
||||
<TableCell ref={cellRef} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow === 23 ? (
|
||||
<TableCell style={{ borderBottom: '1px solid rgb(235, 235, 235)' }} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow === 5 ? (
|
||||
<TableCell style={{ borderBottom: '1px solid rgb(235, 235, 235)' }} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : indexRow % 2 !== 0 ? (
|
||||
<TableCell style={{ borderBottom: '1px solid rgb(235, 235, 235)' }} key={`${indexRow}${indexCell}`}>
|
||||
{value}
|
||||
</TableCell>
|
||||
) : (
|
||||
<TableCell key={`${indexRow}${indexCell}`}>{value}</TableCell>
|
||||
),
|
||||
)}
|
||||
</TableRow>
|
||||
))}
|
||||
<SchedulerEvents cellWidth={cellWidth} cellHeight={cellHeight} />
|
||||
</TableBody>
|
||||
</SchedulerWrapper>
|
||||
);
|
||||
};
|
||||
|
@ -1,57 +1,26 @@
|
||||
import React, { useContext, useEffect, useState, MouseEvent } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { SchedulerRow } from './SchedulerRow';
|
||||
import { coursesContext } from '../contexts/CoursesProvider';
|
||||
import { Group, Basket } from '../types';
|
||||
|
||||
import { selectGroupsToShow } from '../utils/index';
|
||||
import { ROWS_COUNT } from '../constants';
|
||||
interface SchedulerEventsProps {
|
||||
cellTop: number;
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
}
|
||||
|
||||
export const SchedulerEvents = ({ cellTop, cellWidth, cellHeight }: SchedulerEventsProps) => {
|
||||
const { basket } = useContext(coursesContext)!;
|
||||
console.log(`values: cellTop: ${cellTop}, cellWidth: ${cellWidth}, cellHeight: ${cellHeight}`);
|
||||
const [choosenGroupsMappedToEvents, setChoosenGroupsMappedToEvents] = useState<any>([]);
|
||||
export const SchedulerEvents = ({ cellWidth, cellHeight }: SchedulerEventsProps) => {
|
||||
const { selectSchedulerEvents } = useContext(coursesContext)!;
|
||||
|
||||
const groupTimeToEventRowMapping: { [time: string]: number } = {
|
||||
'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 classes = basket.map(({ classes, name }) => ({ ...classes, name })) as Array<Group & { name: string }>;
|
||||
const lectures = basket.map(({ lecture, name }) => ({ ...lecture, name })) as Array<Group & { name: string }>;
|
||||
const merged = [...classes, ...lectures];
|
||||
|
||||
//deleted if statement, maybe it is needed
|
||||
const groupsMapped = merged.map(({ id, day, lecturer, room, time, name, type }) => ({
|
||||
id,
|
||||
day,
|
||||
lecturer,
|
||||
room,
|
||||
eventRow: groupTimeToEventRowMapping[time],
|
||||
name,
|
||||
type,
|
||||
}));
|
||||
setChoosenGroupsMappedToEvents(groupsMapped);
|
||||
}
|
||||
mapGroupTimeToEventRow(basket);
|
||||
}, [basket]);
|
||||
const schedulerEvents = selectSchedulerEvents();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{[...Array(6)].map((_, index) => (
|
||||
{[...Array(ROWS_COUNT)].map((_, index) => (
|
||||
<SchedulerRow
|
||||
key={index}
|
||||
groups={choosenGroupsMappedToEvents.filter((group: any) => group.eventRow === index)}
|
||||
groups={selectGroupsToShow(schedulerEvents, index)}
|
||||
indexRow={index}
|
||||
cellTop={
|
||||
rowTop={
|
||||
index === 0
|
||||
? cellHeight / 2
|
||||
: index === 1
|
||||
|
@ -1,92 +1,163 @@
|
||||
import React, { MouseEvent, useState } from 'react';
|
||||
import { Group, GroupType } from '../types';
|
||||
import styled from 'styled-components/macro';
|
||||
import React, { Fragment, MouseEvent, useState, useEffect, useContext } from 'react';
|
||||
import { GroupType, SchedulerEvent } from '../types';
|
||||
import styled, { css } from 'styled-components/macro';
|
||||
import Popover from '@material-ui/core/Popover';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
|
||||
import { MONDAY_TO_FRIDAY } from '../constants';
|
||||
import { coursesContext } from '../contexts/CoursesProvider';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
popover: {
|
||||
pointerEvents: 'none',
|
||||
fontSize: '14px',
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(1),
|
||||
marginLeft: 5,
|
||||
textAlign: 'center',
|
||||
padding: '15px 15px 15px 15px',
|
||||
textAlign: 'left',
|
||||
lineHeight: `1 !important`,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
interface ClassesWrapperProps {
|
||||
const PopoverSpan = styled.span`
|
||||
font-weight: bold;
|
||||
margin-right: 2px;
|
||||
`;
|
||||
|
||||
interface SchedulerEventsWrapperProps {
|
||||
eventIndex: number;
|
||||
cellTop: number;
|
||||
rowTop: number;
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
}
|
||||
|
||||
const ClassesWrapper = styled.div<ClassesWrapperProps>`
|
||||
const SchedulerEventsWrapper = styled.div<SchedulerEventsWrapperProps>`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
top: ${({ cellTop }) => cellTop}px;
|
||||
left: ${({ cellWidth, eventIndex }) => (cellWidth * 1) / 5 + 15 + cellWidth * eventIndex}px;
|
||||
top: ${({ rowTop }) => rowTop}px;
|
||||
left: ${({ cellWidth, eventIndex }) => (cellWidth * 1) / 5 + 4 + cellWidth * eventIndex}px;
|
||||
width: ${({ cellWidth }) => cellWidth - 10}px;
|
||||
height: ${({ cellHeight }) => cellHeight * 3}px;
|
||||
z-index: 2;
|
||||
padding-left: 10px;
|
||||
`;
|
||||
|
||||
interface ClassesProps {
|
||||
interface SchedulerEventProps {
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
groupType: GroupType;
|
||||
isHovered: boolean;
|
||||
}
|
||||
|
||||
const Classes = styled.div<ClassesProps>`
|
||||
const StyledSchedulerEvent = styled.div<SchedulerEventProps>`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
z-index: 20000;
|
||||
font-size: 0.65vw;
|
||||
line-height: normal;
|
||||
border-radius: 10px;
|
||||
height: ${({ cellHeight }) => cellHeight * 3}px;
|
||||
width: ${({ cellWidth }) => (cellWidth * 3) / 4}px;
|
||||
margin-right: 5px;
|
||||
text-align: left;
|
||||
background-color: ${({ groupType }) => (groupType === 'CLASS' ? '#FFDC61' : '#9ed3ff')};
|
||||
box-shadow: 9px 9px 8px -2px rgba(0, 0, 0, 0.59);
|
||||
padding: 5px 5px 0 5px;
|
||||
text-align: center;
|
||||
background-color: ${({ groupType, isHovered }) => {
|
||||
if (isHovered) {
|
||||
return groupType === 'CLASS' ? '#ffefb5' : '#d4ecff';
|
||||
} else {
|
||||
return groupType === 'CLASS' ? '#FFDC61' : '#9ed3ff';
|
||||
}
|
||||
}};
|
||||
box-shadow: 3px 3px 3px 0px rgba(0, 0, 0, 0.75);
|
||||
`;
|
||||
|
||||
const threeStyles = () => {
|
||||
return css`
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 70px;
|
||||
`;
|
||||
};
|
||||
|
||||
type BoldParagraphProps = {
|
||||
isThree?: boolean;
|
||||
};
|
||||
|
||||
const BoldParagraph = styled.p<BoldParagraphProps>`
|
||||
overflow: hidden;
|
||||
flex: 3;
|
||||
${({ isThree }) => isThree && threeStyles}
|
||||
`;
|
||||
|
||||
const ClassWrap = styled.div`
|
||||
font-weight: 700;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: normal;
|
||||
`;
|
||||
|
||||
const TextWrapper = styled.div`
|
||||
flex: 1;
|
||||
width: inherit;
|
||||
padding: 0 3px 5px 3px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
interface SchedulerRowProps {
|
||||
groups: Array<Group & { name: string }>;
|
||||
groups: Array<SchedulerEvent>;
|
||||
indexRow: number;
|
||||
cellTop: number;
|
||||
rowTop: number;
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
}
|
||||
|
||||
export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth, cellHeight }: SchedulerRowProps) => {
|
||||
const getGroupsPerDay = (groups: Array<SchedulerEvent>) => {
|
||||
const groupsPerDay: any = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 };
|
||||
for (const group of groups) {
|
||||
groupsPerDay[group.day]++;
|
||||
}
|
||||
return groupsPerDay;
|
||||
};
|
||||
|
||||
export const SchedulerRow = ({ groups, indexRow, rowTop, cellWidth, cellHeight }: SchedulerRowProps) => {
|
||||
const { hoveredGroup } = useContext(coursesContext)!;
|
||||
const classes = useStyles();
|
||||
const groupsPerDay = getGroupsPerDay(groups);
|
||||
const [anchorEl, setAnchorEl] = React.useState<HTMLDivElement | null>(null);
|
||||
const [popoverId, setPopoverId] = useState<string | null>(null);
|
||||
|
||||
//looks weird
|
||||
const handlePopoverOpen = (event: MouseEvent<HTMLDivElement, globalThis.MouseEvent>) => {
|
||||
console.log('I was clicked!!!!');
|
||||
setAnchorEl(event.currentTarget);
|
||||
setPopoverId(event.currentTarget.id);
|
||||
};
|
||||
|
||||
const handlePopoverClose = () => {
|
||||
setAnchorEl(null);
|
||||
const handlePopoverClose = (e: MouseEvent<any>) => {
|
||||
console.log('current target:', e.currentTarget);
|
||||
console.log(' target:', e.target);
|
||||
setPopoverId(null);
|
||||
setAnchorEl(null);
|
||||
console.log('click awayyy');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log('anchorEl: ', anchorEl);
|
||||
}, [anchorEl]);
|
||||
const open = Boolean(anchorEl);
|
||||
const id = open ? 'simple-popover' : undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{[...Array(5)].map((_, eventIndex) => (
|
||||
<ClassesWrapper
|
||||
{[...Array(MONDAY_TO_FRIDAY)].map((_, eventIndex) => (
|
||||
<SchedulerEventsWrapper
|
||||
eventIndex={eventIndex}
|
||||
cellTop={cellTop}
|
||||
rowTop={rowTop}
|
||||
cellWidth={cellWidth}
|
||||
cellHeight={cellHeight}
|
||||
key={eventIndex}
|
||||
@ -95,27 +166,35 @@ export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth, cellHeight
|
||||
{groups.map(
|
||||
(group, index) =>
|
||||
group.day === eventIndex && (
|
||||
<>
|
||||
<Classes
|
||||
onClick={() => {
|
||||
console.log('group: ', group);
|
||||
}}
|
||||
<Fragment key={index}>
|
||||
<StyledSchedulerEvent
|
||||
aria-describedby={id}
|
||||
isHovered={group.id === hoveredGroup?.id}
|
||||
groupType={group.type}
|
||||
cellWidth={cellWidth}
|
||||
cellHeight={cellHeight}
|
||||
id={`eventRow${indexRow}eventCol${eventIndex}${index}`}
|
||||
key={index}
|
||||
aria-owns={open ? `mouse-over-popover` : undefined}
|
||||
aria-haspopup="true"
|
||||
onMouseEnter={(e) => handlePopoverOpen(e)}
|
||||
onMouseLeave={handlePopoverClose}
|
||||
onClick={(e) => handlePopoverOpen(e)}
|
||||
>
|
||||
<div>
|
||||
<p style={{ fontWeight: 700 }}>{groups[index].name}</p>
|
||||
<p>{groups[index].room}</p>
|
||||
</div>
|
||||
</Classes>
|
||||
<ClassWrap>
|
||||
<BoldParagraph isThree={groupsPerDay[group.day] >= 3}>{groups[index].name}</BoldParagraph>
|
||||
{groupsPerDay[group.day] < 3 ? (
|
||||
<TextWrapper>
|
||||
<div>{`${groups[index].time[0]}-${groups[index].time[1]}`}</div>
|
||||
<div>3/{groups[index].capacity}</div>
|
||||
</TextWrapper>
|
||||
) : (
|
||||
<TextWrapper style={{ flexDirection: 'column' }}>
|
||||
<div style={{ alignSelf: 'flex-end' }}>3/{groups[index].capacity}</div>
|
||||
</TextWrapper>
|
||||
)}
|
||||
</ClassWrap>
|
||||
</StyledSchedulerEvent>
|
||||
<Popover
|
||||
id={`mouse-over-popover`}
|
||||
id={id}
|
||||
className={classes.popover}
|
||||
classes={{
|
||||
paper: classes.paper,
|
||||
@ -133,16 +212,34 @@ export const SchedulerRow = ({ groups, indexRow, cellTop, cellWidth, cellHeight
|
||||
onClose={handlePopoverClose}
|
||||
disableRestoreFocus
|
||||
>
|
||||
<Typography>
|
||||
<p>{groups[index].name}</p>
|
||||
<p>{groups[index].lecturer}</p>
|
||||
<p>{groups[index].room}</p>
|
||||
</Typography>
|
||||
<div
|
||||
style={{ display: 'flex', flexDirection: 'column', zIndex: 20000 }}
|
||||
onClick={() => {
|
||||
console.log('XDD');
|
||||
}}
|
||||
>
|
||||
<p style={{ margin: '7px 0 7px 0', fontWeight: 'bold' }}>{groups[index].name}</p>
|
||||
<p style={{ margin: '2px 0 2px 0' }}>
|
||||
<PopoverSpan>Prowadzący:</PopoverSpan> {groups[index].lecturer}
|
||||
</p>
|
||||
<p style={{ margin: '2px 0 2px 0' }}>
|
||||
<PopoverSpan>Sala zajęć</PopoverSpan>: {groups[index].room}
|
||||
</p>
|
||||
<p style={{ margin: '2px 0 2px 0' }}>
|
||||
<PopoverSpan>Kod przedmiotu: </PopoverSpan>ACB129
|
||||
</p>
|
||||
<p style={{ margin: '2px 0 2px 0' }}>
|
||||
<PopoverSpan>Kod grupy: </PopoverSpan>FVJ753
|
||||
</p>
|
||||
<p style={{ margin: '2px 0 2px 0' }}>
|
||||
<PopoverSpan>Punkty ECTS:</PopoverSpan> 2
|
||||
</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
</ClassesWrapper>
|
||||
</SchedulerEventsWrapper>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
109
src/components/SelectMenu.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { ClickAwayListener } from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ExpandIcon } from './CourseCard';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
min-width:130px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: #f1f2f5;
|
||||
color: black;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
padding-left: 15px;
|
||||
cursor: pointer;
|
||||
border-top-left-radius: 6px;
|
||||
border-bottom-left-radius: 6px;
|
||||
user-select: none;
|
||||
`;
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
width:100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
const HeaderTitle = styled.div`
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
`;
|
||||
const List = styled.ul`
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin-left: -15px;
|
||||
background-color: #f2f4f7;
|
||||
`;
|
||||
const ListItem = styled.li`
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 16px;
|
||||
user-select: none;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 15px;
|
||||
padding-right: 37px;
|
||||
font-weight: 400;
|
||||
:hover {
|
||||
background-color: #eceef4;
|
||||
}
|
||||
:first-child{
|
||||
margin-top:10px;
|
||||
}
|
||||
`;
|
||||
const ExpandIconSelect = styled(ExpandIcon)`
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
`;
|
||||
export const SelectMenu = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState('przedmioty');
|
||||
return (
|
||||
<ClickAwayListener
|
||||
onClickAway={() => {
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
<Wrapper>
|
||||
<Header
|
||||
onClick={() => {
|
||||
console.log('clicked');
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
<HeaderTitle>{selectedOption}</HeaderTitle>
|
||||
<ExpandIconSelect selected={isOpen} />
|
||||
</Header>
|
||||
{isOpen && (
|
||||
<List>
|
||||
<ListItem
|
||||
onClick={() => {
|
||||
setSelectedOption('przedmioty');
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
przedmioty
|
||||
</ListItem>
|
||||
<ListItem
|
||||
onClick={() => {
|
||||
setSelectedOption('studenci');
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
studenci
|
||||
</ListItem>
|
||||
</List>
|
||||
)}
|
||||
</Wrapper>
|
||||
</ClickAwayListener>
|
||||
);
|
||||
};
|
@ -1,6 +1,4 @@
|
||||
import React, { useState, MouseEvent, ChangeEvent, useEffect } from 'react';
|
||||
import Transfer from '../assets/transfer.png';
|
||||
import Search from '../assets/search.svg';
|
||||
import React, { useState, MouseEvent, ChangeEvent, useEffect, useCallback } from 'react';
|
||||
import { ReactComponent as Close } from '../assets/close.svg';
|
||||
import ProfileIcon from '../assets/account.svg';
|
||||
import { Profile } from './Profile';
|
||||
@ -9,9 +7,10 @@ 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';
|
||||
import { SelectMenu } from './SelectMenu';
|
||||
|
||||
const Topbar = styled.div`
|
||||
background-color: #E3E5ED;
|
||||
background-color: #e3e5ed;
|
||||
height: 80px;
|
||||
padding: 5px;
|
||||
font-size: 24px;
|
||||
@ -31,19 +30,22 @@ const LogoWrapper = styled.div`
|
||||
const Logo = styled.img`
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
@media only screen and (max-width: 670px) {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
@media only screen and (max-width: 1533px) {
|
||||
flex: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const Text = styled.div`
|
||||
margin-left: 10px;
|
||||
font-size: 1.4rem;
|
||||
user-select: none;
|
||||
@media only screen and (max-width: 670px) {
|
||||
margin-left: 10px;
|
||||
font-size: 1.4rem;
|
||||
user-select: none;
|
||||
@media only screen and (max-width: 1533px) {
|
||||
display: none;
|
||||
}
|
||||
@media only screen and (max-width: 1828px) {
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const FlexboxColumn = styled.div`
|
||||
@ -53,28 +55,37 @@ const FlexboxColumn = styled.div`
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-top: 15px;
|
||||
max-height: 40px;
|
||||
background-color: #f2f4f7;
|
||||
border-radius: 6px;
|
||||
align-items: center;
|
||||
border-radius: 6px 6px 6px 6px;
|
||||
padding-left: 6px;
|
||||
&:hover {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
&:hover > input {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 18px;
|
||||
background-color: #f1f2f5;
|
||||
font-size: 20px;
|
||||
height: 40px;
|
||||
max-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)`
|
||||
align-self: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 5px;
|
||||
@ -103,9 +114,9 @@ const Icon = styled.img`
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
|
||||
|
||||
export const Flexbox = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
interface TopbarProps {
|
||||
handleTransfer: (e: MouseEvent) => void;
|
||||
@ -122,21 +133,22 @@ export default function ({ handleTransfer }: TopbarProps) {
|
||||
|
||||
const handleProfile = (event: MouseEvent<HTMLImageElement>) => setAnchorEl(event.currentTarget);
|
||||
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
const handleCloseProfile = () => setAnchorEl(null);
|
||||
|
||||
const handleClearInput = () => setClearInput(!clearInput);
|
||||
const handleClearInput = useCallback(() => setClearInput((clearInput) => !clearInput), []);
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => setInput(event.target.value);
|
||||
|
||||
const handleClick = () => setOpen(true);
|
||||
const handleShowDropdown = () => setOpen(true);
|
||||
|
||||
const handleCloseDropdown = () => setOpen(false);
|
||||
|
||||
const handleClickAway = () => setOpen(false);
|
||||
|
||||
useEffect(() => {
|
||||
clearInput && (setInput(''), handleClearInput());
|
||||
}, [clearInput]);
|
||||
if (clearInput) {
|
||||
setInput('');
|
||||
handleClearInput();
|
||||
}
|
||||
}, [clearInput, handleClearInput]);
|
||||
|
||||
return (
|
||||
<Topbar>
|
||||
@ -145,11 +157,26 @@ export default function ({ handleTransfer }: TopbarProps) {
|
||||
<Text> plan na plan </Text>
|
||||
</LogoWrapper>
|
||||
<FlexboxColumn>
|
||||
<ClickAwayListener onClickAway={handleClickAway}>
|
||||
<InputWrapper>
|
||||
<Input placeholder="Wyszukaj przedmiot..." onChange={handleChange} onClick={handleClick} value={input} />
|
||||
<CloseIcon onClick={handleClearInput} />
|
||||
</InputWrapper>
|
||||
<ClickAwayListener onClickAway={handleCloseDropdown}>
|
||||
<Flexbox>
|
||||
{/* <SelectMenu /> */}
|
||||
|
||||
<InputWrapper>
|
||||
{/* <SelectSearch value={value} onChange={Change}>
|
||||
<SelectOption value="przedmiot">Przedmiot</SelectOption>
|
||||
<SelectOption value="student">Student</SelectOption>
|
||||
</SelectSearch> */}
|
||||
<Input
|
||||
placeholder={`Wyszukaj...`}
|
||||
onChange={handleChange}
|
||||
value={input}
|
||||
onFocus={() => {
|
||||
handleShowDropdown();
|
||||
}}
|
||||
/>
|
||||
<CloseIcon onClick={handleClearInput} />
|
||||
</InputWrapper>
|
||||
</Flexbox>
|
||||
<Dropdown open={open} input={input} handleCloseDropdown={handleCloseDropdown} />
|
||||
</ClickAwayListener>
|
||||
</FlexboxColumn>
|
||||
@ -159,7 +186,7 @@ export default function ({ handleTransfer }: TopbarProps) {
|
||||
{/* <Icon alt="transfer" src={Transfer} onClick={handleTransfer} /> */}
|
||||
<Icon alt="change_language" src={isPolish ? EnglishIcon : PolishIcon} onClick={onLangChange} />
|
||||
<Icon alt="profile" src={ProfileIcon} onClick={handleProfile} />
|
||||
<Profile anchorEl={anchorEl} handleClose={handleClose} />
|
||||
<Profile anchorEl={anchorEl} handleClose={handleCloseProfile} />
|
||||
</IconWrapper>
|
||||
</Topbar>
|
||||
);
|
||||
|
@ -1,37 +1,44 @@
|
||||
export const days = [
|
||||
"",
|
||||
"Poniedziałek",
|
||||
"Wtorek",
|
||||
"Środa",
|
||||
"Czwartek",
|
||||
"Piątek",
|
||||
];
|
||||
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",
|
||||
"",
|
||||
'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;
|
||||
|
||||
//added 12:00, one of lectures starts at that time
|
||||
export const courseStartTimeToEventRow: { [time: string]: number } = {
|
||||
'8.15': 0,
|
||||
'10.00': 1,
|
||||
'11.45': 2,
|
||||
'12.00': 2,
|
||||
'13.45': 3,
|
||||
'15.30': 4,
|
||||
'17.15': 5,
|
||||
'18.45': 6,
|
||||
};
|
||||
|
||||
//groupTimeToEventRowMapping - 1;
|
||||
export const ROWS_COUNT = 6;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect, createContext, ReactNode } from 'react';
|
||||
import { User } from '../types';
|
||||
import axios from 'axios';
|
||||
import { axiosInstance } from '../utils/axiosInstance';
|
||||
|
||||
export interface CASContext {
|
||||
user?: User;
|
||||
logout: () => void;
|
||||
token?: string | null;
|
||||
}
|
||||
|
||||
export const CASContext = createContext<CASContext | undefined>(undefined);
|
||||
@ -15,29 +16,28 @@ export interface CASProviderProps {
|
||||
|
||||
export const CASProvider = ({ children }: CASProviderProps) => {
|
||||
const [user, setUser] = useState<User | undefined>(undefined);
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
const login = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const ticket = urlParams.get('ticket');
|
||||
if (!ticket) {
|
||||
redirectToCASLoginService();
|
||||
}
|
||||
try {
|
||||
if (!sessionStorage.getItem('userToken')) {
|
||||
const { data: token } = await axiosInstance.get(`${process.env.REACT_APP_API_URL}/token?ticket=${ticket}`);
|
||||
sessionStorage.setItem('userToken', token);
|
||||
}
|
||||
const token = sessionStorage.getItem('userToken');
|
||||
setToken(token);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
login();
|
||||
}, []);
|
||||
|
||||
const login = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const ticket = urlParams.get('ticket');
|
||||
if (!ticket) {
|
||||
redirectToCASLoginService();
|
||||
}
|
||||
try {
|
||||
if (!sessionStorage.getItem('userToken')) {
|
||||
const { data: token } = await axios.get(`${process.env.REACT_APP_API_URL}/token?ticket=${ticket}`);
|
||||
sessionStorage.setItem('userToken', token);
|
||||
setUser({ ...user, token });
|
||||
}
|
||||
const token = sessionStorage.getItem('userToken');
|
||||
setUser({ ...user, token });
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
function logout() {
|
||||
redirectToCASLogoutService();
|
||||
}
|
||||
@ -50,5 +50,5 @@ export const CASProvider = ({ children }: CASProviderProps) => {
|
||||
window.location.replace(`https://cas.amu.edu.pl/cas/login?service=${window.origin}&locale=pl`);
|
||||
}
|
||||
|
||||
return <CASContext.Provider value={{ user, logout }}>{children}</CASContext.Provider>;
|
||||
return <CASContext.Provider value={{ user, token, logout }}>{children}</CASContext.Provider>;
|
||||
};
|
||||
|
@ -1,16 +1,33 @@
|
||||
import React, { useState, createContext, useEffect, ReactNode, useContext } from 'react';
|
||||
import { Course, Group, Basket, GroupType } from '../types';
|
||||
import axios from 'axios';
|
||||
import { CASContext } from './CASProvider';
|
||||
import React, { useState, createContext, useEffect, ReactNode } from 'react';
|
||||
import { Course, Group, Basket, GroupType, SchedulerEvent } from '../types';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { createClassTime } from '../utils';
|
||||
import { axiosInstance } from '../utils/axiosInstance';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledCloseIcon = styled(CloseIcon)`
|
||||
color: #000000;
|
||||
&:hover {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
interface CourseContext {
|
||||
courses: Array<Course>;
|
||||
basket: Array<Basket>;
|
||||
addToBasket: (courses: Course) => void;
|
||||
addGroup: (group: Group, id: number) => void;
|
||||
hoveredGroup: Group | undefined | null;
|
||||
addCourseToBasket: (courses: Course) => void;
|
||||
changeHoveredGroup: (group: Group | null) => void;
|
||||
changeGroupInBasket: (group: Group, courseId: number) => void;
|
||||
restoreGroupInBasket: (restoreGroup: Group, courseId: number) => void;
|
||||
deleteFromBasket: (id: number) => void;
|
||||
saveBasket: () => void;
|
||||
selectSchedulerEvents: () => Array<SchedulerEvent>;
|
||||
selectBasketNames: () => Array<string>;
|
||||
selectBasketCourses: () => Array<Course>;
|
||||
selectBasketCourseGroups: (courseId: number) => { lecture: Group | undefined; classes: Group | undefined };
|
||||
}
|
||||
export const coursesContext = createContext<CourseContext | undefined>(undefined);
|
||||
|
||||
@ -19,28 +36,60 @@ interface CoursesProviderProps {
|
||||
}
|
||||
|
||||
export const CoursesProvider = ({ children }: CoursesProviderProps) => {
|
||||
//fetch courses with groups
|
||||
const [courses, setCourses] = useState<Array<Course>>([]);
|
||||
const [basket, setBasket] = useState<Array<Basket>>([]);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const { closeSnackbar } = useSnackbar();
|
||||
|
||||
const CAS = useContext(CASContext)!;
|
||||
const token = CAS?.user?.token;
|
||||
|
||||
const selectBasketIds = (basket: Array<Basket>) => {
|
||||
const classesIds = basket.map((course) => course.classes.id);
|
||||
const lecturesIds = basket.map((course) => course?.lecture?.id);
|
||||
|
||||
return lecturesIds[0] === undefined ? classesIds : [...classesIds, ...lecturesIds];
|
||||
//fetch courses with groups
|
||||
const [courses, setCourses] = useState<Array<Course>>([]);
|
||||
const [basket, setBasket] = useState<Array<Basket>>([]);
|
||||
const [hoveredGroup, setHoveredGroup] = useState<Group | undefined | null>(null);
|
||||
const selectBasketIds = () => {
|
||||
const classesIds = basket.map((course) => course?.classes?.id).filter((course) => course !== undefined);
|
||||
const lecturesIds = basket.map((course) => course?.lecture?.id).filter((course) => course !== undefined);
|
||||
return [...classesIds, ...lecturesIds];
|
||||
};
|
||||
|
||||
const addToBasket = (course: Course) => {
|
||||
const selectBasketNames = () => basket.map(({ name }) => name);
|
||||
|
||||
const selectBasketCourses = () => {
|
||||
const basketNames = selectBasketNames();
|
||||
return basketNames.reduce((sum, basketName) => {
|
||||
const course = courses.find(({ name }) => basketName === name);
|
||||
return course === undefined ? sum : [...sum, course];
|
||||
}, [] as Array<Course>);
|
||||
};
|
||||
|
||||
const selectSchedulerEvents = () => {
|
||||
return basket.reduce((res, el) => {
|
||||
const { name } = el;
|
||||
if (el.classes) {
|
||||
const { time } = el.classes;
|
||||
res.push({ ...el.classes, name, time: createClassTime(time) });
|
||||
}
|
||||
if (el.lecture) {
|
||||
const { time } = el.lecture;
|
||||
res.push({ ...el.lecture, name, time: createClassTime(time) });
|
||||
}
|
||||
return res;
|
||||
}, [] as Array<SchedulerEvent>);
|
||||
};
|
||||
|
||||
const selectBasketCourseGroups = (courseId: number) => {
|
||||
const course = basket.find(({ id }) => id === courseId);
|
||||
if (course !== undefined) {
|
||||
return { lecture: course.lecture, classes: course.classes };
|
||||
} else {
|
||||
return { lecture: undefined, classes: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
const changeHoveredGroup = (group: Group | null) => setHoveredGroup(group);
|
||||
|
||||
const addCourseToBasket = (course: Course) => {
|
||||
const courseToBasket: Basket = {
|
||||
name: course.name,
|
||||
id: course.id,
|
||||
classes: course.classes[0],
|
||||
classes: course.classes !== undefined ? course.classes[0] : undefined,
|
||||
lecture: course.lectures !== undefined ? course.lectures[0] : undefined,
|
||||
};
|
||||
setBasket([...basket, courseToBasket]);
|
||||
@ -49,37 +98,25 @@ export const CoursesProvider = ({ children }: CoursesProviderProps) => {
|
||||
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 basketIds = selectBasketIds();
|
||||
const action = (key: any) => (
|
||||
<>
|
||||
<button
|
||||
<StyledCloseIcon
|
||||
onClick={() => {
|
||||
closeSnackbar(key);
|
||||
}}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
></StyledCloseIcon>
|
||||
</>
|
||||
);
|
||||
|
||||
try {
|
||||
await axios.request(config);
|
||||
await axiosInstance.post(`${process.env.REACT_APP_API_URL}/api/v1/commisions/user`, JSON.stringify(basketIds));
|
||||
enqueueSnackbar('Plan został zapisany', {
|
||||
variant: 'success',
|
||||
action,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log('error: ', e);
|
||||
enqueueSnackbar('Zapisywanie planu nie powiodło się', {
|
||||
variant: 'error',
|
||||
action,
|
||||
@ -87,8 +124,8 @@ export const CoursesProvider = ({ children }: CoursesProviderProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const addGroup = (choosenGroup: Group, id: number) => {
|
||||
const basketCourse = basket.filter((course) => course.id === id)[0];
|
||||
const changeGroupInBasket = (choosenGroup: Group, courseId: number) => {
|
||||
const basketCourse = basket.filter((course) => course.id === courseId)[0];
|
||||
const { type } = choosenGroup;
|
||||
if (type === GroupType.CLASS) {
|
||||
setBasket(
|
||||
@ -99,48 +136,72 @@ export const CoursesProvider = ({ children }: CoursesProviderProps) => {
|
||||
basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, lecture: choosenGroup } : basket)),
|
||||
);
|
||||
}
|
||||
changeHoveredGroup(choosenGroup);
|
||||
};
|
||||
|
||||
const restoreGroupInBasket = (restoreGroup: Group, courseId: number) => {
|
||||
const basketCourse = basket.filter((course) => course.id === courseId)[0];
|
||||
const { type } = restoreGroup;
|
||||
if (type === GroupType.CLASS) {
|
||||
setBasket(
|
||||
basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, classes: restoreGroup } : basket)),
|
||||
);
|
||||
} else if (type === GroupType.LECTURE) {
|
||||
setBasket(
|
||||
basket.map((basket) => (basket.id === basketCourse.id ? { ...basket, lecture: restoreGroup } : basket)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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 = [];
|
||||
}
|
||||
const { data } = await axiosInstance.get(
|
||||
`${process.env.REACT_APP_API_URL}/api/v1/assignments/user`,
|
||||
);
|
||||
const basket = data === '' ? [] : data;
|
||||
setBasket(basket);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchClasses = async () => {
|
||||
const fetchCourses = async () => {
|
||||
try {
|
||||
const { data: courses } = await axios.get<Array<Course>>(
|
||||
`${process.env.REACT_APP_API_URL}/api/v1/courses/getCoursesWithGroups`,
|
||||
const { data: courses } = await axiosInstance.get<Array<Course>>(
|
||||
`${process.env.REACT_APP_API_URL}/api/v1/courses/all?groups=true`,
|
||||
);
|
||||
courses.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
setCourses(courses);
|
||||
const sortedCourses = courses.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
setCourses(sortedCourses);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchClasses();
|
||||
if (token) {
|
||||
setTimeout(() => {
|
||||
fetchCourses();
|
||||
getNewestTimetable();
|
||||
}
|
||||
}, [token]);
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<coursesContext.Provider value={{ courses, basket, addToBasket, addGroup, deleteFromBasket, saveBasket }}>
|
||||
<coursesContext.Provider
|
||||
value={{
|
||||
courses,
|
||||
basket,
|
||||
hoveredGroup,
|
||||
addCourseToBasket,
|
||||
changeHoveredGroup,
|
||||
changeGroupInBasket,
|
||||
deleteFromBasket,
|
||||
restoreGroupInBasket,
|
||||
saveBasket,
|
||||
selectSchedulerEvents,
|
||||
selectBasketNames,
|
||||
selectBasketCourses,
|
||||
selectBasketCourseGroups,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</coursesContext.Provider>
|
||||
);
|
||||
|
@ -1,29 +0,0 @@
|
||||
// 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
|
@ -7,7 +7,7 @@ export interface Basket {
|
||||
id: number;
|
||||
name: string;
|
||||
lecture?: Group;
|
||||
classes: Group;
|
||||
classes?: Group;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
@ -24,11 +24,21 @@ export interface Course {
|
||||
id: number;
|
||||
name: string;
|
||||
lectures?: Array<Group>;
|
||||
classes: Array<Group>;
|
||||
classes?: Array<Group>;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
name?: string;
|
||||
surname?: string;
|
||||
token: string | null;
|
||||
}
|
||||
|
||||
export interface SchedulerEvent {
|
||||
id: number;
|
||||
day: number;
|
||||
time: [string, string];
|
||||
lecturer: string;
|
||||
room: string;
|
||||
type: GroupType;
|
||||
capacity?: number;
|
||||
name: string;
|
||||
}
|
||||
|
15
src/utils/axiosInstance.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export const axiosInstance = axios.create();
|
||||
|
||||
axiosInstance.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = sessionStorage.getItem('userToken');
|
||||
config.headers['Authorization'] = 'Bearer ' + token;
|
||||
config.headers['Content-Type'] = 'application/json';
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
Promise.reject(error);
|
||||
},
|
||||
);
|
80
src/utils/index.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { courseStartTimeToEventRow } from '../constants/index';
|
||||
import { SchedulerEvent } from '../types';
|
||||
|
||||
export const createClassTime = (startTime: string): [string, string] => {
|
||||
const startTimeMapped = courseStartTimeToEventRow[startTime];
|
||||
const endTime = Object.keys(courseStartTimeToEventRow).find(
|
||||
(key) => courseStartTimeToEventRow[key] === startTimeMapped + 1,
|
||||
)!;
|
||||
return [startTime, endTime];
|
||||
};
|
||||
|
||||
export const selectGroupsToShow = (schedulerEvents: Array<SchedulerEvent>, index: number) => {
|
||||
return schedulerEvents.filter(({ time }: { time: [string, string] }) => courseStartTimeToEventRow[time[0]] === index);
|
||||
};
|
||||
|
||||
type Procedure = (...args: any[]) => any;
|
||||
|
||||
interface Debounce {
|
||||
(...args: any[]): any;
|
||||
clear: () => void;
|
||||
flush: () => void;
|
||||
}
|
||||
|
||||
export const debounce = (func: Procedure, wait: number, immediate: boolean = false) => {
|
||||
let timeout: number | null;
|
||||
let args: any;
|
||||
let context: any;
|
||||
let result: any;
|
||||
|
||||
const later = () => {
|
||||
timeout = null;
|
||||
if (!immediate) {
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedFunc: Procedure = function (this: any) {
|
||||
context = this;
|
||||
args = arguments;
|
||||
const callNow = immediate && !timeout;
|
||||
|
||||
if (!timeout) {
|
||||
timeout = window.setTimeout(later, wait);
|
||||
}
|
||||
|
||||
if (callNow) {
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
const flush = () => {
|
||||
if (timeout) {
|
||||
result = func.apply(context, args);
|
||||
context = args = null;
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
const debounced: Debounce = (() => {
|
||||
const f: any = debouncedFunc;
|
||||
f.clear = clear;
|
||||
f.flush = flush;
|
||||
return f;
|
||||
})();
|
||||
|
||||
return debounced;
|
||||
};
|