This commit is contained in:
Виталий Лавшонок
2025-11-04 19:33:47 +03:00
parent 4972836164
commit cdb5595769
18 changed files with 511 additions and 65 deletions

View File

@@ -24,9 +24,9 @@ export interface Contest {
scheduleType: string;
startsAt: string;
endsAt: string;
availableFrom: string | null;
availableUntil: string | null;
attemptDurationMinutes: number | null;
maxAttempts: number | null;
allowEarlyFinish: boolean | null;
groupId: number | null;
groupName: string | null;
missions: Mission[];
@@ -40,30 +40,37 @@ interface ContestsResponse {
}
export interface CreateContestBody {
name: string;
description: string;
scheduleType: 'FixedWindow' | 'Flexible';
startsAt: string;
endsAt: string;
availableFrom: string | null;
availableUntil: string | null;
attemptDurationMinutes: number | null;
groupId: number | null;
missionIds: number[];
articleIds: number[];
participantIds: number[];
organizerIds: number[];
name?: string | null;
description?: string | null;
scheduleType: 'AlwaysOpen' | 'FixedWindow' | 'RollingWindow';
visibility: 'Public' | 'GroupPrivate';
startsAt?: string | null;
endsAt?: string | null;
attemptDurationMinutes?: number | null;
maxAttempts?: number | null;
allowEarlyFinish?: boolean | null;
groupId?: number | null;
missionIds?: number[] | null;
articleIds?: number[] | null;
participantIds?: number[] | null;
organizerIds?: number[] | null;
}
// =====================
// Состояние
// =====================
type Status = 'idle' | 'loading' | 'successful' | 'failed';
interface ContestsState {
contests: Contest[];
selectedContest: Contest | null;
hasNextPage: boolean;
status: 'idle' | 'loading' | 'successful' | 'failed';
statuses: {
fetchList: Status;
fetchById: Status;
create: Status;
};
error: string | null;
}
@@ -71,7 +78,11 @@ const initialState: ContestsState = {
contests: [],
selectedContest: null,
hasNextPage: false,
status: 'idle',
statuses: {
fetchList: 'idle',
fetchById: 'idle',
create: 'idle',
},
error: null,
};
@@ -79,7 +90,6 @@ const initialState: ContestsState = {
// Async Thunks
// =====================
// Получение списка контестов
export const fetchContests = createAsyncThunk(
'contests/fetchAll',
async (
@@ -104,7 +114,6 @@ export const fetchContests = createAsyncThunk(
},
);
// Получение одного контеста по ID
export const fetchContestById = createAsyncThunk(
'contests/fetchById',
async (id: number, { rejectWithValue }) => {
@@ -119,7 +128,6 @@ export const fetchContestById = createAsyncThunk(
},
);
// Создание нового контеста
export const createContest = createAsyncThunk(
'contests/create',
async (contestData: CreateContestBody, { rejectWithValue }) => {
@@ -148,17 +156,26 @@ const contestsSlice = createSlice({
clearSelectedContest: (state) => {
state.selectedContest = null;
},
setContestStatus: (
state,
action: PayloadAction<{
key: keyof ContestsState['statuses'];
status: Status;
}>,
) => {
state.statuses[action.payload.key] = action.payload.status;
},
},
extraReducers: (builder) => {
// fetchContests
builder.addCase(fetchContests.pending, (state) => {
state.status = 'loading';
state.statuses.fetchList = 'loading';
state.error = null;
});
builder.addCase(
fetchContests.fulfilled,
(state, action: PayloadAction<ContestsResponse>) => {
state.status = 'successful';
state.statuses.fetchList = 'successful';
state.contests = action.payload.contests;
state.hasNextPage = action.payload.hasNextPage;
},
@@ -166,47 +183,47 @@ const contestsSlice = createSlice({
builder.addCase(
fetchContests.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.statuses.fetchList = 'failed';
state.error = action.payload;
},
);
// fetchContestById
builder.addCase(fetchContestById.pending, (state) => {
state.status = 'loading';
state.statuses.fetchById = 'loading';
state.error = null;
});
builder.addCase(
fetchContestById.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.status = 'successful';
state.statuses.fetchById = 'successful';
state.selectedContest = action.payload;
},
);
builder.addCase(
fetchContestById.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.statuses.fetchById = 'failed';
state.error = action.payload;
},
);
// createContest
builder.addCase(createContest.pending, (state) => {
state.status = 'loading';
state.statuses.create = 'loading';
state.error = null;
});
builder.addCase(
createContest.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.status = 'successful';
state.statuses.create = 'successful';
state.contests.unshift(action.payload);
},
);
builder.addCase(
createContest.rejected,
(state, action: PayloadAction<any>) => {
state.status = 'failed';
state.statuses.create = 'failed';
state.error = action.payload;
},
);
@@ -216,5 +233,6 @@ const contestsSlice = createSlice({
// =====================
// Экспорты
// =====================
export const { clearSelectedContest } = contestsSlice.actions;
export const { clearSelectedContest, setContestStatus } = contestsSlice.actions;
export const contestsReducer = contestsSlice.reducer;