upload mission modal
This commit is contained in:
@@ -1,146 +1,215 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit";
|
||||
import axios from "../../axios";
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import axios from '../../axios';
|
||||
|
||||
// ─── Типы ────────────────────────────────────────────
|
||||
|
||||
type Status = 'idle' | 'loading' | 'successful' | 'failed';
|
||||
|
||||
// Типы данных
|
||||
interface Statement {
|
||||
id: number;
|
||||
language: string;
|
||||
statementTexts: Record<string, string>;
|
||||
mediaFiles?: { id: number; fileName: string; mediaUrl: string }[];
|
||||
id: number;
|
||||
language: string;
|
||||
statementTexts: Record<string, string>;
|
||||
mediaFiles?: { id: number; fileName: string; mediaUrl: string }[];
|
||||
}
|
||||
|
||||
interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: number;
|
||||
tags: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
statements?: Statement[];
|
||||
export interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: number;
|
||||
tags: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
statements?: Statement[];
|
||||
}
|
||||
|
||||
interface MissionsState {
|
||||
missions: Mission[];
|
||||
currentMission: Mission | null;
|
||||
hasNextPage: boolean;
|
||||
status: "idle" | "loading" | "successful" | "failed";
|
||||
error: string | null;
|
||||
missions: Mission[];
|
||||
currentMission: Mission | null;
|
||||
hasNextPage: boolean;
|
||||
statuses: {
|
||||
fetchList: Status;
|
||||
fetchById: Status;
|
||||
upload: Status;
|
||||
};
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// Инициализация состояния
|
||||
// ─── Инициализация состояния ──────────────────────────
|
||||
|
||||
const initialState: MissionsState = {
|
||||
missions: [],
|
||||
currentMission: null,
|
||||
hasNextPage: false,
|
||||
status: "idle",
|
||||
error: null,
|
||||
missions: [],
|
||||
currentMission: null,
|
||||
hasNextPage: false,
|
||||
statuses: {
|
||||
fetchList: 'idle',
|
||||
fetchById: 'idle',
|
||||
upload: 'idle',
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// AsyncThunk: Получение списка миссий
|
||||
// ─── Async Thunks ─────────────────────────────────────
|
||||
|
||||
// GET /missions
|
||||
export const fetchMissions = createAsyncThunk(
|
||||
"missions/fetchMissions",
|
||||
async (
|
||||
{ page = 0, pageSize = 10, tags = [] }: { page?: number; pageSize?: number; tags?: string[] },
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
try {
|
||||
const params: any = { page, pageSize };
|
||||
if (tags) params.tags = tags;
|
||||
const response = await axios.get("/missions", { params });
|
||||
return response.data; // { hasNextPage, missions }
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(err.response?.data?.message || "Failed to fetch missions");
|
||||
}
|
||||
}
|
||||
'missions/fetchMissions',
|
||||
async (
|
||||
{
|
||||
page = 0,
|
||||
pageSize = 10,
|
||||
tags = [],
|
||||
}: { page?: number; pageSize?: number; tags?: string[] },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const params: any = { page, pageSize };
|
||||
if (tags.length) params.tags = tags;
|
||||
const response = await axios.get('/missions', { params });
|
||||
return response.data; // { missions, hasNextPage }
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении миссий',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// AsyncThunk: Получение миссии по id
|
||||
// GET /missions/{id}
|
||||
export const fetchMissionById = createAsyncThunk(
|
||||
"missions/fetchMissionById",
|
||||
async (id: number, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get(`/missions/${id}`);
|
||||
return response.data; // Mission
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(err.response?.data?.message || "Failed to fetch mission");
|
||||
}
|
||||
}
|
||||
'missions/fetchMissionById',
|
||||
async (id: number, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get(`/missions/${id}`);
|
||||
return response.data; // Mission
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении миссии',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// AsyncThunk: Загрузка миссии
|
||||
// POST /missions/upload
|
||||
export const uploadMission = createAsyncThunk(
|
||||
"missions/uploadMission",
|
||||
async (
|
||||
{ file, name, difficulty, tags }: { file: File; name: string; difficulty: number; tags: string[] },
|
||||
{ rejectWithValue }
|
||||
) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append("MissionFile", file);
|
||||
formData.append("Name", name);
|
||||
formData.append("Difficulty", difficulty.toString());
|
||||
tags.forEach(tag => formData.append("Tags", tag));
|
||||
'missions/uploadMission',
|
||||
async (
|
||||
{
|
||||
file,
|
||||
name,
|
||||
difficulty,
|
||||
tags,
|
||||
}: { file: File; name: string; difficulty: number; tags: string[] },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('MissionFile', file);
|
||||
formData.append('Name', name);
|
||||
formData.append('Difficulty', difficulty.toString());
|
||||
tags.forEach((tag) => formData.append('Tags', tag));
|
||||
|
||||
const response = await axios.post("/missions/upload", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
return response.data; // Mission
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(err.response?.data?.message || "Failed to upload mission");
|
||||
}
|
||||
}
|
||||
const response = await axios.post('/missions/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data; // Mission
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при загрузке миссии',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Slice
|
||||
// ─── Slice ────────────────────────────────────────────
|
||||
|
||||
const missionsSlice = createSlice({
|
||||
name: "missions",
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
// fetchMissions
|
||||
builder.addCase(fetchMissions.pending, (state) => {
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(fetchMissions.fulfilled, (state, action: PayloadAction<{ missions: Mission[]; hasNextPage: boolean }>) => {
|
||||
state.status = "successful";
|
||||
state.missions = action.payload.missions;
|
||||
state.hasNextPage = action.payload.hasNextPage;
|
||||
});
|
||||
builder.addCase(fetchMissions.rejected, (state, action: PayloadAction<any>) => {
|
||||
state.status = "failed";
|
||||
state.error = action.payload;
|
||||
});
|
||||
name: 'missions',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearCurrentMission: (state) => {
|
||||
state.currentMission = null;
|
||||
},
|
||||
setMissionsStatus: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
key: keyof MissionsState['statuses'];
|
||||
status: Status;
|
||||
}>,
|
||||
) => {
|
||||
const { key, status } = action.payload;
|
||||
state.statuses[key] = status;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// ─── FETCH MISSIONS ───
|
||||
builder.addCase(fetchMissions.pending, (state) => {
|
||||
state.statuses.fetchList = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchMissions.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
missions: Mission[];
|
||||
hasNextPage: boolean;
|
||||
}>,
|
||||
) => {
|
||||
state.statuses.fetchList = 'successful';
|
||||
state.missions = action.payload.missions;
|
||||
state.hasNextPage = action.payload.hasNextPage;
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
fetchMissions.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.fetchList = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// fetchMissionById
|
||||
builder.addCase(fetchMissionById.pending, (state) => {
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(fetchMissionById.fulfilled, (state, action: PayloadAction<Mission>) => {
|
||||
state.status = "successful";
|
||||
state.currentMission = action.payload;
|
||||
});
|
||||
builder.addCase(fetchMissionById.rejected, (state, action: PayloadAction<any>) => {
|
||||
state.status = "failed";
|
||||
state.error = action.payload;
|
||||
});
|
||||
// ─── FETCH MISSION BY ID ───
|
||||
builder.addCase(fetchMissionById.pending, (state) => {
|
||||
state.statuses.fetchById = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchMissionById.fulfilled,
|
||||
(state, action: PayloadAction<Mission>) => {
|
||||
state.statuses.fetchById = 'successful';
|
||||
state.currentMission = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
fetchMissionById.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.fetchById = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// uploadMission
|
||||
builder.addCase(uploadMission.pending, (state) => {
|
||||
state.status = "loading";
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(uploadMission.fulfilled, (state, action: PayloadAction<Mission>) => {
|
||||
state.status = "successful";
|
||||
state.missions.unshift(action.payload); // Добавляем новую миссию в начало списка
|
||||
});
|
||||
builder.addCase(uploadMission.rejected, (state, action: PayloadAction<any>) => {
|
||||
state.status = "failed";
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
// ─── UPLOAD MISSION ───
|
||||
builder.addCase(uploadMission.pending, (state) => {
|
||||
state.statuses.upload = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
uploadMission.fulfilled,
|
||||
(state, action: PayloadAction<Mission>) => {
|
||||
state.statuses.upload = 'successful';
|
||||
state.missions.unshift(action.payload);
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
uploadMission.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.upload = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const { clearCurrentMission, setMissionsStatus } = missionsSlice.actions;
|
||||
export const missionsReducer = missionsSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user