delete mission
This commit is contained in:
@@ -2,6 +2,7 @@ import { FC } from 'react';
|
|||||||
import { Modal } from './Modal';
|
import { Modal } from './Modal';
|
||||||
import { PrimaryButton } from '../../components/button/PrimaryButton';
|
import { PrimaryButton } from '../../components/button/PrimaryButton';
|
||||||
import { SecondaryButton } from '../../components/button/SecondaryButton';
|
import { SecondaryButton } from '../../components/button/SecondaryButton';
|
||||||
|
import { cn } from '../../lib/cn';
|
||||||
|
|
||||||
interface ConfirmModalProps {
|
interface ConfirmModalProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -11,6 +12,7 @@ interface ConfirmModalProps {
|
|||||||
message?: string;
|
message?: string;
|
||||||
confirmColor?: 'primary' | 'secondary' | 'error' | 'warning' | 'success';
|
confirmColor?: 'primary' | 'secondary' | 'error' | 'warning' | 'success';
|
||||||
confirmText?: string;
|
confirmText?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmModal: FC<ConfirmModalProps> = ({
|
const ConfirmModal: FC<ConfirmModalProps> = ({
|
||||||
@@ -21,10 +23,14 @@ const ConfirmModal: FC<ConfirmModalProps> = ({
|
|||||||
message,
|
message,
|
||||||
confirmColor = 'secondary',
|
confirmColor = 'secondary',
|
||||||
confirmText = 'Ок',
|
confirmText = 'Ок',
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white fixed top-0 left-0"
|
className={cn(
|
||||||
|
'bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
onOpenChange={setActive}
|
onOpenChange={setActive}
|
||||||
open={active}
|
open={active}
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { useParams, Navigate } from 'react-router-dom';
|
import { useParams, Navigate, useNavigate } from 'react-router-dom';
|
||||||
import CodeEditor from '../views/mission/codeeditor/CodeEditor';
|
import CodeEditor from '../views/mission/codeeditor/CodeEditor';
|
||||||
import Statement from '../views/mission/statement/Statement';
|
import Statement from '../views/mission/statement/Statement';
|
||||||
import { PrimaryButton } from '../components/button/PrimaryButton';
|
import { PrimaryButton } from '../components/button/PrimaryButton';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
||||||
import { fetchMySubmitsByMission, submitMission } from '../redux/slices/submit';
|
import { fetchMySubmitsByMission, submitMission } from '../redux/slices/submit';
|
||||||
import { fetchMissionById } from '../redux/slices/missions';
|
import { fetchMissionById, setMissionsStatus } from '../redux/slices/missions';
|
||||||
import Header from '../views/mission/statement/Header';
|
import Header from '../views/mission/statement/Header';
|
||||||
import MissionSubmissions from '../views/mission/statement/MissionSubmissions';
|
import MissionSubmissions from '../views/mission/statement/MissionSubmissions';
|
||||||
import { useQuery } from '../hooks/useQuery';
|
import { useQuery } from '../hooks/useQuery';
|
||||||
|
|
||||||
const Mission = () => {
|
const Mission = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Получаем параметры из URL
|
// Получаем параметры из URL
|
||||||
const { missionId } = useParams<{ missionId: string }>();
|
const { missionId } = useParams<{ missionId: string }>();
|
||||||
const mission = useAppSelector((state) => state.missions.currentMission);
|
const mission = useAppSelector((state) => state.missions.currentMission);
|
||||||
|
const missionStatus = useAppSelector(
|
||||||
|
(state) => state.missions.statuses.fetchById,
|
||||||
|
);
|
||||||
const missionIdNumber = Number(missionId);
|
const missionIdNumber = Number(missionId);
|
||||||
|
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
@@ -74,6 +78,12 @@ const Mission = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (missionStatus == 'failed') {
|
||||||
|
setMissionsStatus({ key: 'fetchById', status: 'idle' });
|
||||||
|
navigate(back ?? '/home/missions');
|
||||||
|
}
|
||||||
|
}, [missionStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
submissionsRef.current = submissions;
|
submissionsRef.current = submissions;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface MissionsState {
|
|||||||
fetchById: Status;
|
fetchById: Status;
|
||||||
upload: Status;
|
upload: Status;
|
||||||
fetchMy: Status;
|
fetchMy: Status;
|
||||||
|
delete: Status;
|
||||||
};
|
};
|
||||||
error: string | null;
|
error: string | null;
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,7 @@ const initialState: MissionsState = {
|
|||||||
fetchById: 'idle',
|
fetchById: 'idle',
|
||||||
upload: 'idle',
|
upload: 'idle',
|
||||||
fetchMy: 'idle',
|
fetchMy: 'idle',
|
||||||
|
delete: 'idle',
|
||||||
},
|
},
|
||||||
error: null,
|
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 ────────────────────────────────────────────
|
// ─── Slice ────────────────────────────────────────────
|
||||||
|
|
||||||
const missionsSlice = createSlice({
|
const missionsSlice = createSlice({
|
||||||
@@ -245,6 +262,32 @@ const missionsSlice = createSlice({
|
|||||||
state.error = action.payload;
|
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<number>) => {
|
||||||
|
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<any>) => {
|
||||||
|
state.statuses.delete = 'failed';
|
||||||
|
state.error = action.payload;
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { FC, useEffect } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
|
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
|
||||||
import { setMenuActiveProfilePage } from '../../../../redux/slices/store';
|
import { setMenuActiveProfilePage } from '../../../../redux/slices/store';
|
||||||
import { cn } from '../../../../lib/cn';
|
import { cn } from '../../../../lib/cn';
|
||||||
import MissionsBlock from './MissionsBlock';
|
import MissionsBlock from './MissionsBlock';
|
||||||
import {
|
import {
|
||||||
|
deleteMission,
|
||||||
fetchMyMissions,
|
fetchMyMissions,
|
||||||
setMissionsStatus,
|
setMissionsStatus,
|
||||||
} from '../../../../redux/slices/missions';
|
} from '../../../../redux/slices/missions';
|
||||||
|
import ConfirmModal from '../../../../components/modal/ConfirmModal';
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps {
|
||||||
count: number;
|
count: number;
|
||||||
@@ -43,6 +45,8 @@ const Missions = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const missions = useAppSelector((state) => state.missions.missions);
|
const missions = useAppSelector((state) => state.missions.missions);
|
||||||
const status = useAppSelector((state) => state.missions.statuses.fetchMy);
|
const status = useAppSelector((state) => state.missions.statuses.fetchMy);
|
||||||
|
const [modalDeleteTask, setModalDeleteTask] = useState<boolean>(false);
|
||||||
|
const [taskdeleteId, setTaskDeleteId] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setMenuActiveProfilePage('missions'));
|
dispatch(setMenuActiveProfilePage('missions'));
|
||||||
@@ -99,9 +103,23 @@ const Missions = () => {
|
|||||||
<MissionsBlock
|
<MissionsBlock
|
||||||
missions={missions ?? []}
|
missions={missions ?? []}
|
||||||
title="Мои миссии"
|
title="Мои миссии"
|
||||||
|
setTastDeleteId={setTaskDeleteId}
|
||||||
|
setDeleteModalActive={setModalDeleteTask}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
active={modalDeleteTask}
|
||||||
|
setActive={setModalDeleteTask}
|
||||||
|
title="Подтвердите действия"
|
||||||
|
message={`Вы действительно хотите удалить задачу #${taskdeleteId}?`}
|
||||||
|
confirmColor="error"
|
||||||
|
confirmText="Удалить"
|
||||||
|
onConfirmClick={() => {
|
||||||
|
dispatch(deleteMission(taskdeleteId));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ interface MissionsBlockProps {
|
|||||||
missions: Mission[];
|
missions: Mission[];
|
||||||
title: string;
|
title: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
setTastDeleteId: (v: number) => void;
|
||||||
|
setDeleteModalActive: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MissionsBlock: FC<MissionsBlockProps> = ({
|
const MissionsBlock: FC<MissionsBlockProps> = ({
|
||||||
missions,
|
missions,
|
||||||
title,
|
title,
|
||||||
className,
|
className,
|
||||||
|
|
||||||
|
setTastDeleteId,
|
||||||
|
setDeleteModalActive,
|
||||||
}) => {
|
}) => {
|
||||||
const [active, setActive] = useState<boolean>(true);
|
const [active, setActive] = useState<boolean>(true);
|
||||||
|
|
||||||
@@ -59,6 +64,8 @@ const MissionsBlock: FC<MissionsBlockProps> = ({
|
|||||||
memoryLimit={v.memoryLimit}
|
memoryLimit={v.memoryLimit}
|
||||||
difficulty={v.difficulty}
|
difficulty={v.difficulty}
|
||||||
type={i % 2 ? 'second' : 'first'}
|
type={i % 2 ? 'second' : 'first'}
|
||||||
|
setTastDeleteId={setTastDeleteId}
|
||||||
|
setDeleteModalActive={setDeleteModalActive}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { cn } from '../../../../lib/cn';
|
import { cn } from '../../../../lib/cn';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Edit } from '../../../../assets/icons/input';
|
import { Edit } from '../../../../assets/icons/input';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
|
||||||
|
import { deleteMission } from '../../../../redux/slices/missions';
|
||||||
|
|
||||||
export interface MissionItemProps {
|
export interface MissionItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -14,6 +16,8 @@ export interface MissionItemProps {
|
|||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
type?: 'first' | 'second';
|
type?: 'first' | 'second';
|
||||||
status?: 'empty' | 'success' | 'error';
|
status?: 'empty' | 'success' | 'error';
|
||||||
|
setTastDeleteId: (v: number) => void;
|
||||||
|
setDeleteModalActive: (v: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatMilliseconds(ms: number): string {
|
export function formatMilliseconds(ms: number): string {
|
||||||
@@ -35,11 +39,17 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
|||||||
memoryLimit = 256 * 1024 * 1024,
|
memoryLimit = 256 * 1024 * 1024,
|
||||||
type,
|
type,
|
||||||
status,
|
status,
|
||||||
|
setTastDeleteId,
|
||||||
|
setDeleteModalActive,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const difficultyItems = ['Easy', 'Medium', 'Hard'];
|
const difficultyItems = ['Easy', 'Medium', 'Hard'];
|
||||||
const difficultyString =
|
const difficultyString =
|
||||||
difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)];
|
difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)];
|
||||||
|
const deleteStatus = useAppSelector(
|
||||||
|
(state) => state.missions.statuses.delete,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -76,9 +86,17 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
|||||||
<div className="h-[24px] w-[24px]">
|
<div className="h-[24px] w-[24px]">
|
||||||
<img
|
<img
|
||||||
src={Edit}
|
src={Edit}
|
||||||
className="hover:bg-liquid-light rounded-[8px] transition-all duration-300"
|
className={cn(
|
||||||
|
'hover:bg-liquid-light rounded-[8px] transition-all duration-300',
|
||||||
|
deleteStatus == 'loading' &&
|
||||||
|
'cursor-default pointer-events-none hover:bg-transparent opacity-35',
|
||||||
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
if (deleteStatus != 'loading') {
|
||||||
|
setTastDeleteId(id);
|
||||||
|
setDeleteModalActive(true);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ const ModalLeave: FC<ModalLeaveProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
|
className=" fixed top-0 left-0"
|
||||||
active={modalConfirmActive}
|
active={modalConfirmActive}
|
||||||
setActive={setModalConfirmActive}
|
setActive={setModalConfirmActive}
|
||||||
title="Подтвердите действия"
|
title="Подтвердите действия"
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
|
className=" fixed top-0 left-0"
|
||||||
active={modalConfirmDeleteUser}
|
active={modalConfirmDeleteUser}
|
||||||
setActive={setModalConfirmDeleteUser}
|
setActive={setModalConfirmDeleteUser}
|
||||||
title="Подтвердите действия"
|
title="Подтвердите действия"
|
||||||
@@ -176,6 +177,7 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
|
className=" fixed top-0 left-0"
|
||||||
active={modalConfirmRoleActive}
|
active={modalConfirmRoleActive}
|
||||||
setActive={setModalConfirmRoleActive}
|
setActive={setModalConfirmRoleActive}
|
||||||
title="Подтвердите действия"
|
title="Подтвердите действия"
|
||||||
|
|||||||
Reference in New Issue
Block a user