add filter
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import FilterActive from './filters-active.svg';
|
import iconFilterActive from './filters-active.svg';
|
||||||
import Filter from './filters.svg';
|
import iconFilter from './filters.svg';
|
||||||
import Sort from './sort.svg';
|
import iconSort from './sort.svg';
|
||||||
import SortActive from './sort-active.svg';
|
import iconSortActive from './sort-active.svg';
|
||||||
|
import iconSearch from './search.svg';
|
||||||
|
|
||||||
export { Filter, FilterActive, Sort, SortActive };
|
export { iconFilter, iconFilterActive, iconSort, iconSortActive, iconSearch };
|
||||||
|
|||||||
3
src/assets/icons/filters/search.svg
Normal file
3
src/assets/icons/filters/search.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16.927 17.04L20.4001 20.4M19.2801 11.44C19.2801 15.7699 15.77 19.28 11.4401 19.28C7.11019 19.28 3.6001 15.7699 3.6001 11.44C3.6001 7.11009 7.11019 3.60001 11.4401 3.60001C15.77 3.60001 19.2801 7.11009 19.2801 11.44Z" stroke="#576466" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 389 B |
@@ -2,119 +2,124 @@ import React from 'react';
|
|||||||
import { cn } from '../../lib/cn';
|
import { cn } from '../../lib/cn';
|
||||||
import { checkMark } from '../../assets/icons/input';
|
import { checkMark } from '../../assets/icons/input';
|
||||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||||
import { Sort, SortActive } from '../../assets/icons/filters';
|
import { iconFilter, iconFilterActive } from '../../assets/icons/filters';
|
||||||
|
|
||||||
export interface FilterItem {
|
export interface FilterItem {
|
||||||
text: string;
|
text: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FilterProps {
|
interface FilterProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onChange: (items: FilterItem[]) => void;
|
onChange: (items: FilterItem[]) => void;
|
||||||
defaultState?: FilterItem[];
|
defaultState?: FilterItem[];
|
||||||
items: FilterItem[];
|
items: FilterItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Filter: React.FC<FilterProps> = ({
|
export const FilterDropDown: React.FC<FilterProps> = ({
|
||||||
disabled = false,
|
disabled = false,
|
||||||
className = '',
|
className = '',
|
||||||
onChange,
|
onChange,
|
||||||
defaultState = [],
|
defaultState = [],
|
||||||
items = [],
|
items = [],
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = React.useState<FilterItem[]>(defaultState);
|
const [value, setValue] = React.useState<FilterItem[]>(defaultState);
|
||||||
const [active, setActive] = React.useState(false);
|
const [active, setActive] = React.useState(false);
|
||||||
|
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
const ref = React.useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useClickOutside(ref, () => {
|
useClickOutside(ref, () => {
|
||||||
setActive(false);
|
setActive(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
onChange(value);
|
onChange(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const toggleItem = (item: FilterItem) => {
|
const toggleItem = (item: FilterItem) => {
|
||||||
const exists = value.some((val) => val.value === item.value);
|
const exists = value.some((val) => val.value === item.value);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
setValue(value.filter((val) => val.value !== item.value));
|
setValue(value.filter((val) => val.value !== item.value));
|
||||||
} else {
|
} else {
|
||||||
setValue([...value, item]);
|
setValue([...value, item]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative', className)} ref={ref}>
|
<div className={cn('relative', className)} ref={ref}>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'items-center h-[40px] rounded-full bg-liquid-lighter w-[40px] flex',
|
'items-center h-[40px] rounded-full bg-liquid-lighter w-[40px] flex',
|
||||||
'text-[18px] font-bold cursor-pointer select-none',
|
'text-[18px] font-bold cursor-pointer select-none',
|
||||||
'overflow-hidden',
|
'overflow-hidden',
|
||||||
(active || value.length > 0) && 'w-fit border-liquid-brightmain border-[1px] border-solid',
|
(active || value.length > 0) &&
|
||||||
)}
|
'w-fit border-liquid-brightmain border-[1px] border-solid',
|
||||||
onClick={() => {
|
)}
|
||||||
if (!disabled) setActive(!active);
|
onClick={() => {
|
||||||
}}
|
if (!disabled) setActive(!active);
|
||||||
>
|
}}
|
||||||
<div className="text-liquid-brightmain pl-[42px] pr-[16px] w-fit">
|
>
|
||||||
{value.length}
|
<div className="text-liquid-brightmain pl-[42px] pr-[16px] w-fit">
|
||||||
</div>
|
{value.length}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sort icons */}
|
|
||||||
<img
|
|
||||||
src={Sort}
|
|
||||||
className={cn(
|
|
||||||
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={SortActive}
|
|
||||||
className={cn(
|
|
||||||
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
|
||||||
(active || value.length > 0) && 'opacity-100',
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Dropdown */}
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'absolute rounded-[10px] bg-liquid-lighter w-[460px] left-0 top-[48px] z-50 transition-all duration-300',
|
|
||||||
'grid overflow-hidden',
|
|
||||||
active ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="overflow-hidden p-[8px]">
|
|
||||||
<div className="overflow-y-scroll max-h-[200px] thin-scrollbar pr-[8px] grid grid-cols-2 gap-[20px]">
|
|
||||||
{items.map((v) => {
|
|
||||||
const selected = value.some((val) => val.value === v.value);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={v.value}
|
|
||||||
className={cn(
|
|
||||||
'cursor-pointer h-[36px] relative transition-all duration-300',
|
|
||||||
'text-[16px] font-medium select-none flex items-center pl-[8px]',
|
|
||||||
'hover:bg-liquid-background rounded-[10px]',
|
|
||||||
selected && 'bg-liquid-background/50',
|
|
||||||
)}
|
|
||||||
onClick={() => toggleItem(v)}
|
|
||||||
>
|
|
||||||
{v.text}
|
|
||||||
{selected && (
|
|
||||||
<img
|
|
||||||
src={checkMark}
|
|
||||||
className="absolute right-[8px] h-[20px] w-[20px]"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
})}
|
|
||||||
</div>
|
{/* Filter icons */}
|
||||||
|
<img
|
||||||
|
src={iconFilter}
|
||||||
|
className={cn(
|
||||||
|
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={iconFilterActive}
|
||||||
|
className={cn(
|
||||||
|
'absolute left-[8px] top-[8px] h-[24px] w-[24px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
||||||
|
(active || value.length > 0) && 'opacity-100',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Dropdown */}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute rounded-[10px] bg-liquid-lighter w-[460px] left-0 top-[48px] z-50 transition-all duration-300',
|
||||||
|
'grid overflow-hidden',
|
||||||
|
active
|
||||||
|
? 'grid-rows-[1fr] opacity-100'
|
||||||
|
: 'grid-rows-[0fr] opacity-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden p-[8px]">
|
||||||
|
<div className="overflow-y-scroll max-h-[200px] thin-scrollbar pr-[8px] grid grid-cols-2 gap-[20px]">
|
||||||
|
{items.map((v) => {
|
||||||
|
const selected = value.some(
|
||||||
|
(val) => val.value === v.value,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={v.value}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer h-[36px] relative transition-all duration-300',
|
||||||
|
'text-[16px] font-medium select-none flex items-center pl-[8px]',
|
||||||
|
'hover:bg-liquid-background rounded-[10px]',
|
||||||
|
selected && 'bg-liquid-background/50',
|
||||||
|
)}
|
||||||
|
onClick={() => toggleItem(v)}
|
||||||
|
>
|
||||||
|
{v.text}
|
||||||
|
{selected && (
|
||||||
|
<img
|
||||||
|
src={checkMark}
|
||||||
|
className="absolute right-[8px] h-[20px] w-[20px]"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import { FC, useEffect, useRef, useState } from 'react';
|
||||||
import { cn } from '../../lib/cn';
|
import { cn } from '../../lib/cn';
|
||||||
import { checkMark } from '../../assets/icons/input';
|
import { checkMark } from '../../assets/icons/input';
|
||||||
import { useClickOutside } from '../../hooks/useClickOutside';
|
import { useClickOutside } from '../../hooks/useClickOutside';
|
||||||
import { Sort, SortActive } from '../../assets/icons/filters';
|
import { iconSort, iconSortActive } from '../../assets/icons/filters';
|
||||||
|
|
||||||
export interface SorterItem {
|
export interface SorterItem {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -17,7 +17,7 @@ interface SorterProps {
|
|||||||
items: SorterItem[];
|
items: SorterItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Sorter: React.FC<SorterProps> = ({
|
export const SorterDropDown: FC<SorterProps> = ({
|
||||||
// disabled = false,
|
// disabled = false,
|
||||||
className = '',
|
className = '',
|
||||||
onChange,
|
onChange,
|
||||||
@@ -26,14 +26,15 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (items.length == 0) items.push({ text: '', value: '' });
|
if (items.length == 0) items.push({ text: '', value: '' });
|
||||||
|
|
||||||
const [value, setValue] = React.useState<SorterItem>(
|
const [value, setValue] = useState<SorterItem>(
|
||||||
defaultState != undefined ? defaultState : items[0],
|
defaultState != undefined ? defaultState : items[0],
|
||||||
);
|
);
|
||||||
const [active, setActive] = React.useState<boolean>(false);
|
const [active, setActive] = useState<boolean>(false);
|
||||||
|
const [activate, setActivate] = useState<Boolean>(false);
|
||||||
|
|
||||||
React.useEffect(() => onChange(value.value), [value]);
|
useEffect(() => onChange(value.value), [value]);
|
||||||
|
|
||||||
const ref = React.useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useClickOutside(ref, () => {
|
useClickOutside(ref, () => {
|
||||||
setActive(false);
|
setActive(false);
|
||||||
@@ -46,7 +47,8 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]',
|
'grid items-center h-[40px] rounded-full bg-liquid-lighter grid-cols-[40px]',
|
||||||
'text-[18px] font-bold cursor-pointer select-none',
|
'text-[18px] font-bold cursor-pointer select-none',
|
||||||
'overflow-hidden',
|
'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={() => {
|
onClick={() => {
|
||||||
setActive(!active);
|
setActive(!active);
|
||||||
@@ -63,18 +65,17 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-[24px] w-[24px]"></div>
|
|
||||||
<img
|
<img
|
||||||
src={Sort}
|
src={iconSort}
|
||||||
className={cn(
|
className={cn(
|
||||||
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none',
|
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
src={SortActive}
|
src={iconSortActive}
|
||||||
className={cn(
|
className={cn(
|
||||||
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
' absolute right-[16px] h-[24px] w-[24px] top-[8px] left-[8px] rotate-0 transition-all duration-300 pointer-events-none opacity-0',
|
||||||
active && ' opacity-100',
|
(active || activate) && ' opacity-100',
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -107,6 +108,7 @@ export const Sorter: React.FC<SorterProps> = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setValue(v);
|
setValue(v);
|
||||||
setActive(false);
|
setActive(false);
|
||||||
|
setActivate(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{v.text}
|
{v.text}
|
||||||
|
|||||||
59
src/components/input/SearchInput.tsx
Normal file
59
src/components/input/SearchInput.tsx
Normal file
@@ -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<HTMLInputElement>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchInput: React.FC<searchInputProps> = ({
|
||||||
|
placeholder = '',
|
||||||
|
className = '',
|
||||||
|
onChange,
|
||||||
|
defaultState = '',
|
||||||
|
name = '',
|
||||||
|
autocomplete = '',
|
||||||
|
onKeyDown,
|
||||||
|
}) => {
|
||||||
|
const [value, setValue] = React.useState<string>(defaultState);
|
||||||
|
|
||||||
|
React.useEffect(() => onChange(value), [value]);
|
||||||
|
React.useEffect(() => setValue(defaultState), [defaultState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={cn(
|
||||||
|
'relative bg-liquid-lighter w-[200px] h-[40px] rounded-full px-[16px] pl-[50px] py-[8px] cursor-text',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className={cn(
|
||||||
|
'placeholder:text-liquid-light w-full bg-transparent outline-none text-liquid-white',
|
||||||
|
)}
|
||||||
|
value={value}
|
||||||
|
name={name}
|
||||||
|
autoComplete={autocomplete}
|
||||||
|
type="text"
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue(e.target.value);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (onKeyDown) onKeyDown(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<img src={iconSearch} className=" absolute top-[8px] left-[16px]" />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { cn } from '../../../../lib/cn';
|
import { cn } from '../../../../lib/cn';
|
||||||
import { IconError, IconSuccess } from '../../../../assets/icons/missions';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Edit } from '../../../../assets/icons/input';
|
import { Edit } from '../../../../assets/icons/input';
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ArticleItem from './ArticleItem';
|
|||||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { fetchArticles } from '../../../redux/slices/articles';
|
import { fetchArticles } from '../../../redux/slices/articles';
|
||||||
|
import Filters from './Filter';
|
||||||
|
|
||||||
export interface Article {
|
export interface Article {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -42,7 +43,7 @@ const Articles = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-liquid-lighter h-[50px] mb-[20px]"></div>
|
<Filters />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{articles.map((v, i) => (
|
{articles.map((v, i) => (
|
||||||
|
|||||||
51
src/views/home/articles/Filter.tsx
Normal file
51
src/views/home/articles/Filter.tsx
Normal file
@@ -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 (
|
||||||
|
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||||
|
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
|
||||||
|
|
||||||
|
<SorterDropDown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: '1',
|
||||||
|
text: 'Сложность',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '2',
|
||||||
|
text: 'Дата создания',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '3',
|
||||||
|
text: 'ID',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(v) => console.log(v)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FilterDropDown
|
||||||
|
items={items}
|
||||||
|
defaultState={[]}
|
||||||
|
onChange={(values) => console.log(values)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
||||||
@@ -6,6 +6,7 @@ import ContestsBlock from './ContestsBlock';
|
|||||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||||
import { fetchContests } from '../../../redux/slices/contests';
|
import { fetchContests } from '../../../redux/slices/contests';
|
||||||
import ModalCreateContest from './ModalCreate';
|
import ModalCreateContest from './ModalCreate';
|
||||||
|
import Filters from './Filter';
|
||||||
|
|
||||||
const Contests = () => {
|
const Contests = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -48,16 +49,24 @@ const Contests = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-liquid-lighter h-[50px] mb-[20px]" />
|
<Filters />
|
||||||
{status == 'loading' && <div className="text-liquid-white p-4">Загрузка контестов...</div>}
|
{status == 'loading' && (
|
||||||
{status == 'failed' && <div className="text-red-500 p-4">Ошибка: {error}</div>}
|
<div className="text-liquid-white p-4">
|
||||||
{status == 'successful' &&
|
Загрузка контестов...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{status == 'failed' && (
|
||||||
|
<div className="text-red-500 p-4">Ошибка: {error}</div>
|
||||||
|
)}
|
||||||
|
{status == 'successful' && (
|
||||||
<>
|
<>
|
||||||
<ContestsBlock
|
<ContestsBlock
|
||||||
className="mb-[20px]"
|
className="mb-[20px]"
|
||||||
title="Текущие"
|
title="Текущие"
|
||||||
contests={contests.filter((contest) => {
|
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();
|
return endTime >= now.getTime();
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
@@ -66,12 +75,14 @@ const Contests = () => {
|
|||||||
className="mb-[20px]"
|
className="mb-[20px]"
|
||||||
title="Прошедшие"
|
title="Прошедшие"
|
||||||
contests={contests.filter((contest) => {
|
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();
|
return endTime < now.getTime();
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ModalCreateContest
|
<ModalCreateContest
|
||||||
|
|||||||
51
src/views/home/contests/Filter.tsx
Normal file
51
src/views/home/contests/Filter.tsx
Normal file
@@ -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 (
|
||||||
|
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||||
|
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
|
||||||
|
|
||||||
|
<SorterDropDown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: '1',
|
||||||
|
text: 'Сложность',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '2',
|
||||||
|
text: 'Дата создания',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '3',
|
||||||
|
text: 'ID',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(v) => console.log(v)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FilterDropDown
|
||||||
|
items={items}
|
||||||
|
defaultState={[]}
|
||||||
|
onChange={(values) => console.log(values)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
||||||
51
src/views/home/groups/Filter.tsx
Normal file
51
src/views/home/groups/Filter.tsx
Normal file
@@ -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 (
|
||||||
|
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||||
|
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
|
||||||
|
|
||||||
|
<SorterDropDown
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: '1',
|
||||||
|
text: 'Сложность',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '2',
|
||||||
|
text: 'Дата создания',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '3',
|
||||||
|
text: 'ID',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(v) => console.log(v)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FilterDropDown
|
||||||
|
items={items}
|
||||||
|
defaultState={[]}
|
||||||
|
onChange={(values) => console.log(values)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Filters;
|
||||||
@@ -7,6 +7,7 @@ import { setMenuActivePage } from '../../../redux/slices/store';
|
|||||||
import { fetchMyGroups } from '../../../redux/slices/groups';
|
import { fetchMyGroups } from '../../../redux/slices/groups';
|
||||||
import ModalCreate from './ModalCreate';
|
import ModalCreate from './ModalCreate';
|
||||||
import ModalUpdate from './ModalUpdate';
|
import ModalUpdate from './ModalUpdate';
|
||||||
|
import Filters from './Filter';
|
||||||
|
|
||||||
export interface GroupUpdate {
|
export interface GroupUpdate {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -86,7 +87,7 @@ const Groups = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-liquid-lighter h-[50px] mb-[20px]"></div>
|
<Filters />
|
||||||
|
|
||||||
<GroupsBlock
|
<GroupsBlock
|
||||||
className="mb-[20px]"
|
className="mb-[20px]"
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Filter, FilterItem } from '../../../components/drop-down-list/Filter';
|
import {
|
||||||
import { Sorter } from '../../../components/drop-down-list/Sorter';
|
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 Filters = () => {
|
||||||
const items: FilterItem[] = [
|
const items: FilterItem[] = [
|
||||||
@@ -14,10 +18,10 @@ const Filters = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" h-[50px] mb-[20px] flex gap-[20px]">
|
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||||
<div></div>
|
<SearchInput onChange={() => {}} placeholder="Поиск задачи" />
|
||||||
|
|
||||||
<Sorter
|
<SorterDropDown
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
value: '1',
|
value: '1',
|
||||||
@@ -35,7 +39,7 @@ const Filters = () => {
|
|||||||
onChange={(v) => console.log(v)}
|
onChange={(v) => console.log(v)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Filter
|
<FilterDropDown
|
||||||
items={items}
|
items={items}
|
||||||
defaultState={[]}
|
defaultState={[]}
|
||||||
onChange={(values) => console.log(values)}
|
onChange={(values) => console.log(values)}
|
||||||
|
|||||||
@@ -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"}
|
{"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"}
|
||||||
Reference in New Issue
Block a user