From d6ab1cba4d3dbab492ff1c687914de64f6521ae8 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: Sun, 23 Nov 2025 14:26:04 +0300 Subject: [PATCH] delete mission --- src/components/modal/ConfirmModal.tsx | 8 +++- src/pages/Mission.tsx | 14 +++++- src/redux/slices/missions.ts | 43 +++++++++++++++++++ src/views/home/account/missions/Missions.tsx | 20 ++++++++- .../home/account/missions/MissionsBlock.tsx | 7 +++ .../home/account/missions/MyMissionItem.tsx | 20 ++++++++- .../home/rightpanel/group/ModalLeave.tsx | 1 + .../home/rightpanel/group/ModalUpdate.tsx | 2 + 8 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/components/modal/ConfirmModal.tsx b/src/components/modal/ConfirmModal.tsx index b5c4e10..667481c 100644 --- a/src/components/modal/ConfirmModal.tsx +++ b/src/components/modal/ConfirmModal.tsx @@ -2,6 +2,7 @@ import { FC } from 'react'; import { Modal } from './Modal'; import { PrimaryButton } from '../../components/button/PrimaryButton'; import { SecondaryButton } from '../../components/button/SecondaryButton'; +import { cn } from '../../lib/cn'; interface ConfirmModalProps { active: boolean; @@ -11,6 +12,7 @@ interface ConfirmModalProps { message?: string; confirmColor?: 'primary' | 'secondary' | 'error' | 'warning' | 'success'; confirmText?: string; + className?: string; } const ConfirmModal: FC = ({ @@ -21,10 +23,14 @@ const ConfirmModal: FC = ({ message, confirmColor = 'secondary', confirmText = 'Ок', + className, }) => { return ( { const dispatch = useAppDispatch(); + const navigate = useNavigate(); // Получаем параметры из URL const { missionId } = useParams<{ missionId: string }>(); const mission = useAppSelector((state) => state.missions.currentMission); + const missionStatus = useAppSelector( + (state) => state.missions.statuses.fetchById, + ); const missionIdNumber = Number(missionId); const query = useQuery(); @@ -74,6 +78,12 @@ const Mission = () => { } }; }, []); + useEffect(() => { + if (missionStatus == 'failed') { + setMissionsStatus({ key: 'fetchById', status: 'idle' }); + navigate(back ?? '/home/missions'); + } + }, [missionStatus]); useEffect(() => { submissionsRef.current = submissions; diff --git a/src/redux/slices/missions.ts b/src/redux/slices/missions.ts index f3c84ee..1d749cb 100644 --- a/src/redux/slices/missions.ts +++ b/src/redux/slices/missions.ts @@ -34,6 +34,7 @@ interface MissionsState { fetchById: Status; upload: Status; fetchMy: Status; + delete: Status; }; error: string | null; } @@ -49,6 +50,7 @@ const initialState: MissionsState = { fetchById: 'idle', upload: 'idle', fetchMy: 'idle', + delete: 'idle', }, error: null, }; @@ -141,6 +143,21 @@ export const uploadMission = createAsyncThunk( }, ); +// DELETE /missions/{id} +export const deleteMission = createAsyncThunk( + 'missions/deleteMission', + async (id: number, { rejectWithValue }) => { + try { + await axios.delete(`/missions/${id}`); + return id; // возвращаем id удалённой миссии + } catch (err: any) { + return rejectWithValue( + err.response?.data?.message || 'Ошибка при удалении миссии', + ); + } + }, +); + // ─── Slice ──────────────────────────────────────────── const missionsSlice = createSlice({ @@ -245,6 +262,32 @@ const missionsSlice = createSlice({ state.error = action.payload; }, ); + + // ─── DELETE MISSION ─── + builder.addCase(deleteMission.pending, (state) => { + state.statuses.delete = 'loading'; + state.error = null; + }); + builder.addCase( + deleteMission.fulfilled, + (state, action: PayloadAction) => { + state.statuses.delete = 'successful'; + state.missions = state.missions.filter( + (m) => m.id !== action.payload, + ); + + if (state.currentMission?.id === action.payload) { + state.currentMission = null; + } + }, + ); + builder.addCase( + deleteMission.rejected, + (state, action: PayloadAction) => { + state.statuses.delete = 'failed'; + state.error = action.payload; + }, + ); }, }); diff --git a/src/views/home/account/missions/Missions.tsx b/src/views/home/account/missions/Missions.tsx index 1d8d2fb..5dc74b4 100644 --- a/src/views/home/account/missions/Missions.tsx +++ b/src/views/home/account/missions/Missions.tsx @@ -1,12 +1,14 @@ -import { FC, useEffect } from 'react'; +import { FC, useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { setMenuActiveProfilePage } from '../../../../redux/slices/store'; import { cn } from '../../../../lib/cn'; import MissionsBlock from './MissionsBlock'; import { + deleteMission, fetchMyMissions, setMissionsStatus, } from '../../../../redux/slices/missions'; +import ConfirmModal from '../../../../components/modal/ConfirmModal'; interface ItemProps { count: number; @@ -43,6 +45,8 @@ const Missions = () => { const dispatch = useAppDispatch(); const missions = useAppSelector((state) => state.missions.missions); const status = useAppSelector((state) => state.missions.statuses.fetchMy); + const [modalDeleteTask, setModalDeleteTask] = useState(false); + const [taskdeleteId, setTaskDeleteId] = useState(0); useEffect(() => { dispatch(setMenuActiveProfilePage('missions')); @@ -99,9 +103,23 @@ const Missions = () => { + + { + dispatch(deleteMission(taskdeleteId)); + }} + /> ); }; diff --git a/src/views/home/account/missions/MissionsBlock.tsx b/src/views/home/account/missions/MissionsBlock.tsx index b9b2efe..884d658 100644 --- a/src/views/home/account/missions/MissionsBlock.tsx +++ b/src/views/home/account/missions/MissionsBlock.tsx @@ -8,12 +8,17 @@ interface MissionsBlockProps { missions: Mission[]; title: string; className?: string; + setTastDeleteId: (v: number) => void; + setDeleteModalActive: (v: boolean) => void; } const MissionsBlock: FC = ({ missions, title, className, + + setTastDeleteId, + setDeleteModalActive, }) => { const [active, setActive] = useState(true); @@ -59,6 +64,8 @@ const MissionsBlock: FC = ({ memoryLimit={v.memoryLimit} difficulty={v.difficulty} type={i % 2 ? 'second' : 'first'} + setTastDeleteId={setTastDeleteId} + setDeleteModalActive={setDeleteModalActive} /> ))} diff --git a/src/views/home/account/missions/MyMissionItem.tsx b/src/views/home/account/missions/MyMissionItem.tsx index 5fa27f0..556820b 100644 --- a/src/views/home/account/missions/MyMissionItem.tsx +++ b/src/views/home/account/missions/MyMissionItem.tsx @@ -1,6 +1,8 @@ import { cn } from '../../../../lib/cn'; import { useNavigate } from 'react-router-dom'; import { Edit } from '../../../../assets/icons/input'; +import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; +import { deleteMission } from '../../../../redux/slices/missions'; export interface MissionItemProps { id: number; @@ -14,6 +16,8 @@ export interface MissionItemProps { updatedAt?: string; type?: 'first' | 'second'; status?: 'empty' | 'success' | 'error'; + setTastDeleteId: (v: number) => void; + setDeleteModalActive: (v: boolean) => void; } export function formatMilliseconds(ms: number): string { @@ -35,11 +39,17 @@ const MissionItem: React.FC = ({ memoryLimit = 256 * 1024 * 1024, type, status, + setTastDeleteId, + setDeleteModalActive, }) => { const navigate = useNavigate(); + const dispatch = useAppDispatch(); const difficultyItems = ['Easy', 'Medium', 'Hard']; const difficultyString = difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)]; + const deleteStatus = useAppSelector( + (state) => state.missions.statuses.delete, + ); return (
= ({
{ e.stopPropagation(); + if (deleteStatus != 'loading') { + setTastDeleteId(id); + setDeleteModalActive(true); + } }} />
diff --git a/src/views/home/rightpanel/group/ModalLeave.tsx b/src/views/home/rightpanel/group/ModalLeave.tsx index c9339c3..ceff48d 100644 --- a/src/views/home/rightpanel/group/ModalLeave.tsx +++ b/src/views/home/rightpanel/group/ModalLeave.tsx @@ -81,6 +81,7 @@ const ModalLeave: FC = ({
= ({ = ({ />