From b12a3acf1d711885b31293a0a99a07310c3e7274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Sat, 8 Nov 2025 06:58:56 +0300 Subject: [PATCH] missions and filter --- src/assets/icons/filters/filters-active.svg | 3 + src/assets/icons/filters/filters.svg | 3 + src/assets/icons/filters/index.ts | 6 + src/assets/icons/filters/sort-active.svg | 3 + src/assets/icons/filters/sort.svg | 3 + src/components/button/PrimaryButton.tsx | 11 +- src/components/button/ReverseButton.tsx | 11 +- src/components/button/SecondaryButton.tsx | 9 +- src/components/drop-down-list/Filter.tsx | 124 +++++++++++++++++ src/components/drop-down-list/Sorter.tsx | 127 ++++++++++++++++++ src/pages/ContestEditor.tsx | 78 +++++------ src/pages/Home.tsx | 4 +- src/redux/slices/account.ts | 0 src/redux/slices/missions.ts | 40 ++++++ src/views/home/account/Account.tsx | 7 +- src/views/home/account/contests/Contests.tsx | 3 - .../home/account/contests/MyContestItem.tsx | 6 - .../account/contests/RegisterContestItem.tsx | 14 +- src/views/home/account/missions/Missions.tsx | 109 +++++++++++++++ .../home/account/missions/MissionsBlock.tsx | 119 ++++++++-------- .../home/account/missions/MyMissionItem.tsx | 90 +++++++++++++ src/views/home/contest/Contest.tsx | 17 +-- src/views/home/contests/ContestItem.tsx | 12 +- src/views/home/missions/Filter.tsx | 48 +++++++ src/views/home/missions/Missions.tsx | 3 +- tsconfig.app.tsbuildinfo | 2 +- 26 files changed, 694 insertions(+), 158 deletions(-) create mode 100644 src/assets/icons/filters/filters-active.svg create mode 100644 src/assets/icons/filters/filters.svg create mode 100644 src/assets/icons/filters/index.ts create mode 100644 src/assets/icons/filters/sort-active.svg create mode 100644 src/assets/icons/filters/sort.svg create mode 100644 src/components/drop-down-list/Filter.tsx create mode 100644 src/components/drop-down-list/Sorter.tsx delete mode 100644 src/redux/slices/account.ts create mode 100644 src/views/home/account/missions/Missions.tsx create mode 100644 src/views/home/account/missions/MyMissionItem.tsx create mode 100644 src/views/home/missions/Filter.tsx diff --git a/src/assets/icons/filters/filters-active.svg b/src/assets/icons/filters/filters-active.svg new file mode 100644 index 0000000..4c120fa --- /dev/null +++ b/src/assets/icons/filters/filters-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/filters/filters.svg b/src/assets/icons/filters/filters.svg new file mode 100644 index 0000000..00b357b --- /dev/null +++ b/src/assets/icons/filters/filters.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/filters/index.ts b/src/assets/icons/filters/index.ts new file mode 100644 index 0000000..2eca117 --- /dev/null +++ b/src/assets/icons/filters/index.ts @@ -0,0 +1,6 @@ +import FilterActive from './filters-active.svg'; +import Filter from './filters.svg'; +import Sort from './sort.svg'; +import SortActive from './sort-active.svg'; + +export { Filter, FilterActive, Sort, SortActive }; diff --git a/src/assets/icons/filters/sort-active.svg b/src/assets/icons/filters/sort-active.svg new file mode 100644 index 0000000..971008b --- /dev/null +++ b/src/assets/icons/filters/sort-active.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/filters/sort.svg b/src/assets/icons/filters/sort.svg new file mode 100644 index 0000000..5035447 --- /dev/null +++ b/src/assets/icons/filters/sort.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/button/PrimaryButton.tsx b/src/components/button/PrimaryButton.tsx index 6a9423f..415ad1f 100644 --- a/src/components/button/PrimaryButton.tsx +++ b/src/components/button/PrimaryButton.tsx @@ -5,7 +5,7 @@ interface ButtonProps { disabled?: boolean; text?: string; className?: string; - onClick: (e: React.MouseEvent) => void; + onClick: () => void; children?: React.ReactNode; color?: 'primary' | 'secondary' | 'error' | 'warning' | 'success'; } @@ -41,6 +41,9 @@ export const PrimaryButton: React.FC = ({ disabled && 'pointer-events-none', className, )} + onClick={(e) => { + e.stopPropagation(); + }} > {/* Основной контейнер, */}
= ({ '[&:focus-visible+*]:outline-liquid-brightmain', )} disabled={disabled} - onClick={( - e: React.MouseEvent, - ) => { - onClick(e); + onClick={() => { + onClick(); }} /> diff --git a/src/components/button/ReverseButton.tsx b/src/components/button/ReverseButton.tsx index 67ddceb..142924a 100644 --- a/src/components/button/ReverseButton.tsx +++ b/src/components/button/ReverseButton.tsx @@ -5,7 +5,7 @@ interface ButtonProps { disabled?: boolean; text?: string; className?: string; - onClick: (e: React.MouseEvent) => void; + onClick: () => void; children?: React.ReactNode; color?: 'primary' | 'secondary' | 'error' | 'warning' | 'success'; } @@ -41,6 +41,9 @@ export const ReverseButton: React.FC = ({ disabled && 'pointer-events-none', className, )} + onClick={(e) => { + e.stopPropagation(); + }} > {/* Основной контейнер, */}
= ({ '[&:focus-visible+*]:outline-liquid-brightmain', )} disabled={disabled} - onClick={( - e: React.MouseEvent, - ) => { - onClick(e); + onClick={() => { + onClick(); }} /> diff --git a/src/components/button/SecondaryButton.tsx b/src/components/button/SecondaryButton.tsx index e71ab94..bbb2f36 100644 --- a/src/components/button/SecondaryButton.tsx +++ b/src/components/button/SecondaryButton.tsx @@ -5,7 +5,7 @@ interface ButtonProps { disabled?: boolean; text?: string; className?: string; - onClick: (e: React.MouseEvent) => void; + onClick: () => void; children?: React.ReactNode; } @@ -23,6 +23,9 @@ export const SecondaryButton: React.FC = ({ disabled && 'pointer-events-none', className, )} + onClick={(e) => { + e.stopPropagation(); + }} > {/* Основной контейнер, */}
= ({ '[&:focus-visible+*]:outline-liquid-brightmain', )} disabled={disabled} - onClick={(e) => { - onClick(e); + onClick={() => { + onClick(); }} /> diff --git a/src/components/drop-down-list/Filter.tsx b/src/components/drop-down-list/Filter.tsx new file mode 100644 index 0000000..a3816c2 --- /dev/null +++ b/src/components/drop-down-list/Filter.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { cn } from '../../lib/cn'; +import { checkMark, chevroneDropDownList } from '../../assets/icons/input'; +import { useClickOutside } from '../../hooks/useClickOutside'; + +export interface FilterItem { + text: string; + value: string; +} + +interface FilterProps { + disabled?: boolean; + className?: string; + onChange: (state: string[]) => void; // теперь массив выбранных значений + defaultState?: FilterItem[]; + items: FilterItem[]; +} + +export const Filter: React.FC = ({ + className = '', + onChange, + defaultState = [], + items = [{ text: '', value: '' }], +}) => { + if (items.length === 0) items.push({ text: '', value: '' }); + + const [selectedValues, setSelectedValues] = + React.useState(defaultState); + const [active, setActive] = React.useState(false); + + React.useEffect(() => { + onChange(selectedValues.map((v) => v.value)); + }, [selectedValues]); + + const ref = React.useRef(null); + useClickOutside(ref, () => setActive(false)); + + const toggleItem = (item: FilterItem) => { + const exists = selectedValues.some((v) => v.value === item.value); + if (exists) { + setSelectedValues( + selectedValues.filter((v) => v.value !== item.value), + ); + } else { + setSelectedValues([...selectedValues, item]); + } + }; + + const displayText = + selectedValues.length > 0 + ? selectedValues.map((v) => v.text).join(', ') + : 'Выберите...'; + + return ( +
+
setActive(!active)} + title={displayText} + > + {displayText} +
+ + + +
+
+
+ {items.map((v, i) => { + const isSelected = selectedValues.some( + (sel) => sel.value === v.value, + ); + return ( +
toggleItem(v)} + > + {v.text} + + {isSelected && ( + + )} +
+ ); + })} +
+
+
+
+ ); +}; diff --git a/src/components/drop-down-list/Sorter.tsx b/src/components/drop-down-list/Sorter.tsx new file mode 100644 index 0000000..8c1e14d --- /dev/null +++ b/src/components/drop-down-list/Sorter.tsx @@ -0,0 +1,127 @@ +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'; + +export interface SorterItem { + text: string; + value: string; +} + +interface SorterProps { + disabled?: boolean; + className?: string; + onChange: (state: string) => void; + defaultState?: SorterItem; + items: SorterItem[]; +} + +export const Sorter: React.FC = ({ + // disabled = false, + className = '', + onChange, + defaultState, + items = [{ text: '', value: '' }], +}) => { + if (items.length == 0) items.push({ text: '', value: '' }); + + const [value, setValue] = React.useState( + defaultState != undefined ? defaultState : items[0], + ); + const [active, setActive] = React.useState(false); + + React.useEffect(() => onChange(value.value), [value]); + + const ref = React.useRef(null); + + useClickOutside(ref, () => { + setActive(false); + }); + + return ( +
+
{ + setActive(!active); + }} + > +
+ {' '} + {value.text} +
+
+ +
+ + + +
+
+
+ {items.map((v, i) => ( +
{ + setValue(v); + setActive(false); + }} + > + {v.text} + + {v.text == value.text && ( + + )} +
+ ))} +
+
+
+
+ ); +}; diff --git a/src/pages/ContestEditor.tsx b/src/pages/ContestEditor.tsx index 03d06b0..6b28820 100644 --- a/src/pages/ContestEditor.tsx +++ b/src/pages/ContestEditor.tsx @@ -4,7 +4,6 @@ import { PrimaryButton } from '../components/button/PrimaryButton'; import { Input } from '../components/input/Input'; import { useAppDispatch, useAppSelector } from '../redux/hooks'; import { - createContest, CreateContestBody, deleteContest, fetchContestById, @@ -17,7 +16,6 @@ import { Navigate, useNavigate } from 'react-router-dom'; import { fetchMissionById } from '../redux/slices/missions'; import { ReverseButton } from '../components/button/ReverseButton'; - interface Mission { id: number; name: string; @@ -27,7 +25,6 @@ interface Mission { * Страница создания / редактирования контеста */ const ContestEditor = () => { - const dispatch = useAppDispatch(); const navigate = useNavigate(); @@ -36,21 +33,16 @@ const ContestEditor = () => { const contestId = Number(query.get('contestId') ?? undefined); const refactor = !!contestId; - if (!refactor){ - return + if (!refactor) { + return ; } - - const status = useAppSelector( (state) => state.contests.createContest.status, ); - - const [missionIdInput, setMissionIdInput] = useState(''); - const [contest, setContest] = useState({ name: '', description: '', @@ -67,9 +59,12 @@ const ContestEditor = () => { const [missions, setMissions] = useState([]); - - const statusDelete = useAppSelector((state) => state.contests.deleteContest.status) - const statusUpdate = useAppSelector((state) => state.contests.updateContest.status); + const statusDelete = useAppSelector( + (state) => state.contests.deleteContest.status, + ); + const statusUpdate = useAppSelector( + (state) => state.contests.updateContest.status, + ); const { contest: contestById, status: contestByIdstatus } = useAppSelector( (state) => state.contests.fetchContestById, @@ -85,15 +80,13 @@ const ContestEditor = () => { }; const handleUpdateContest = () => { - dispatch(updateContest({...contest, contestId})); + dispatch(updateContest({ ...contest, contestId })); }; - + const handleDeleteContest = () => { dispatch(deleteContest(contestId)); }; - - const addMission = () => { const id = Number(missionIdInput.trim()); if (!id || contest.missionIds?.includes(id)) return; @@ -121,19 +114,22 @@ const ContestEditor = () => { }; useEffect(() => { - if (statusDelete == "successful"){ - dispatch(setContestStatus({key: "deleteContest", status: "idle"})) - navigate('/home/account/contests') + if (statusDelete == 'successful') { + dispatch( + setContestStatus({ key: 'deleteContest', status: 'idle' }), + ); + navigate('/home/account/contests'); } - }, [statusDelete]) + }, [statusDelete]); - useEffect(() => { - if (statusUpdate == "successful"){ - dispatch(setContestStatus({key: "updateContest", status: "idle"})) - navigate('/home/account/contests') + if (statusUpdate == 'successful') { + dispatch( + setContestStatus({ key: 'updateContest', status: 'idle' }), + ); + navigate('/home/account/contests'); } - }, [statusUpdate]) + }, [statusUpdate]); useEffect(() => { if (refactor) { @@ -146,8 +142,10 @@ const ContestEditor = () => { setContest({ ...contestById, // groupIds: contestById.groups.map(group => group.groupId), - missionIds: contestById.missions?.map(mission => mission.id), - articleIds: contestById.articles?.map(article => article.articleId), + missionIds: contestById.missions?.map((mission) => mission.id), + articleIds: contestById.articles?.map( + (article) => article.articleId, + ), visibility: 'Public', scheduleType: 'AlwaysOpen', }); @@ -300,19 +298,17 @@ const ContestEditor = () => { {/* Кнопки */}
- - - - + +
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 9cebabb..18e78ff 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -51,8 +51,10 @@ const Home = () => {

{jwt}

{ - if (jwt) + if (jwt) { navigator.clipboard.writeText(jwt); + alert(jwt); + } }} text="скопировать токен" className="pt-[20px]" diff --git a/src/redux/slices/account.ts b/src/redux/slices/account.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/redux/slices/missions.ts b/src/redux/slices/missions.ts index 93f24eb..9125402 100644 --- a/src/redux/slices/missions.ts +++ b/src/redux/slices/missions.ts @@ -20,6 +20,8 @@ export interface Mission { tags: string[]; createdAt: string; updatedAt: string; + timeLimit: number; + memoryLimit: number; statements?: Statement[]; } @@ -31,6 +33,7 @@ interface MissionsState { fetchList: Status; fetchById: Status; upload: Status; + fetchMy: Status; }; error: string | null; } @@ -45,6 +48,7 @@ const initialState: MissionsState = { fetchList: 'idle', fetchById: 'idle', upload: 'idle', + fetchMy: 'idle', }, error: null, }; @@ -90,6 +94,22 @@ export const fetchMissionById = createAsyncThunk( }, ); +// ✅ GET /missions/my +export const fetchMyMissions = createAsyncThunk( + 'missions/fetchMyMissions', + async (_, { rejectWithValue }) => { + try { + const response = await axios.get('/missions/my'); + return response.data as Mission[]; // массив миссий пользователя + } catch (err: any) { + return rejectWithValue( + err.response?.data?.message || + 'Ошибка при получении моих миссий', + ); + } + }, +); + // POST /missions/upload export const uploadMission = createAsyncThunk( 'missions/uploadMission', @@ -189,6 +209,26 @@ const missionsSlice = createSlice({ }, ); + // ✅ FETCH MY MISSIONS ─── + builder.addCase(fetchMyMissions.pending, (state) => { + state.statuses.fetchMy = 'loading'; + state.error = null; + }); + builder.addCase( + fetchMyMissions.fulfilled, + (state, action: PayloadAction) => { + state.statuses.fetchMy = 'successful'; + state.missions = action.payload; + }, + ); + builder.addCase( + fetchMyMissions.rejected, + (state, action: PayloadAction) => { + state.statuses.fetchMy = 'failed'; + state.error = action.payload; + }, + ); + // ─── UPLOAD MISSION ─── builder.addCase(uploadMission.pending, (state) => { state.statuses.upload = 'loading'; diff --git a/src/views/home/account/Account.tsx b/src/views/home/account/Account.tsx index 5d4530a..527dc5c 100644 --- a/src/views/home/account/Account.tsx +++ b/src/views/home/account/Account.tsx @@ -1,7 +1,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import AccountMenu from './AccoutMenu'; import RightPanel from './RightPanel'; -import MissionsBlock from './missions/MissionsBlock'; +import Missions from './missions/Missions'; import Contests from './contests/Contests'; import ArticlesBlock from './articles/ArticlesBlock'; import { useAppDispatch } from '../../../redux/hooks'; @@ -24,10 +24,7 @@ const Account = () => {
- } - /> + } /> } diff --git a/src/views/home/account/contests/Contests.tsx b/src/views/home/account/contests/Contests.tsx index 8f96a04..67fdbb7 100644 --- a/src/views/home/account/contests/Contests.tsx +++ b/src/views/home/account/contests/Contests.tsx @@ -14,9 +14,6 @@ const Contests = () => { const myContestsState = useAppSelector( (state) => state.contests.fetchMyContests, ); - const regContestsState = useAppSelector( - (state) => state.contests.fetchRegisteredContests, - ); // При загрузке страницы — выставляем вкладку и подгружаем контесты useEffect(() => { diff --git a/src/views/home/account/contests/MyContestItem.tsx b/src/views/home/account/contests/MyContestItem.tsx index 1fc3785..eef1bf4 100644 --- a/src/views/home/account/contests/MyContestItem.tsx +++ b/src/views/home/account/contests/MyContestItem.tsx @@ -1,7 +1,5 @@ import { cn } from '../../../../lib/cn'; import { Account } from '../../../../assets/icons/auth'; -import { PrimaryButton } from '../../../../components/button/PrimaryButton'; -import { ReverseButton } from '../../../../components/button/ReverseButton'; import { useNavigate } from 'react-router-dom'; import { Edit } from '../../../../assets/icons/input'; @@ -57,10 +55,6 @@ const ContestItem: React.FC = ({ }) => { const navigate = useNavigate(); - const now = new Date(); - - const waitTime = new Date(startAt).getTime() - now.getTime(); - return (
= ({ {statusRegister == 'reg' ? ( <> {' '} - { - e.stopPropagation(); - }} - text="Регистрация" - /> + {}} text="Регистрация" /> ) : ( <> {' '} - { - e.stopPropagation(); - }} - text="Вы записаны" - /> + {}} text="Вы записаны" /> )}
diff --git a/src/views/home/account/missions/Missions.tsx b/src/views/home/account/missions/Missions.tsx new file mode 100644 index 0000000..1d8d2fb --- /dev/null +++ b/src/views/home/account/missions/Missions.tsx @@ -0,0 +1,109 @@ +import { FC, useEffect } from 'react'; +import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; +import { setMenuActiveProfilePage } from '../../../../redux/slices/store'; +import { cn } from '../../../../lib/cn'; +import MissionsBlock from './MissionsBlock'; +import { + fetchMyMissions, + setMissionsStatus, +} from '../../../../redux/slices/missions'; + +interface ItemProps { + count: number; + totalCount: number; + title: string; + color?: 'default' | 'red' | 'green' | 'orange'; +} + +const Item: FC = ({ + count, + totalCount, + title, + color = 'default', +}) => { + return ( +
+
+ {count}/{totalCount} +
+
{title}
+
+ ); +}; + +const Missions = () => { + const dispatch = useAppDispatch(); + const missions = useAppSelector((state) => state.missions.missions); + const status = useAppSelector((state) => state.missions.statuses.fetchMy); + + useEffect(() => { + dispatch(setMenuActiveProfilePage('missions')); + dispatch(fetchMyMissions()); + }, []); + + useEffect(() => { + dispatch(setMissionsStatus({ key: 'fetchMy', status: 'idle' })); + }, [status]); + + return ( +
+
+
+
+ Решенные задачи +
+
+
+ +
+
+ + + +
+
+
+ Компетенции +
+ +
+ + + +
+
+
+ +
+
+
+ ); +}; + +export default Missions; diff --git a/src/views/home/account/missions/MissionsBlock.tsx b/src/views/home/account/missions/MissionsBlock.tsx index 8cd10b0..b9b2efe 100644 --- a/src/views/home/account/missions/MissionsBlock.tsx +++ b/src/views/home/account/missions/MissionsBlock.tsx @@ -1,66 +1,71 @@ -import { FC, useEffect } from "react"; -import { useAppDispatch } from "../../../../redux/hooks"; -import { setMenuActiveProfilePage } from "../../../../redux/slices/store"; -import { cn } from "../../../../lib/cn"; +import { useState, FC } from 'react'; +import { cn } from '../../../../lib/cn'; +import { ChevroneDown } from '../../../../assets/icons/groups'; +import MyMissionItem from './MyMissionItem'; +import { Mission } from '../../../../redux/slices/missions'; - -interface ItemProps { - count: number; - totalCount: number; +interface MissionsBlockProps { + missions: Mission[]; title: string; - color?: "default" | "red" | "green" | "orange"; + className?: string; } -const Item: FC = ({count, totalCount, title, color = "default"}) => { +const MissionsBlock: FC = ({ + missions, + title, + className, +}) => { + const [active, setActive] = useState(true); - return
-
{count}/{totalCount}
-
{title}
-
-}; - -const MissionsBlock = () => { - const dispatch = useAppDispatch(); - - useEffect(() => { - dispatch(setMenuActiveProfilePage("missions")); - }, []); - - return ( -
-
-
-
Решенные задачи
-
- -
- -
-
- - - -
- -
-
Компетенции
- -
- - - -
+ return ( +
+
{ + setActive(!active); + }} + > + {title} + +
+
+
+
+ {missions.map((v, i) => ( + + ))} +
+
+
-
Недавиние задачи
-
Мои задачи
-
-
- ); + ); }; export default MissionsBlock; diff --git a/src/views/home/account/missions/MyMissionItem.tsx b/src/views/home/account/missions/MyMissionItem.tsx new file mode 100644 index 0000000..18b1ef3 --- /dev/null +++ b/src/views/home/account/missions/MyMissionItem.tsx @@ -0,0 +1,90 @@ +import { cn } from '../../../../lib/cn'; +import { IconError, IconSuccess } from '../../../../assets/icons/missions'; +import { useNavigate } from 'react-router-dom'; +import { Edit } from '../../../../assets/icons/input'; + +export interface MissionItemProps { + id: number; + authorId?: number; + name: string; + difficulty: number; + tags?: string[]; + timeLimit?: number; + memoryLimit?: number; + createdAt?: string; + updatedAt?: string; + type?: 'first' | 'second'; + status?: 'empty' | 'success' | 'error'; +} + +export function formatMilliseconds(ms: number): string { + const rounded = Math.round(ms) / 1000; + const formatted = rounded.toString().replace(/\.?0+$/, ''); + return `${formatted} c`; +} + +export function formatBytesToMB(bytes: number): string { + const megabytes = Math.floor(bytes / (1024 * 1024)); + return `${megabytes} МБ`; +} + +const MissionItem: React.FC = ({ + id, + name, + difficulty, + timeLimit = 1000, + memoryLimit = 256 * 1024 * 1024, + type, + status, +}) => { + const navigate = useNavigate(); + const difficultyItems = ['Easy', 'Medium', 'Hard']; + const difficultyString = + difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)]; + + return ( +
{ + navigate(`/mission/${id}?back=/home/account/missions`); + }} + > +
#{id}
+
{name}
+
+ стандартный ввод/вывод {formatMilliseconds(timeLimit)},{' '} + {formatBytesToMB(memoryLimit)} +
+
+ {difficultyString} +
+
+ { + e.stopPropagation(); + }} + /> +
+
+ ); +}; + +export default MissionItem; diff --git a/src/views/home/contest/Contest.tsx b/src/views/home/contest/Contest.tsx index b30de29..5a026bc 100644 --- a/src/views/home/contest/Contest.tsx +++ b/src/views/home/contest/Contest.tsx @@ -1,10 +1,9 @@ import { useEffect } from 'react'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { setMenuActivePage } from '../../../redux/slices/store'; -import { Navigate, Route, Routes, useNavigate, useParams } from 'react-router-dom'; +import { Navigate, Route, Routes, useParams } from 'react-router-dom'; import { fetchContestById } from '../../../redux/slices/contests'; import ContestMissions from './Missions'; -import { PrimaryButton } from '../../../components/button/PrimaryButton'; import Submissions from './Submissions'; export interface Article { @@ -14,7 +13,6 @@ export interface Article { } const Contest = () => { - const navigate = useNavigate(); const { contestId } = useParams<{ contestId: string }>(); const contestIdNumber = contestId && /^\d+$/.test(contestId) ? parseInt(contestId, 10) : null; @@ -22,8 +20,9 @@ const Contest = () => { return ; } const dispatch = useAppDispatch(); - const contest = useAppSelector((state) => state.contests.fetchContestById.contest); - + const contest = useAppSelector( + (state) => state.contests.fetchContestById.contest, + ); useEffect(() => { dispatch(setMenuActivePage('contest')); @@ -34,19 +33,17 @@ const Contest = () => { }, [contestIdNumber]); return ( -
+
- } + element={} /> } + element={} /> -
); }; diff --git a/src/views/home/contests/ContestItem.tsx b/src/views/home/contests/ContestItem.tsx index c2fc6fd..edf948e 100644 --- a/src/views/home/contests/ContestItem.tsx +++ b/src/views/home/contests/ContestItem.tsx @@ -73,6 +73,7 @@ const ContestItem: React.FC = ({ : ' bg-liquid-background', )} onClick={() => { + console.log(456); navigate(`/contest/${id}`); }} > @@ -99,8 +100,8 @@ const ContestItem: React.FC = ({ <> {' '} { - e.stopPropagation(); + onClick={() => { + console.log(123); }} text="Регистрация" /> @@ -108,12 +109,7 @@ const ContestItem: React.FC = ({ ) : ( <> {' '} - { - e.stopPropagation(); - }} - text="Вы записаны" - /> + {}} text="Вы записаны" /> )}
diff --git a/src/views/home/missions/Filter.tsx b/src/views/home/missions/Filter.tsx new file mode 100644 index 0000000..e698815 --- /dev/null +++ b/src/views/home/missions/Filter.tsx @@ -0,0 +1,48 @@ +import { Filter, FilterItem } from '../../../components/drop-down-list/Filter'; +import { Sorter } from '../../../components/drop-down-list/Sorter'; + +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 ( +
+
+ + console.log(v)} + /> + + {/* console.log(values)} // обработчик изменения + className="w-[240px]" + /> */} +
+ ); +}; + +export default Filters; diff --git a/src/views/home/missions/Missions.tsx b/src/views/home/missions/Missions.tsx index 3b5203e..28d71b0 100644 --- a/src/views/home/missions/Missions.tsx +++ b/src/views/home/missions/Missions.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'; import { setMenuActivePage } from '../../../redux/slices/store'; import { fetchMissions } from '../../../redux/slices/missions'; import ModalCreate from './ModalCreate'; +import Filters from './Filter'; export interface Mission { id: number; @@ -45,7 +46,7 @@ const Missions = () => { />
-
+
{missions.map((v, i) => ( diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo index dc32943..88b15bb 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/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/articlesblock.tsx","./src/views/home/account/contestsblock.tsx","./src/views/home/account/missionsblock.tsx","./src/views/home/account/rightpanel.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/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/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