Added most of backend interactions and rewrote filers logic
This commit is contained in:
parent
a83e667dae
commit
403c57cdba
104
src/App.tsx
104
src/App.tsx
@ -1,62 +1,98 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Switch, Route, useLocation } from "react-router-dom";
|
import { Switch, Route, useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import "./App.css";
|
import './App.css';
|
||||||
import Home from "./Home";
|
import Home from './Home';
|
||||||
import Navbar from "./Navbar";
|
import Navbar from './Navbar';
|
||||||
import SubjectList from "./SubjectList";
|
import { queryIsEmpty } from './Navbar/utils';
|
||||||
import {ILoadingState} from './types'
|
import SubjectList from './SubjectList';
|
||||||
|
import { ILoadingState, IFilterQuery } from './types';
|
||||||
|
|
||||||
const genName = (path: string, search?: string): string => {
|
const genName = (searchQuery: IFilterQuery, path: string): string => {
|
||||||
if (path === "/list" && search) {
|
if (path === '/list' && searchQuery) {
|
||||||
search = decodeURI(search);
|
let result = '';
|
||||||
let query: any = {};
|
|
||||||
search
|
|
||||||
.split("?")
|
|
||||||
.slice(1)
|
|
||||||
.map((param) => (query[param.split("=")[0]] = param.split("=")[1]));
|
|
||||||
|
|
||||||
let result = "";
|
if (searchQuery.class_num) {
|
||||||
|
result = result + searchQuery.class_num + ' класс';
|
||||||
if (query.clas) {
|
|
||||||
result = result + query.clas + " класс";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.subject) {
|
if (searchQuery.predmet_type) {
|
||||||
result = result + ", " + query.subject;
|
result = result + ' ' + searchQuery.predmet_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.teacher) {
|
if (searchQuery.teacher) {
|
||||||
result = result + ", " + query.teacher;
|
result = result + ' ' + searchQuery.teacher;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchQuery.search) {
|
||||||
|
result = result + ' поиск по "' + searchQuery.search + '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (path === "/") {
|
if (path === '/') {
|
||||||
return "Банк семинаров";
|
return 'Банк семинаров';
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
function App() {
|
const useDidUpdate: typeof useEffect = (func, dependencies) => {
|
||||||
const location = useLocation();
|
const didMountRef = useRef(false);
|
||||||
|
|
||||||
const [loading, setLoading] = useState<ILoadingState>({ fetching: true, error: "" });
|
useEffect(() => {
|
||||||
|
if (didMountRef.current) {
|
||||||
|
func();
|
||||||
|
} else {
|
||||||
|
didMountRef.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, dependencies);
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState<ILoadingState>({
|
||||||
|
fetching: true,
|
||||||
|
error: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState<IFilterQuery>({
|
||||||
|
search: '',
|
||||||
|
class_num: '',
|
||||||
|
type_num: '',
|
||||||
|
predmet_type: '',
|
||||||
|
teacher: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
useDidUpdate(() => {
|
||||||
|
if (queryIsEmpty(searchQuery)) return;
|
||||||
|
|
||||||
|
history.push('/list');
|
||||||
|
}, [searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<header id="name">
|
<header id="name">
|
||||||
<h1>{genName(location.pathname, location.search)}</h1>
|
<h1>{genName(searchQuery, history.location.pathname)}</h1>
|
||||||
</header>
|
</header>
|
||||||
<Navbar />
|
<Navbar query={searchQuery} setSearchQuery={setSearchQuery} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/">
|
<Route exact path="/">
|
||||||
<Home setLoading={setLoading} />
|
<Home
|
||||||
|
setLoading={setLoading}
|
||||||
|
setSearchQuery={setSearchQuery}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/list">
|
<Route path="/list">
|
||||||
<SubjectList setLoading={setLoading} />
|
<SubjectList
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
setLoading={setLoading}
|
||||||
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*">
|
<Route path="*">
|
||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
@ -64,6 +100,6 @@ function App() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import './main.css'
|
|
||||||
|
|
||||||
const Card = (props) => (
|
|
||||||
<a className="card" href={ '/' + props.data.image.slice(props.data.image.indexOf('media'))}>
|
|
||||||
<h4 className="cardTitle">{props.data.title}</h4>
|
|
||||||
<h5 className="cardTeacher">{props.data.teacher}</h5>
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Card;
|
|
16
src/Card/index.tsx
Normal file
16
src/Card/index.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { IData } from '../types';
|
||||||
|
|
||||||
|
import './main.css';
|
||||||
|
|
||||||
|
const Card = ({ data }: { data: IData }) => (
|
||||||
|
<a
|
||||||
|
className="card"
|
||||||
|
href={'/' + data.image.slice(data.image.indexOf('media'))}
|
||||||
|
>
|
||||||
|
<h4 className="cardTitle">{data.title}</h4>
|
||||||
|
<h5 className="cardTeacher">{data.teacher}</h5>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Card;
|
@ -1,27 +1,28 @@
|
|||||||
import React, { Dispatch, useEffect, useState } from "react";
|
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Card from "../Card";
|
import Card from '../Card';
|
||||||
import { IData, ILoadingState } from "../types";
|
import { IData, IFilterQuery, ILoadingState } from '../types';
|
||||||
import "./main.css";
|
import './main.css';
|
||||||
|
|
||||||
const Home = ({ setLoading }: {setLoading: Dispatch<ILoadingState>}) => {
|
const Home = ({
|
||||||
|
setLoading,
|
||||||
|
setSearchQuery
|
||||||
|
}: {
|
||||||
|
setLoading: Dispatch<SetStateAction<ILoadingState>>;
|
||||||
|
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||||
|
}) => {
|
||||||
const [data, setData] = useState<IData[]>([]);
|
const [data, setData] = useState<IData[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading({ fetching: true, error: "" });
|
setLoading({ fetching: true, error: '' });
|
||||||
console.log("Loading data");
|
|
||||||
|
|
||||||
const requestURL =
|
const requestURL = 'https://upml-bank.dmitriy.icu/api/cards';
|
||||||
"https://cors-anywhere.herokuapp.com/upml-bank.dmitriy.icu/api/cards";
|
|
||||||
fetch(requestURL)
|
fetch(requestURL)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("Fetched data");
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
setData(data);
|
setData(data);
|
||||||
setLoading({ fetching: false, error: "" });
|
setLoading({ fetching: false, error: '' });
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setLoading({ fetching: false, error: err });
|
setLoading({ fetching: false, error: err });
|
||||||
@ -30,9 +31,17 @@ const Home = ({ setLoading }: {setLoading: Dispatch<ILoadingState>}) => {
|
|||||||
}, [setLoading]);
|
}, [setLoading]);
|
||||||
|
|
||||||
const classes: number[] = [
|
const classes: number[] = [
|
||||||
...Array.from(new Set(data.map((el) => parseInt(el.class_num)).sort())),
|
...Array.from(new Set(data.map((el) => parseInt(el.class_num)).sort()))
|
||||||
];
|
];
|
||||||
const subjects: string[] = [...Array.from(new Set(data.map((el) => el.predmet_type).sort()))];
|
const subjects: string[] = [
|
||||||
|
...Array.from(new Set(data.map((el) => el.predmet_type).sort()))
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleShowMore = (predmet_type: string, class_num: number) => {
|
||||||
|
setSearchQuery((prev): IFilterQuery => {
|
||||||
|
return { ...prev, predmet_type, class_num: class_num.toString() };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="homeContainer">
|
<main className="homeContainer">
|
||||||
@ -62,12 +71,13 @@ const Home = ({ setLoading }: {setLoading: Dispatch<ILoadingState>}) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="showMore">
|
<div className="showMore">
|
||||||
<Link
|
<Link
|
||||||
to={
|
onClick={() =>
|
||||||
"/list?subject=" +
|
handleShowMore(
|
||||||
subject +
|
subject,
|
||||||
"?clas=" +
|
class_num
|
||||||
class_num
|
)
|
||||||
}
|
}
|
||||||
|
to={'/list'}
|
||||||
>
|
>
|
||||||
Больше →
|
Больше →
|
||||||
</Link>
|
</Link>
|
||||||
@ -75,7 +85,7 @@ const Home = ({ setLoading }: {setLoading: Dispatch<ILoadingState>}) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
""
|
''
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -1,57 +1,138 @@
|
|||||||
import React, { useState } from "react";
|
import React, {
|
||||||
import { motion } from "framer-motion";
|
ChangeEvent,
|
||||||
import { Link } from "react-router-dom";
|
Dispatch,
|
||||||
|
SetStateAction,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import "./main.css";
|
import './main.css';
|
||||||
|
|
||||||
import LogoImage from "./logo.png";
|
import LogoImage from './logo.png';
|
||||||
import FilterIcon from "./filter.svg";
|
import FilterIcon from './filter.svg';
|
||||||
import SearchIcon from "./search.svg";
|
import SearchIcon from './search.svg';
|
||||||
|
import { IFilterQuery } from '../types';
|
||||||
|
import { emptyQuery } from './utils';
|
||||||
|
import { useFocus } from '../utils';
|
||||||
|
|
||||||
|
const Navbar = ({
|
||||||
|
setSearchQuery,
|
||||||
|
query
|
||||||
|
}: {
|
||||||
|
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||||
|
query: IFilterQuery;
|
||||||
|
}) => {
|
||||||
|
/*
|
||||||
|
* Hooks
|
||||||
|
*/
|
||||||
|
|
||||||
const Navbar = () => {
|
|
||||||
const [searchCollapsed, setSearchCollapsed] = useState(true);
|
const [searchCollapsed, setSearchCollapsed] = useState(true);
|
||||||
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
||||||
|
const [localFilters, setLocalFilters] = useState<Partial<IFilterQuery>>();
|
||||||
|
|
||||||
|
const searchInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const setInputFocus = useFocus(searchInput);
|
||||||
|
|
||||||
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (formRef.current) {
|
||||||
|
for (const [key, value] of Object.entries(query)) {
|
||||||
|
console.log(key, value);
|
||||||
|
if (formRef.current.elements.namedItem(key)) {
|
||||||
|
(formRef.current.elements.namedItem(
|
||||||
|
key
|
||||||
|
) as HTMLSelectElement).value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Animations
|
||||||
|
*/
|
||||||
|
|
||||||
const searchVariants = {
|
const searchVariants = {
|
||||||
open: {
|
open: {
|
||||||
width: "calc(100vw - 4vh)",
|
width: 'calc(100vw - 4vh)',
|
||||||
display: "block",
|
display: 'block'
|
||||||
},
|
},
|
||||||
closed: {
|
closed: {
|
||||||
width: "6vh",
|
width: '6vh',
|
||||||
transitionEnd: {
|
transitionEnd: {
|
||||||
display: "none",
|
display: 'none'
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navVariants = {
|
const navVariants = {
|
||||||
open: {
|
open: {
|
||||||
height: "100vh",
|
height: '100vh',
|
||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderTopRightRadius: 0,
|
borderTopRightRadius: 0
|
||||||
},
|
},
|
||||||
closed: {
|
closed: {
|
||||||
height: "10vh",
|
height: '10vh',
|
||||||
borderTopLeftRadius: "20px",
|
borderTopLeftRadius: '20px',
|
||||||
borderTopRightRadius: "20px",
|
borderTopRightRadius: '20px'
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filtersVariants = {
|
const filtersVariants = {
|
||||||
open: {
|
open: {
|
||||||
height: "100vh",
|
height: '100vh',
|
||||||
padding: "2vh",
|
padding: '2vh'
|
||||||
},
|
},
|
||||||
closed: {
|
closed: {
|
||||||
height: 0,
|
height: 0,
|
||||||
padding: 0,
|
padding: 0
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const transition = {
|
const transition = {
|
||||||
ease: "easeIn",
|
ease: 'easeIn',
|
||||||
duration: 0.5,
|
duration: 0.5
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Input handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
const handleFiltersButton = () => {
|
||||||
|
if (!filtersCollapsed) {
|
||||||
|
setSearchQuery((prev) => {
|
||||||
|
return { ...prev, ...localFilters };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setFiltersCollapsed((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = ({
|
||||||
|
target: element
|
||||||
|
}: ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setLocalFilters((prev) => {
|
||||||
|
return { ...prev, [element.name]: element.value };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchButton = () => {
|
||||||
|
if (searchCollapsed) {
|
||||||
|
setSearchCollapsed(false);
|
||||||
|
setInputFocus();
|
||||||
|
} else if (searchInput && searchInput.current) {
|
||||||
|
const value = searchInput.current.value;
|
||||||
|
|
||||||
|
setSearchQuery((prev) => {
|
||||||
|
return { ...prev, search: value };
|
||||||
|
});
|
||||||
|
setSearchCollapsed(true);
|
||||||
|
|
||||||
|
searchInput.current.value = '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -59,10 +140,15 @@ const Navbar = () => {
|
|||||||
id="navbar"
|
id="navbar"
|
||||||
variants={navVariants}
|
variants={navVariants}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
animate={filtersCollapsed ? "closed" : "open"}
|
animate={filtersCollapsed ? 'closed' : 'open'}
|
||||||
>
|
>
|
||||||
<nav>
|
<nav>
|
||||||
<Link to="/">
|
<Link
|
||||||
|
to="/"
|
||||||
|
onClick={() => {
|
||||||
|
emptyQuery(setSearchQuery);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<img id="logo" src={LogoImage} alt="Логотип ЮФМЛ" />
|
<img id="logo" src={LogoImage} alt="Логотип ЮФМЛ" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -71,7 +157,7 @@ const Navbar = () => {
|
|||||||
<button
|
<button
|
||||||
className="navButton"
|
className="navButton"
|
||||||
id="filter"
|
id="filter"
|
||||||
onClick={() => setFiltersCollapsed((prev) => !prev)}
|
onClick={handleFiltersButton}
|
||||||
>
|
>
|
||||||
<img src={FilterIcon} alt="Фильтр" />
|
<img src={FilterIcon} alt="Фильтр" />
|
||||||
</button>
|
</button>
|
||||||
@ -79,16 +165,17 @@ const Navbar = () => {
|
|||||||
<button
|
<button
|
||||||
className="navButton"
|
className="navButton"
|
||||||
id="search"
|
id="search"
|
||||||
onClick={() => setSearchCollapsed((prev) => !prev)}
|
onClick={handleSearchButton}
|
||||||
>
|
>
|
||||||
<img src={SearchIcon} alt="Поиск" />
|
<img src={SearchIcon} alt="Поиск" />
|
||||||
</button>
|
</button>
|
||||||
<motion.input
|
<motion.input
|
||||||
type="search"
|
animate={searchCollapsed ? 'closed' : 'open'}
|
||||||
animate={searchCollapsed ? "closed" : "open"}
|
|
||||||
variants={searchVariants}
|
variants={searchVariants}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
aria-label="Поиск"
|
aria-label="Поиск"
|
||||||
|
ref={searchInput}
|
||||||
|
type="search"
|
||||||
name="search"
|
name="search"
|
||||||
id="searchInput"
|
id="searchInput"
|
||||||
/>
|
/>
|
||||||
@ -97,17 +184,20 @@ const Navbar = () => {
|
|||||||
<motion.form
|
<motion.form
|
||||||
variants={filtersVariants}
|
variants={filtersVariants}
|
||||||
transition={transition}
|
transition={transition}
|
||||||
animate={filtersCollapsed ? "closed" : "open"}
|
animate={filtersCollapsed ? 'closed' : 'open'}
|
||||||
id="filters"
|
id="filters"
|
||||||
|
ref={formRef}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="teacherFilter">
|
<label htmlFor="teacherFilter">
|
||||||
<h2>Преподаватель</h2>
|
<h2>Преподаватель</h2>
|
||||||
</label>
|
</label>
|
||||||
<select name="teacher" id="teacherFilter">
|
<select
|
||||||
<option value="">
|
name="teacher"
|
||||||
-
|
onChange={handleSelectChange}
|
||||||
</option>
|
id="teacherFilter"
|
||||||
|
>
|
||||||
|
<option value="">-</option>
|
||||||
<option value="Попов Д.А">Попов Д.А</option>
|
<option value="Попов Д.А">Попов Д.А</option>
|
||||||
<option value="Ильин А.Б">Ильин А.Б</option>
|
<option value="Ильин А.Б">Ильин А.Б</option>
|
||||||
<option value="Пачин И.М">Пачин И.М</option>
|
<option value="Пачин И.М">Пачин И.М</option>
|
||||||
@ -127,10 +217,12 @@ const Navbar = () => {
|
|||||||
<label htmlFor="typeFilter">
|
<label htmlFor="typeFilter">
|
||||||
<h2>Тип задания</h2>
|
<h2>Тип задания</h2>
|
||||||
</label>
|
</label>
|
||||||
<select name="type_num" id="typeFilter">
|
<select
|
||||||
<option value="">
|
name="type_num"
|
||||||
-
|
onChange={handleSelectChange}
|
||||||
</option>
|
id="typeFilter"
|
||||||
|
>
|
||||||
|
<option value="">-</option>
|
||||||
<option value="Семестровки">Семестровки</option>
|
<option value="Семестровки">Семестровки</option>
|
||||||
<option value="Семинары">Семинары</option>
|
<option value="Семинары">Семинары</option>
|
||||||
<option value="Потоковые">Потоковые</option>
|
<option value="Потоковые">Потоковые</option>
|
||||||
@ -140,10 +232,12 @@ const Navbar = () => {
|
|||||||
<label htmlFor="predmetFilter">
|
<label htmlFor="predmetFilter">
|
||||||
<h2>Предмет</h2>
|
<h2>Предмет</h2>
|
||||||
</label>
|
</label>
|
||||||
<select name="predmet_type" id="predmetFilter">
|
<select
|
||||||
<option value="">
|
name="predmet_type"
|
||||||
-
|
onChange={handleSelectChange}
|
||||||
</option>
|
id="predmetFilter"
|
||||||
|
>
|
||||||
|
<option value="">-</option>
|
||||||
<option value="Математика">Математика</option>
|
<option value="Математика">Математика</option>
|
||||||
<option value="Физика">Физика</option>
|
<option value="Физика">Физика</option>
|
||||||
<option value="Информатика">Информатика</option>
|
<option value="Информатика">Информатика</option>
|
||||||
@ -153,10 +247,12 @@ const Navbar = () => {
|
|||||||
<label htmlFor="classFilter">
|
<label htmlFor="classFilter">
|
||||||
<h2>Класс</h2>
|
<h2>Класс</h2>
|
||||||
</label>
|
</label>
|
||||||
<select name="class_num" id="classFilter">
|
<select
|
||||||
<option value="">
|
name="class_num"
|
||||||
-
|
onChange={handleSelectChange}
|
||||||
</option>
|
id="classFilter"
|
||||||
|
>
|
||||||
|
<option value="">-</option>
|
||||||
<option value="10">10</option>
|
<option value="10">10</option>
|
||||||
<option value="11">11</option>
|
<option value="11">11</option>
|
||||||
</select>
|
</select>
|
||||||
|
24
src/Navbar/utils.ts
Normal file
24
src/Navbar/utils.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
|
import { IFilterQuery } from '../types';
|
||||||
|
|
||||||
|
const queryIsEmpty = (q: IFilterQuery): boolean => {
|
||||||
|
for (const [_, value] of Object.entries(q)) {
|
||||||
|
if (value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const emptyQuery = (setter: Dispatch<SetStateAction<IFilterQuery>>) => {
|
||||||
|
setter({
|
||||||
|
class_num: '',
|
||||||
|
type_num: '',
|
||||||
|
predmet_type: '',
|
||||||
|
search: '',
|
||||||
|
teacher: ''
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { queryIsEmpty, emptyQuery };
|
@ -1,20 +1,32 @@
|
|||||||
import React, { useEffect, useState, Dispatch } from 'react';
|
import React, { useEffect, useState, Dispatch } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { IData, ILoadingState } from '../types';
|
import { IData, ILoadingState, IFilterQuery } from '../types';
|
||||||
|
import { queryIsEmpty } from '../Navbar/utils';
|
||||||
import Card from '../Card';
|
import Card from '../Card';
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
|
||||||
const SubjectList = ({
|
const SubjectList = ({
|
||||||
setLoading
|
setLoading,
|
||||||
|
searchQuery
|
||||||
}: {
|
}: {
|
||||||
setLoading: Dispatch<ILoadingState>;
|
setLoading: Dispatch<ILoadingState>;
|
||||||
|
searchQuery: IFilterQuery;
|
||||||
}) => {
|
}) => {
|
||||||
const [data, setData] = useState<IData[]>([]);
|
const [data, setData] = useState<IData[]>([]);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (queryIsEmpty(searchQuery)) {
|
||||||
|
history.push('/');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading({ fetching: true, error: '' });
|
setLoading({ fetching: true, error: '' });
|
||||||
const fetchURL = 'https://upml-bank.dmitriy.icu/api/cards?' + new URLSearchParams({class_num: '11'}).toString()
|
const fetchURL =
|
||||||
|
'https://upml-bank.dmitriy.icu/api/cards?' +
|
||||||
|
new URLSearchParams({ ...searchQuery }).toString();
|
||||||
fetch(fetchURL)
|
fetch(fetchURL)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -25,7 +37,7 @@ const SubjectList = ({
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
setLoading({ fetching: false, error: err });
|
setLoading({ fetching: false, error: err });
|
||||||
});
|
});
|
||||||
}, [setLoading]);
|
}, [setLoading, searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="subjectList">
|
<main className="subjectList">
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
@ -36,4 +37,7 @@ code {
|
|||||||
#name h1 {
|
#name h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 6vh;
|
line-height: 6vh;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
10
src/types.ts
10
src/types.ts
@ -15,4 +15,12 @@ interface IData {
|
|||||||
card_id: number;
|
card_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { ILoadingState, IData };
|
interface IFilterQuery {
|
||||||
|
class_num: string;
|
||||||
|
type_num: string;
|
||||||
|
predmet_type: string;
|
||||||
|
teacher: string;
|
||||||
|
search: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ILoadingState, IData, IFilterQuery };
|
||||||
|
11
src/utils.ts
Normal file
11
src/utils.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const useFocus = (focusRef: React.RefObject<HTMLInputElement>) => {
|
||||||
|
const setFocus = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
focusRef.current && focusRef.current.focus();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return setFocus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useFocus };
|
@ -1,25 +1,20 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": [
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"dom",
|
"allowJs": true,
|
||||||
"dom.iterable",
|
"skipLibCheck": true,
|
||||||
"esnext"
|
"esModuleInterop": true,
|
||||||
],
|
"allowSyntheticDefaultImports": true,
|
||||||
"allowJs": true,
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"esModuleInterop": true,
|
"module": "esnext",
|
||||||
"allowSyntheticDefaultImports": true,
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"resolveJsonModule": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"isolatedModules": true,
|
||||||
"module": "esnext",
|
"noEmit": true,
|
||||||
"moduleResolution": "node",
|
"jsx": "react",
|
||||||
"resolveJsonModule": true,
|
"suppressImplicitAnyIndexErrors": true
|
||||||
"isolatedModules": true,
|
},
|
||||||
"noEmit": true,
|
"include": ["src"]
|
||||||
"jsx": "react"
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user