Files
LiquidCode_Frontend/src/redux/slices/contests.ts
Виталий Лавшонок dc6df1480e my contests
2025-11-06 00:41:01 +03:00

347 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from '../../axios';
// =====================
// Типы
// =====================
export interface Mission {
id: number;
authorId: number;
name: string;
difficulty: number;
tags: string[];
createdAt: string;
updatedAt: string;
timeLimitMilliseconds: number;
memoryLimitBytes: number;
statements: null;
}
export interface Member {
userId: number;
username: string;
role: string;
}
export interface Contest {
id: number;
name: string;
description: string;
scheduleType: string;
startsAt: string;
endsAt: string;
attemptDurationMinutes: number | null;
maxAttempts: number | null;
allowEarlyFinish: boolean | null;
groupId: number | null;
groupName: string | null;
missions: Mission[];
articles: any[];
members: Member[];
}
interface ContestsResponse {
hasNextPage: boolean;
contests: Contest[];
}
export interface CreateContestBody {
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 {
fetchContests: {
contests: Contest[];
hasNextPage: boolean;
status: Status;
error: string | null;
};
fetchContestById: {
contest: Contest | null;
status: Status;
error: string | null;
};
createContest: {
contest: Contest | null;
status: Status;
error: string | null;
};
fetchMyContests: {
contests: Contest[];
status: Status;
error: string | null;
};
fetchRegisteredContests: {
contests: Contest[];
hasNextPage: boolean;
status: Status;
error: string | null;
};
}
const initialState: ContestsState = {
fetchContests: {
contests: [],
hasNextPage: false,
status: 'idle',
error: null,
},
fetchContestById: {
contest: null,
status: 'idle',
error: null,
},
createContest: {
contest: null,
status: 'idle',
error: null,
},
fetchMyContests: {
contests: [],
status: 'idle',
error: null,
},
fetchRegisteredContests: {
contests: [],
hasNextPage: false,
status: 'idle',
error: null,
},
};
// =====================
// Async Thunks
// =====================
// Все контесты
export const fetchContests = createAsyncThunk(
'contests/fetchAll',
async (
params: {
page?: number;
pageSize?: number;
groupId?: number | null;
} = {},
{ rejectWithValue },
) => {
try {
const { page = 0, pageSize = 10, groupId } = params;
const response = await axios.get<ContestsResponse>('/contests', {
params: { page, pageSize, groupId },
});
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Failed to fetch contests',
);
}
},
);
// Контест по ID
export const fetchContestById = createAsyncThunk(
'contests/fetchById',
async (id: number, { rejectWithValue }) => {
try {
const response = await axios.get<Contest>(`/contests/${id}`);
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Failed to fetch contest',
);
}
},
);
// Создание контеста
export const createContest = createAsyncThunk(
'contests/create',
async (contestData: CreateContestBody, { rejectWithValue }) => {
try {
const response = await axios.post<Contest>(
'/contests',
contestData,
);
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Failed to create contest',
);
}
},
);
// Контесты, созданные мной
export const fetchMyContests = createAsyncThunk(
'contests/fetchMyContests',
async (_, { rejectWithValue }) => {
try {
const response = await axios.get<Contest[]>('/contests/my');
// Возвращаем просто массив контестов
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Failed to fetch my contests',
);
}
},
);
// Контесты, где я зарегистрирован
export const fetchRegisteredContests = createAsyncThunk(
'contests/fetchRegisteredContests',
async (
params: { page?: number; pageSize?: number } = {},
{ rejectWithValue },
) => {
try {
const { page = 0, pageSize = 10 } = params;
const response = await axios.get<ContestsResponse>(
'/contests/registered',
{ params: { page, pageSize } },
);
return response.data;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message ||
'Failed to fetch registered contests',
);
}
},
);
// =====================
// Slice
// =====================
const contestsSlice = createSlice({
name: 'contests',
initialState,
reducers: {
clearSelectedContest: (state) => {
state.fetchContestById.contest = null;
},
},
extraReducers: (builder) => {
// fetchContests
builder.addCase(fetchContests.pending, (state) => {
state.fetchContests.status = 'loading';
state.fetchContests.error = null;
});
builder.addCase(
fetchContests.fulfilled,
(state, action: PayloadAction<ContestsResponse>) => {
state.fetchContests.status = 'successful';
state.fetchContests.contests = action.payload.contests;
state.fetchContests.hasNextPage = action.payload.hasNextPage;
},
);
builder.addCase(fetchContests.rejected, (state, action: any) => {
state.fetchContests.status = 'failed';
state.fetchContests.error = action.payload;
});
// fetchContestById
builder.addCase(fetchContestById.pending, (state) => {
state.fetchContestById.status = 'loading';
state.fetchContestById.error = null;
});
builder.addCase(
fetchContestById.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.fetchContestById.status = 'successful';
state.fetchContestById.contest = action.payload;
},
);
builder.addCase(fetchContestById.rejected, (state, action: any) => {
state.fetchContestById.status = 'failed';
state.fetchContestById.error = action.payload;
});
// createContest
builder.addCase(createContest.pending, (state) => {
state.createContest.status = 'loading';
state.createContest.error = null;
});
builder.addCase(
createContest.fulfilled,
(state, action: PayloadAction<Contest>) => {
state.createContest.status = 'successful';
state.createContest.contest = action.payload;
},
);
builder.addCase(createContest.rejected, (state, action: any) => {
state.createContest.status = 'failed';
state.createContest.error = action.payload;
});
// fetchMyContests
// fetchMyContests
builder.addCase(fetchMyContests.pending, (state) => {
state.fetchMyContests.status = 'loading';
state.fetchMyContests.error = null;
});
builder.addCase(
fetchMyContests.fulfilled,
(state, action: PayloadAction<Contest[]>) => {
state.fetchMyContests.status = 'successful';
state.fetchMyContests.contests = action.payload;
},
);
builder.addCase(fetchMyContests.rejected, (state, action: any) => {
state.fetchMyContests.status = 'failed';
state.fetchMyContests.error = action.payload;
});
// fetchRegisteredContests
builder.addCase(fetchRegisteredContests.pending, (state) => {
state.fetchRegisteredContests.status = 'loading';
state.fetchRegisteredContests.error = null;
});
builder.addCase(
fetchRegisteredContests.fulfilled,
(state, action: PayloadAction<ContestsResponse>) => {
state.fetchRegisteredContests.status = 'successful';
state.fetchRegisteredContests.contests =
action.payload.contests;
state.fetchRegisteredContests.hasNextPage =
action.payload.hasNextPage;
},
);
builder.addCase(
fetchRegisteredContests.rejected,
(state, action: any) => {
state.fetchRegisteredContests.status = 'failed';
state.fetchRegisteredContests.error = action.payload;
},
);
},
});
// =====================
// Экспорты
// =====================
export const { clearSelectedContest } = contestsSlice.actions;
export const contestsReducer = contestsSlice.reducer;