Refactored components code
This commit is contained in:
parent
64340c4ef0
commit
c50ad9f2b9
@ -3,7 +3,11 @@ import { IData } from '../types';
|
||||
|
||||
import './main.css';
|
||||
|
||||
const Card = ({ data }: { data: IData }) => (
|
||||
type props = {
|
||||
data: IData;
|
||||
};
|
||||
|
||||
const Card: React.FC<props> = ({ data }) => (
|
||||
<a
|
||||
className="card"
|
||||
href={'/' + data.image.slice(data.image.indexOf('media'))}
|
||||
|
23
src/Header/animations.ts
Normal file
23
src/Header/animations.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Transition, Variants } from 'framer-motion';
|
||||
|
||||
const headerVariants: Variants = {
|
||||
expanded: {
|
||||
height: '100vh'
|
||||
},
|
||||
collapsed: {
|
||||
height: '10vh'
|
||||
}
|
||||
};
|
||||
|
||||
const headerTransition: Transition = {
|
||||
duration: 0.5,
|
||||
ease: 'linear'
|
||||
};
|
||||
|
||||
const loadingLogoTransition: Transition = {
|
||||
loop: Infinity,
|
||||
ease: 'linear',
|
||||
duration: 3
|
||||
};
|
||||
|
||||
export { headerVariants, headerTransition, loadingLogoTransition };
|
@ -1,95 +1,43 @@
|
||||
import React, { Dispatch, SetStateAction, useEffect } from 'react';
|
||||
import { motion, Transition } from 'framer-motion';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import React, { Dispatch, SetStateAction } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { IFilterQuery, ILoadingState } from '../types';
|
||||
import './main.css';
|
||||
import Logotype from '../Logotype';
|
||||
import { genName } from './utils';
|
||||
import {
|
||||
headerTransition,
|
||||
headerVariants,
|
||||
loadingLogoTransition
|
||||
} from './animations';
|
||||
|
||||
const genName = (
|
||||
searchQuery: IFilterQuery,
|
||||
path: string,
|
||||
error: string
|
||||
): string => {
|
||||
if (error) {
|
||||
return error.toString();
|
||||
}
|
||||
if (path === '/list' && searchQuery) {
|
||||
let result = '';
|
||||
|
||||
if (searchQuery.class_num) {
|
||||
result = result + searchQuery.class_num + ' класс';
|
||||
}
|
||||
|
||||
if (searchQuery.predmet_type) {
|
||||
result = result + ' ' + searchQuery.predmet_type;
|
||||
}
|
||||
|
||||
if (searchQuery.teacher) {
|
||||
result = result + ' ' + searchQuery.teacher;
|
||||
}
|
||||
|
||||
if (searchQuery.search) {
|
||||
result = result + ' поиск по "' + searchQuery.search + '"';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
return 'Банк семинаров';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const Header = ({
|
||||
query,
|
||||
loading,
|
||||
setSearchQuery
|
||||
}: {
|
||||
type props = {
|
||||
query: IFilterQuery;
|
||||
loading: ILoadingState;
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const Header: React.FC<props> = ({ query, loading, setSearchQuery }) => {
|
||||
/*
|
||||
* Hooks definitions
|
||||
*/
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {}, [loading]);
|
||||
|
||||
/*
|
||||
* Animations
|
||||
*/
|
||||
const headerVariants = {
|
||||
expanded: {
|
||||
height: '100vh'
|
||||
},
|
||||
collapsed: {
|
||||
height: '10vh'
|
||||
}
|
||||
};
|
||||
|
||||
const transition: Transition = {
|
||||
duration: 0.5,
|
||||
ease: 'linear'
|
||||
};
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<motion.header
|
||||
id="name"
|
||||
variants={headerVariants}
|
||||
transition={transition}
|
||||
transition={headerTransition}
|
||||
animate={loading.fetching ? 'expanded' : 'collapsed'}
|
||||
initial="expanded"
|
||||
>
|
||||
<h1>{genName(query, history.location.pathname, loading.error)}</h1>
|
||||
<h1>{genName(query, pathname, loading.error)}</h1>
|
||||
{loading.fetching ? (
|
||||
<motion.div
|
||||
id="loadingLogo"
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ loop: Infinity, ease: 'linear', duration: 3 }}
|
||||
transition={loadingLogoTransition}
|
||||
>
|
||||
<Logotype setSearchQuery={setSearchQuery} />
|
||||
</motion.div>
|
||||
|
40
src/Header/utils.ts
Normal file
40
src/Header/utils.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { IFilterQuery } from '../types';
|
||||
|
||||
const genName = (
|
||||
searchQuery: IFilterQuery,
|
||||
path: string,
|
||||
error: string
|
||||
): string => {
|
||||
if (error) {
|
||||
return error.toString();
|
||||
}
|
||||
if (path === '/list' && searchQuery) {
|
||||
let result = '';
|
||||
|
||||
if (searchQuery.class_num) {
|
||||
result = result + searchQuery.class_num + ' класс';
|
||||
}
|
||||
|
||||
if (searchQuery.predmet_type) {
|
||||
result = result + ' ' + searchQuery.predmet_type;
|
||||
}
|
||||
|
||||
if (searchQuery.teacher) {
|
||||
result = result + ' ' + searchQuery.teacher;
|
||||
}
|
||||
|
||||
if (searchQuery.search) {
|
||||
result = result + ' поиск по "' + searchQuery.search + '"';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
return 'Банк семинаров';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export { genName };
|
20
src/Home/handlers.ts
Normal file
20
src/Home/handlers.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { IFilterQuery } from '../types';
|
||||
|
||||
const handleShowMore = (
|
||||
predmet_type: string,
|
||||
class_num: number,
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>
|
||||
) => {
|
||||
setSearchQuery(
|
||||
(prev): IFilterQuery => {
|
||||
return {
|
||||
...prev,
|
||||
predmet_type,
|
||||
class_num: class_num.toString()
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export { handleShowMore };
|
@ -4,15 +4,15 @@ import { Link } from 'react-router-dom';
|
||||
import Card from '../Card';
|
||||
import NothingFound from '../NothingFound';
|
||||
import { IData, IFilterQuery, ILoadingState } from '../types';
|
||||
import { handleShowMore } from './handlers';
|
||||
import './main.css';
|
||||
|
||||
const Home = ({
|
||||
setLoading,
|
||||
setSearchQuery
|
||||
}: {
|
||||
type props = {
|
||||
setLoading: Dispatch<SetStateAction<ILoadingState>>;
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const Home: React.FC<props> = ({ setLoading, setSearchQuery }) => {
|
||||
const [data, setData] = useState<IData[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -38,18 +38,6 @@ const Home = ({
|
||||
...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 (
|
||||
<main className="homeContainer">
|
||||
{classes.length ? (
|
||||
@ -87,7 +75,8 @@ const Home = ({
|
||||
onClick={() =>
|
||||
handleShowMore(
|
||||
subject,
|
||||
class_num
|
||||
class_num,
|
||||
setSearchQuery
|
||||
)
|
||||
}
|
||||
to={'/list'}
|
||||
|
@ -4,13 +4,13 @@ import { Link } from 'react-router-dom';
|
||||
import { emptyQuery } from '../Navbar/utils';
|
||||
import { IFilterQuery } from '../types';
|
||||
import LogoImage from './logo.png';
|
||||
import './main.css'
|
||||
import './main.css';
|
||||
|
||||
const Logotype = ({
|
||||
setSearchQuery
|
||||
}: {
|
||||
type props = {
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const Logotype: React.FC<props> = ({ setSearchQuery }) => {
|
||||
return (
|
||||
<Link
|
||||
to="/"
|
||||
|
45
src/Navbar/animations.ts
Normal file
45
src/Navbar/animations.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { Transition, Variants } from 'framer-motion'
|
||||
|
||||
const searchVariants: Variants = {
|
||||
open: {
|
||||
width: 'calc(100vw - 4vh)',
|
||||
display: 'block'
|
||||
},
|
||||
closed: {
|
||||
width: '6vh',
|
||||
transitionEnd: {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navVariants: Variants = {
|
||||
open: {
|
||||
height: '100vh',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
closed: {
|
||||
height: '10vh',
|
||||
borderTopLeftRadius: '20px',
|
||||
borderTopRightRadius: '20px'
|
||||
}
|
||||
};
|
||||
|
||||
const filtersVariants: Variants = {
|
||||
open: {
|
||||
height: '100vh',
|
||||
padding: '2vh'
|
||||
},
|
||||
closed: {
|
||||
height: 0,
|
||||
padding: 0
|
||||
}
|
||||
};
|
||||
|
||||
const transition: Transition = {
|
||||
ease: 'easeIn',
|
||||
duration: 0.5
|
||||
};
|
||||
|
||||
export { filtersVariants, navVariants, searchVariants, transition };
|
49
src/Navbar/handlers.ts
Normal file
49
src/Navbar/handlers.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Dispatch, RefObject, SetStateAction } from 'react';
|
||||
import { IFilterQuery } from '../types';
|
||||
|
||||
const handleFiltersButton = (
|
||||
filtersCollapsed: boolean,
|
||||
localFilters: Partial<IFilterQuery>,
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>,
|
||||
setFiltersCollapsed: Dispatch<SetStateAction<boolean>>
|
||||
) => {
|
||||
if (!filtersCollapsed) {
|
||||
setSearchQuery((prev) => {
|
||||
return { ...prev, ...localFilters };
|
||||
});
|
||||
}
|
||||
setFiltersCollapsed((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleSelectChange = (
|
||||
element: HTMLSelectElement,
|
||||
setLocalFilters: Dispatch<SetStateAction<Partial<IFilterQuery>>>
|
||||
) => {
|
||||
setLocalFilters((prev) => {
|
||||
return { ...prev, [element.name]: element.value };
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearchButton = (
|
||||
searchCollapsed: boolean,
|
||||
setSearchCollapsed: Dispatch<SetStateAction<boolean>>,
|
||||
setInputFocus: () => void,
|
||||
searchInputRef: RefObject<HTMLInputElement>,
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>
|
||||
) => {
|
||||
if (searchCollapsed) {
|
||||
setSearchCollapsed(false);
|
||||
setInputFocus();
|
||||
} else if (searchInputRef && searchInputRef.current) {
|
||||
const value = searchInputRef.current.value;
|
||||
|
||||
setSearchQuery((prev) => {
|
||||
return { ...prev, search: value };
|
||||
});
|
||||
setSearchCollapsed(true);
|
||||
|
||||
searchInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
export { handleFiltersButton, handleSelectChange, handleSearchButton };
|
@ -1,5 +1,4 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
@ -14,25 +13,31 @@ import SearchIcon from './search.svg';
|
||||
import { IFilterQuery } from '../types';
|
||||
import { useFocus } from '../utils';
|
||||
import Logotype from '../Logotype';
|
||||
import {
|
||||
filtersVariants,
|
||||
navVariants,
|
||||
searchVariants,
|
||||
transition
|
||||
} from './animations';
|
||||
import {
|
||||
handleFiltersButton,
|
||||
handleSearchButton,
|
||||
handleSelectChange
|
||||
} from './handlers';
|
||||
|
||||
const Navbar = ({
|
||||
setSearchQuery,
|
||||
query
|
||||
}: {
|
||||
type props = {
|
||||
setSearchQuery: Dispatch<SetStateAction<IFilterQuery>>;
|
||||
query: IFilterQuery;
|
||||
}) => {
|
||||
/*
|
||||
* Hooks
|
||||
*/
|
||||
};
|
||||
|
||||
const Navbar: React.FC<props> = ({ setSearchQuery, query }) => {
|
||||
const [searchCollapsed, setSearchCollapsed] = useState(true);
|
||||
const [filtersCollapsed, setFiltersCollapsed] = useState(true);
|
||||
const [localFilters, setLocalFilters] = useState<Partial<IFilterQuery>>();
|
||||
const [localFilters, setLocalFilters] = useState<Partial<IFilterQuery>>({});
|
||||
|
||||
const searchInput = useRef<HTMLInputElement>(null);
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const setInputFocus = useFocus(searchInput);
|
||||
const setInputFocus = useFocus(searchInputRef);
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
@ -48,89 +53,6 @@ const Navbar = ({
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
/*
|
||||
* Animations
|
||||
*/
|
||||
|
||||
const searchVariants = {
|
||||
open: {
|
||||
width: 'calc(100vw - 4vh)',
|
||||
display: 'block'
|
||||
},
|
||||
closed: {
|
||||
width: '6vh',
|
||||
transitionEnd: {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navVariants = {
|
||||
open: {
|
||||
height: '100vh',
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0
|
||||
},
|
||||
closed: {
|
||||
height: '10vh',
|
||||
borderTopLeftRadius: '20px',
|
||||
borderTopRightRadius: '20px'
|
||||
}
|
||||
};
|
||||
|
||||
const filtersVariants = {
|
||||
open: {
|
||||
height: '100vh',
|
||||
padding: '2vh'
|
||||
},
|
||||
closed: {
|
||||
height: 0,
|
||||
padding: 0
|
||||
}
|
||||
};
|
||||
|
||||
const transition = {
|
||||
ease: 'easeIn',
|
||||
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 (
|
||||
<motion.header
|
||||
id="navbar"
|
||||
@ -146,7 +68,14 @@ const Navbar = ({
|
||||
<button
|
||||
className="navButton"
|
||||
id="filter"
|
||||
onClick={handleFiltersButton}
|
||||
onClick={() =>
|
||||
handleFiltersButton(
|
||||
filtersCollapsed,
|
||||
localFilters,
|
||||
setSearchQuery,
|
||||
setFiltersCollapsed
|
||||
)
|
||||
}
|
||||
>
|
||||
<img src={FilterIcon} alt="Фильтр" />
|
||||
</button>
|
||||
@ -154,7 +83,15 @@ const Navbar = ({
|
||||
<button
|
||||
className="navButton"
|
||||
id="search"
|
||||
onClick={handleSearchButton}
|
||||
onClick={() =>
|
||||
handleSearchButton(
|
||||
searchCollapsed,
|
||||
setSearchCollapsed,
|
||||
setInputFocus,
|
||||
searchInputRef,
|
||||
setSearchQuery
|
||||
)
|
||||
}
|
||||
>
|
||||
<img src={SearchIcon} alt="Поиск" />
|
||||
</button>
|
||||
@ -163,10 +100,16 @@ const Navbar = ({
|
||||
variants={searchVariants}
|
||||
transition={transition}
|
||||
aria-label="Поиск"
|
||||
ref={searchInput}
|
||||
ref={searchInputRef}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearchButton();
|
||||
handleSearchButton(
|
||||
searchCollapsed,
|
||||
setSearchCollapsed,
|
||||
setInputFocus,
|
||||
searchInputRef,
|
||||
setSearchQuery
|
||||
);
|
||||
}
|
||||
}}
|
||||
type="search"
|
||||
@ -188,7 +131,9 @@ const Navbar = ({
|
||||
</label>
|
||||
<select
|
||||
name="teacher"
|
||||
onChange={handleSelectChange}
|
||||
onChange={(e) =>
|
||||
handleSelectChange(e.target, setLocalFilters)
|
||||
}
|
||||
id="teacherFilter"
|
||||
>
|
||||
<option value="">-</option>
|
||||
@ -213,7 +158,9 @@ const Navbar = ({
|
||||
</label>
|
||||
<select
|
||||
name="type_num"
|
||||
onChange={handleSelectChange}
|
||||
onChange={(e) =>
|
||||
handleSelectChange(e.target, setLocalFilters)
|
||||
}
|
||||
id="typeFilter"
|
||||
>
|
||||
<option value="">-</option>
|
||||
@ -228,7 +175,9 @@ const Navbar = ({
|
||||
</label>
|
||||
<select
|
||||
name="predmet_type"
|
||||
onChange={handleSelectChange}
|
||||
onChange={(e) =>
|
||||
handleSelectChange(e.target, setLocalFilters)
|
||||
}
|
||||
id="predmetFilter"
|
||||
>
|
||||
<option value="">-</option>
|
||||
@ -243,7 +192,9 @@ const Navbar = ({
|
||||
</label>
|
||||
<select
|
||||
name="class_num"
|
||||
onChange={handleSelectChange}
|
||||
onChange={(e) =>
|
||||
handleSelectChange(e.target, setLocalFilters)
|
||||
}
|
||||
id="classFilter"
|
||||
>
|
||||
<option value="">-</option>
|
||||
|
@ -4,11 +4,11 @@ import './main.css';
|
||||
import icon from './icon.svg';
|
||||
import { ILoadingState } from '../types';
|
||||
|
||||
const NothingFound = ({
|
||||
setLoading
|
||||
}: {
|
||||
type props = {
|
||||
setLoading?: Dispatch<SetStateAction<ILoadingState>>;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const NothingFound: React.FC<props> = ({ setLoading }) => {
|
||||
useEffect(() => {
|
||||
if (!setLoading) return;
|
||||
|
||||
|
@ -7,13 +7,12 @@ import Card from '../Card';
|
||||
import './main.css';
|
||||
import NothingFound from '../NothingFound';
|
||||
|
||||
const SubjectList = ({
|
||||
setLoading,
|
||||
searchQuery
|
||||
}: {
|
||||
type props = {
|
||||
setLoading: Dispatch<SetStateAction<ILoadingState>>;
|
||||
searchQuery: IFilterQuery;
|
||||
}) => {
|
||||
};
|
||||
|
||||
const SubjectList: React.FC<props> = ({ setLoading, searchQuery }) => {
|
||||
const [data, setData] = useState<IData[]>([]);
|
||||
|
||||
const { push: historyPush } = useHistory();
|
||||
|
Loading…
x
Reference in New Issue
Block a user