diff --git a/src/assets/icons/filters/index.ts b/src/assets/icons/filters/index.ts index 2eca117..4a4b8a6 100644 --- a/src/assets/icons/filters/index.ts +++ b/src/assets/icons/filters/index.ts @@ -1,6 +1,7 @@ -import FilterActive from './filters-active.svg'; -import Filter from './filters.svg'; -import Sort from './sort.svg'; -import SortActive from './sort-active.svg'; +import iconFilterActive from './filters-active.svg'; +import iconFilter from './filters.svg'; +import iconSort from './sort.svg'; +import iconSortActive from './sort-active.svg'; +import iconSearch from './search.svg'; -export { Filter, FilterActive, Sort, SortActive }; +export { iconFilter, iconFilterActive, iconSort, iconSortActive, iconSearch }; diff --git a/src/assets/icons/filters/search.svg b/src/assets/icons/filters/search.svg new file mode 100644 index 0000000..7827a6e --- /dev/null +++ b/src/assets/icons/filters/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/drop-down-list/Filter.tsx b/src/components/drop-down-list/Filter.tsx index d0d47f8..5ef647f 100644 --- a/src/components/drop-down-list/Filter.tsx +++ b/src/components/drop-down-list/Filter.tsx @@ -2,119 +2,124 @@ import React from 'react'; import { cn } from '../../lib/cn'; import { checkMark } from '../../assets/icons/input'; import { useClickOutside } from '../../hooks/useClickOutside'; -import { Sort, SortActive } from '../../assets/icons/filters'; +import { iconFilter, iconFilterActive } from '../../assets/icons/filters'; export interface FilterItem { - text: string; - value: string; + text: string; + value: string; } interface FilterProps { - disabled?: boolean; - className?: string; - onChange: (items: FilterItem[]) => void; - defaultState?: FilterItem[]; - items: FilterItem[]; + disabled?: boolean; + className?: string; + onChange: (items: FilterItem[]) => void; + defaultState?: FilterItem[]; + items: FilterItem[]; } -export const Filter: React.FC = ({ - disabled = false, - className = '', - onChange, - defaultState = [], - items = [], +export const FilterDropDown: React.FC = ({ + disabled = false, + className = '', + onChange, + defaultState = [], + items = [], }) => { - const [value, setValue] = React.useState(defaultState); - const [active, setActive] = React.useState(false); + const [value, setValue] = React.useState(defaultState); + const [active, setActive] = React.useState(false); - const ref = React.useRef(null); + const ref = React.useRef(null); - useClickOutside(ref, () => { - setActive(false); - }); + useClickOutside(ref, () => { + setActive(false); + }); - React.useEffect(() => { - onChange(value); - }, [value]); + React.useEffect(() => { + onChange(value); + }, [value]); - const toggleItem = (item: FilterItem) => { - const exists = value.some((val) => val.value === item.value); - if (exists) { - setValue(value.filter((val) => val.value !== item.value)); - } else { - setValue([...value, item]); - } - }; + const toggleItem = (item: FilterItem) => { + const exists = value.some((val) => val.value === item.value); + if (exists) { + setValue(value.filter((val) => val.value !== item.value)); + } else { + setValue([...value, item]); + } + }; - return ( -
-
0) && 'w-fit border-liquid-brightmain border-[1px] border-solid', - )} - onClick={() => { - if (!disabled) setActive(!active); - }} - > -
- {value.length} -
-
- - {/* Sort icons */} - - 0) && 'opacity-100', - )} - /> - - {/* Dropdown */} -
-
-
- {items.map((v) => { - const selected = value.some((val) => val.value === v.value); - return ( -
toggleItem(v)} - > - {v.text} - {selected && ( - - )} + return ( +
+
0) && + 'w-fit border-liquid-brightmain border-[1px] border-solid', + )} + onClick={() => { + if (!disabled) setActive(!active); + }} + > +
+ {value.length}
- ); - })} -
+
+ + {/* Filter icons */} + + 0) && 'opacity-100', + )} + /> + + {/* Dropdown */} +
+
+
+ {items.map((v) => { + const selected = value.some( + (val) => val.value === v.value, + ); + return ( +
toggleItem(v)} + > + {v.text} + {selected && ( + + )} +
+ ); + })} +
+
+
-
-
- ); + ); }; diff --git a/src/components/drop-down-list/Sorter.tsx b/src/components/drop-down-list/Sorter.tsx index 7ce3b85..49ca751 100644 --- a/src/components/drop-down-list/Sorter.tsx +++ b/src/components/drop-down-list/Sorter.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { cn } from '../../lib/cn'; import { checkMark } from '../../assets/icons/input'; import { useClickOutside } from '../../hooks/useClickOutside'; -import { Sort, SortActive } from '../../assets/icons/filters'; +import { iconSort, iconSortActive } from '../../assets/icons/filters'; export interface SorterItem { text: string; @@ -17,7 +17,7 @@ interface SorterProps { items: SorterItem[]; } -export const Sorter: React.FC = ({ +export const SorterDropDown: FC = ({ // disabled = false, className = '', onChange, @@ -26,14 +26,15 @@ export const Sorter: React.FC = ({ }) => { if (items.length == 0) items.push({ text: '', value: '' }); - const [value, setValue] = React.useState( + const [value, setValue] = useState( defaultState != undefined ? defaultState : items[0], ); - const [active, setActive] = React.useState(false); + const [active, setActive] = useState(false); + const [activate, setActivate] = useState(false); - React.useEffect(() => onChange(value.value), [value]); + useEffect(() => onChange(value.value), [value]); - const ref = React.useRef(null); + const ref = useRef(null); useClickOutside(ref, () => { setActive(false); @@ -46,7 +47,8 @@ export const Sorter: React.FC = ({ 'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]', 'text-[18px] font-bold cursor-pointer select-none', 'overflow-hidden', - active && ' grid-cols-[1fr] border-liquid-brightmain border-[1px] border-solid', + (active || activate) && + ' grid-cols-[1fr] border-liquid-brightmain border-[1px] border-solid', )} onClick={() => { setActive(!active); @@ -63,18 +65,17 @@ export const Sorter: React.FC = ({
-
@@ -107,6 +108,7 @@ export const Sorter: React.FC = ({ onClick={() => { setValue(v); setActive(false); + setActivate(true); }} > {v.text} diff --git a/src/components/input/SearchInput.tsx b/src/components/input/SearchInput.tsx new file mode 100644 index 0000000..f091b4f --- /dev/null +++ b/src/components/input/SearchInput.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { cn } from '../../lib/cn'; +import { iconSearch } from '../../assets/icons/filters'; + +interface searchInputProps { + name?: string; + error?: string; + disabled?: boolean; + required?: boolean; + label?: string; + placeholder?: string; + className?: string; + onChange: (state: string) => void; + defaultState?: string; + autocomplete?: string; + onKeyDown?: (e: React.KeyboardEvent) => void; +} + +export const SearchInput: React.FC = ({ + placeholder = '', + className = '', + onChange, + defaultState = '', + name = '', + autocomplete = '', + onKeyDown, +}) => { + const [value, setValue] = React.useState(defaultState); + + React.useEffect(() => onChange(value), [value]); + React.useEffect(() => setValue(defaultState), [defaultState]); + + return ( + + ); +}; diff --git a/src/views/home/account/missions/MyMissionItem.tsx b/src/views/home/account/missions/MyMissionItem.tsx index 18b1ef3..5fa27f0 100644 --- a/src/views/home/account/missions/MyMissionItem.tsx +++ b/src/views/home/account/missions/MyMissionItem.tsx @@ -1,5 +1,4 @@ import { cn } from '../../../../lib/cn'; -import { IconError, IconSuccess } from '../../../../assets/icons/missions'; import { useNavigate } from 'react-router-dom'; import { Edit } from '../../../../assets/icons/input'; diff --git a/src/views/home/articles/Articles.tsx b/src/views/home/articles/Articles.tsx index 13519fd..5d124a8 100644 --- a/src/views/home/articles/Articles.tsx +++ b/src/views/home/articles/Articles.tsx @@ -5,6 +5,7 @@ import ArticleItem from './ArticleItem'; import { setMenuActivePage } from '../../../redux/slices/store'; import { useNavigate } from 'react-router-dom'; import { fetchArticles } from '../../../redux/slices/articles'; +import Filters from './Filter'; export interface Article { id: number; @@ -42,7 +43,7 @@ const Articles = () => { /> -
+
{articles.map((v, i) => ( diff --git a/src/views/home/articles/Filter.tsx b/src/views/home/articles/Filter.tsx new file mode 100644 index 0000000..ca01a9d --- /dev/null +++ b/src/views/home/articles/Filter.tsx @@ -0,0 +1,51 @@ +import { + FilterDropDown, + FilterItem, +} from '../../../components/drop-down-list/Filter'; +import { SorterDropDown } from '../../../components/drop-down-list/Sorter'; +import { SearchInput } from '../../../components/input/SearchInput'; + +const Filters = () => { + const items: FilterItem[] = [ + { text: 'React', value: 'react' }, + { text: 'Vue', value: 'vue' }, + { text: 'Angular', value: 'angular' }, + { text: 'Svelte', value: 'svelte' }, + { text: 'Next.js', value: 'next' }, + { text: 'Nuxt', value: 'nuxt' }, + { text: 'Solid', value: 'solid' }, + { text: 'Qwik', value: 'qwik' }, + ]; + + return ( +
+ {}} placeholder="Поиск задачи" /> + + console.log(v)} + /> + + console.log(values)} + /> +
+ ); +}; + +export default Filters; diff --git a/src/views/home/contests/Contests.tsx b/src/views/home/contests/Contests.tsx index 999ee29..7e33094 100644 --- a/src/views/home/contests/Contests.tsx +++ b/src/views/home/contests/Contests.tsx @@ -6,6 +6,7 @@ import ContestsBlock from './ContestsBlock'; import { setMenuActivePage } from '../../../redux/slices/store'; import { fetchContests } from '../../../redux/slices/contests'; import ModalCreateContest from './ModalCreate'; +import Filters from './Filter'; const Contests = () => { const dispatch = useAppDispatch(); @@ -48,16 +49,24 @@ const Contests = () => { />
-
- {status == 'loading' &&
Загрузка контестов...
} - {status == 'failed' &&
Ошибка: {error}
} - {status == 'successful' && + + {status == 'loading' && ( +
+ Загрузка контестов... +
+ )} + {status == 'failed' && ( +
Ошибка: {error}
+ )} + {status == 'successful' && ( <> { - const endTime = new Date(contest.endsAt ?? new Date().toDateString()).getTime(); + const endTime = new Date( + contest.endsAt ?? new Date().toDateString(), + ).getTime(); return endTime >= now.getTime(); })} /> @@ -66,12 +75,14 @@ const Contests = () => { className="mb-[20px]" title="Прошедшие" contests={contests.filter((contest) => { - const endTime = new Date(contest.endsAt ?? new Date().toDateString()).getTime(); + const endTime = new Date( + contest.endsAt ?? new Date().toDateString(), + ).getTime(); return endTime < now.getTime(); })} /> - } + )}
{ + const items: FilterItem[] = [ + { text: 'React', value: 'react' }, + { text: 'Vue', value: 'vue' }, + { text: 'Angular', value: 'angular' }, + { text: 'Svelte', value: 'svelte' }, + { text: 'Next.js', value: 'next' }, + { text: 'Nuxt', value: 'nuxt' }, + { text: 'Solid', value: 'solid' }, + { text: 'Qwik', value: 'qwik' }, + ]; + + return ( +
+ {}} placeholder="Поиск задачи" /> + + console.log(v)} + /> + + console.log(values)} + /> +
+ ); +}; + +export default Filters; diff --git a/src/views/home/groups/Filter.tsx b/src/views/home/groups/Filter.tsx new file mode 100644 index 0000000..ca01a9d --- /dev/null +++ b/src/views/home/groups/Filter.tsx @@ -0,0 +1,51 @@ +import { + FilterDropDown, + FilterItem, +} from '../../../components/drop-down-list/Filter'; +import { SorterDropDown } from '../../../components/drop-down-list/Sorter'; +import { SearchInput } from '../../../components/input/SearchInput'; + +const Filters = () => { + const items: FilterItem[] = [ + { text: 'React', value: 'react' }, + { text: 'Vue', value: 'vue' }, + { text: 'Angular', value: 'angular' }, + { text: 'Svelte', value: 'svelte' }, + { text: 'Next.js', value: 'next' }, + { text: 'Nuxt', value: 'nuxt' }, + { text: 'Solid', value: 'solid' }, + { text: 'Qwik', value: 'qwik' }, + ]; + + return ( +
+ {}} placeholder="Поиск задачи" /> + + console.log(v)} + /> + + console.log(values)} + /> +
+ ); +}; + +export default Filters; diff --git a/src/views/home/groups/Groups.tsx b/src/views/home/groups/Groups.tsx index 64b3692..51650e6 100644 --- a/src/views/home/groups/Groups.tsx +++ b/src/views/home/groups/Groups.tsx @@ -7,6 +7,7 @@ import { setMenuActivePage } from '../../../redux/slices/store'; import { fetchMyGroups } from '../../../redux/slices/groups'; import ModalCreate from './ModalCreate'; import ModalUpdate from './ModalUpdate'; +import Filters from './Filter'; export interface GroupUpdate { id: number; @@ -86,7 +87,7 @@ const Groups = () => { /> -
+ { const items: FilterItem[] = [ @@ -14,10 +18,10 @@ const Filters = () => { ]; return ( -
-
+
+ {}} placeholder="Поиск задачи" /> - { onChange={(v) => console.log(v)} /> - console.log(values)} diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index 88b15bb..f725851 100644 --- a/tsconfig.app.tsbuildinfo +++ b/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/axios.ts","./src/main.tsx","./src/vite-env.d.ts","./src/assets/icons/account/index.ts","./src/assets/icons/auth/index.ts","./src/assets/icons/groups/index.ts","./src/assets/icons/header/index.ts","./src/assets/icons/input/index.ts","./src/assets/icons/menu/index.ts","./src/assets/icons/missions/index.ts","./src/assets/logos/index.ts","./src/components/button/primarybutton.tsx","./src/components/button/reversebutton.tsx","./src/components/button/secondarybutton.tsx","./src/components/checkbox/checkbox.tsx","./src/components/drop-down-list/dropdownlist.tsx","./src/components/input/daterangeinput.tsx","./src/components/input/input.tsx","./src/components/modal/modal.tsx","./src/components/router/protectedroute.tsx","./src/components/switch/switch.tsx","./src/config/colors.ts","./src/hooks/useclickoutside.ts","./src/hooks/usequery.ts","./src/lib/cn.ts","./src/pages/article.tsx","./src/pages/articleeditor.tsx","./src/pages/contesteditor.tsx","./src/pages/home.tsx","./src/pages/mission.tsx","./src/redux/hooks.ts","./src/redux/store.ts","./src/redux/slices/account.ts","./src/redux/slices/articles.ts","./src/redux/slices/auth.ts","./src/redux/slices/contests.ts","./src/redux/slices/groups.ts","./src/redux/slices/missions.ts","./src/redux/slices/store.ts","./src/redux/slices/submit.ts","./src/views/article/header.tsx","./src/views/articleeditor/editor.tsx","./src/views/articleeditor/header.tsx","./src/views/articleeditor/marckdownpreview.tsx","./src/views/home/account/account.tsx","./src/views/home/account/accoutmenu.tsx","./src/views/home/account/rightpanel.tsx","./src/views/home/account/articles/articlesblock.tsx","./src/views/home/account/contests/contests.tsx","./src/views/home/account/contests/contestsblock.tsx","./src/views/home/account/contests/mycontestitem.tsx","./src/views/home/account/contests/registercontestitem.tsx","./src/views/home/account/missions/missionsblock.tsx","./src/views/home/articles/articleitem.tsx","./src/views/home/articles/articles.tsx","./src/views/home/auth/login.tsx","./src/views/home/auth/register.tsx","./src/views/home/contest/contest.tsx","./src/views/home/contest/missionitem.tsx","./src/views/home/contest/missions.tsx","./src/views/home/contest/submissionitem.tsx","./src/views/home/contest/submissions.tsx","./src/views/home/contests/contestitem.tsx","./src/views/home/contests/contests.tsx","./src/views/home/contests/contestsblock.tsx","./src/views/home/contests/modalcreate.tsx","./src/views/home/groups/group.tsx","./src/views/home/groups/groupitem.tsx","./src/views/home/groups/groups.tsx","./src/views/home/groups/groupsblock.tsx","./src/views/home/groups/modalcreate.tsx","./src/views/home/groups/modalupdate.tsx","./src/views/home/menu/menu.tsx","./src/views/home/menu/menuitem.tsx","./src/views/home/missions/missionitem.tsx","./src/views/home/missions/missions.tsx","./src/views/home/missions/modalcreate.tsx","./src/views/mission/codeeditor/codeeditor.tsx","./src/views/mission/statement/header.tsx","./src/views/mission/statement/latextcontainer.tsx","./src/views/mission/statement/missionsubmissions.tsx","./src/views/mission/statement/statement.tsx","./src/views/mission/statement/submissionitem.tsx","./src/views/mission/submission/submission.tsx"],"version":"5.6.2"} \ No newline at end of file +{"root":["./src/app.tsx","./src/axios.ts","./src/main.tsx","./src/vite-env.d.ts","./src/assets/icons/account/index.ts","./src/assets/icons/auth/index.ts","./src/assets/icons/filters/index.ts","./src/assets/icons/groups/index.ts","./src/assets/icons/header/index.ts","./src/assets/icons/input/index.ts","./src/assets/icons/menu/index.ts","./src/assets/icons/missions/index.ts","./src/assets/logos/index.ts","./src/components/button/primarybutton.tsx","./src/components/button/reversebutton.tsx","./src/components/button/secondarybutton.tsx","./src/components/checkbox/checkbox.tsx","./src/components/drop-down-list/dropdownlist.tsx","./src/components/drop-down-list/filter.tsx","./src/components/drop-down-list/sorter.tsx","./src/components/input/daterangeinput.tsx","./src/components/input/input.tsx","./src/components/input/searchinput.tsx","./src/components/modal/modal.tsx","./src/components/router/protectedroute.tsx","./src/components/switch/switch.tsx","./src/config/colors.ts","./src/hooks/useclickoutside.ts","./src/hooks/usequery.ts","./src/lib/cn.ts","./src/pages/article.tsx","./src/pages/articleeditor.tsx","./src/pages/contesteditor.tsx","./src/pages/home.tsx","./src/pages/mission.tsx","./src/redux/hooks.ts","./src/redux/store.ts","./src/redux/slices/articles.ts","./src/redux/slices/auth.ts","./src/redux/slices/contests.ts","./src/redux/slices/groups.ts","./src/redux/slices/missions.ts","./src/redux/slices/store.ts","./src/redux/slices/submit.ts","./src/views/article/header.tsx","./src/views/articleeditor/editor.tsx","./src/views/articleeditor/header.tsx","./src/views/articleeditor/marckdownpreview.tsx","./src/views/home/account/account.tsx","./src/views/home/account/accoutmenu.tsx","./src/views/home/account/rightpanel.tsx","./src/views/home/account/articles/articlesblock.tsx","./src/views/home/account/contests/contests.tsx","./src/views/home/account/contests/contestsblock.tsx","./src/views/home/account/contests/mycontestitem.tsx","./src/views/home/account/contests/registercontestitem.tsx","./src/views/home/account/missions/missions.tsx","./src/views/home/account/missions/missionsblock.tsx","./src/views/home/account/missions/mymissionitem.tsx","./src/views/home/articles/articleitem.tsx","./src/views/home/articles/articles.tsx","./src/views/home/articles/filter.tsx","./src/views/home/auth/login.tsx","./src/views/home/auth/register.tsx","./src/views/home/contest/contest.tsx","./src/views/home/contest/missionitem.tsx","./src/views/home/contest/missions.tsx","./src/views/home/contest/submissionitem.tsx","./src/views/home/contest/submissions.tsx","./src/views/home/contests/contestitem.tsx","./src/views/home/contests/contests.tsx","./src/views/home/contests/contestsblock.tsx","./src/views/home/contests/filter.tsx","./src/views/home/contests/modalcreate.tsx","./src/views/home/groups/filter.tsx","./src/views/home/groups/group.tsx","./src/views/home/groups/groupitem.tsx","./src/views/home/groups/groups.tsx","./src/views/home/groups/groupsblock.tsx","./src/views/home/groups/modalcreate.tsx","./src/views/home/groups/modalupdate.tsx","./src/views/home/menu/menu.tsx","./src/views/home/menu/menuitem.tsx","./src/views/home/missions/filter.tsx","./src/views/home/missions/missionitem.tsx","./src/views/home/missions/missions.tsx","./src/views/home/missions/modalcreate.tsx","./src/views/mission/codeeditor/codeeditor.tsx","./src/views/mission/statement/header.tsx","./src/views/mission/statement/latextcontainer.tsx","./src/views/mission/statement/missionsubmissions.tsx","./src/views/mission/statement/statement.tsx","./src/views/mission/statement/submissionitem.tsx","./src/views/mission/submission/submission.tsx"],"version":"5.6.2"} \ No newline at end of file