Skip to content

Commit e225c78

Browse files
committed
feat: implement schedule presentation retrieval and update related components
1 parent 6d0a304 commit e225c78

10 files changed

Lines changed: 225 additions & 76 deletions

File tree

backend/prisma/schema.prisma

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ model SchedulePresent {
179179
180180
trialDate String @unique @map("trial_date") @db.VarChar(255)
181181
officialDate Json @map("offical_date") @db.Json
182-
finalDate String? @unique @map("final_date") @db.VarChar(255)
182+
finalDate String? @map("final_date") @db.VarChar(255)
183183
184184
createdAt DateTime @default(now()) @map("created_at")
185185
updatedAt DateTime @updatedAt @map("updated_at")

backend/src/controllers/team.controllers.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,19 @@ export const createSchedulePresentation = async (
4747
return next(error);
4848
}
4949
};
50-
5150
export const getSchedulePresentation = async (
51+
req: Request<ParamsDictionary, {}, {}>,
52+
res: Response,
53+
next: NextFunction,
54+
) => {
55+
try {
56+
const result = await teamService.getSchedulePresentationAll();
57+
return res.status(HTTP_STATUS.OK).json(new ResponseClient({ result }));
58+
} catch (error) {
59+
return next(error);
60+
}
61+
};
62+
export const getSchedulePresentationInTeam = async (
5263
req: Request<ParamsDictionary, {}, { teamId: string }>,
5364
res: Response,
5465
next: NextFunction,
@@ -57,7 +68,7 @@ export const getSchedulePresentation = async (
5768
const userId = req.userId!;
5869
const { teamId } = req.params;
5970
console.log("teamId", userId, teamId);
60-
71+
6172
const result = await teamService.getSchedulePresentation(userId, teamId);
6273
return res.status(HTTP_STATUS.OK).json(new ResponseClient({ result }));
6374
} catch (error) {

backend/src/repositories/team.repository.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,10 @@ class TeamRepository {
268268
},
269269
});
270270
};
271+
272+
findAllPresentationSchedules = async () => {
273+
return prisma.schedulePresent.findMany();
274+
};
271275
}
272276

273277
const teamRepository = new TeamRepository();

backend/src/routes/team.routes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ const teamRouter = Router();
1515
// teamRouter.get("/", auth, isRole([RoleType.ADMIN, RoleType.MENTOR]), validate(getAllSchema), teamController.getAll);
1616
teamRouter.get("/", auth, validate(getAllSchema), teamController.getAll);
1717
teamRouter.post("/present", auth, teamController.createSchedulePresentation);
18-
teamRouter.get("/get-schedule/:teamId", auth, teamController.getSchedulePresentation);
18+
19+
// get các lịch đã có thể đăng ký
20+
teamRouter.get("/get-schedule-all", auth, teamController.getSchedulePresentation);
21+
22+
teamRouter.get("/get-schedule/:teamId", auth, teamController.getSchedulePresentationInTeam);
1923

2024
teamRouter.get("/:id", auth, validate(idParamSchema), teamController.getDetail);
2125

backend/src/services/team.service.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { RoleType } from "~/constants/enums";
22
import { HTTP_STATUS } from "~/constants/httpStatus";
33
import teamRepository from "~/repositories/team.repository";
44
import { ErrorWithStatus } from "~/rules/error";
5-
5+
interface TimeSlots {
6+
[date: string]: string[];
7+
}
68
class TeamService {
79
async getAll({
810
page,
@@ -154,6 +156,24 @@ class TeamService {
154156
message: "Chỉ trưởng nhóm mới có quyền đăng ký lịch trình thuyết trình.",
155157
});
156158
}
159+
// 1 nhóm chỉ dăng ký 1 lần
160+
const existingSchedule = await teamRepository.findSchedulePresentationByTeamId(teamId);
161+
if (existingSchedule) {
162+
throw new ErrorWithStatus({
163+
status: HTTP_STATUS.BAD_REQUEST,
164+
message: "Nhóm đã đăng ký lịch trình thuyết trình, không thể đăng ký lại.",
165+
});
166+
}
167+
// present thử đủ 10 slot thì k cho đăng ký nữa (nếu dữ liệu cột trial_date có data và đủ 10 cái thì k cho đăng ký)
168+
const schedules = await teamRepository.findAllPresentationSchedules();
169+
const countOnTrialDate = schedules.filter((s) => s.trialDate.length > 0).length;
170+
if (countOnTrialDate >= 10) {
171+
throw new ErrorWithStatus({
172+
status: HTTP_STATUS.BAD_REQUEST,
173+
message: "Ngày thuyết trình thử đã đủ số nhóm đăng ký. Bạn không thể đăng ký thêm.",
174+
});
175+
}
176+
157177
const created = await teamRepository.createPresentationSchedule({
158178
teamId,
159179
trialDate,
@@ -179,6 +199,57 @@ class TeamService {
179199
}
180200
return schedule;
181201
}
202+
203+
async getSchedulePresentationAll() {
204+
const trialTimeSlots: TimeSlots = {
205+
"17/01/2026": ["10h05 - 10h50", "16h05 - 16h50", "19h05 - 19h50", "20h05 - 20h50"],
206+
"18/01/2026": ["15h05 - 15h50", "16h05 - 16h50", "19h05 - 19h50", "20h05 - 20h50"],
207+
"19/01/2026": ["09h05 - 09h50", "10h05 - 10h50", "19h05 - 19h50", "20h05 - 20h50"],
208+
"20/01/2026": ["19h05 - 19h50", "20h05 - 20h50"],
209+
"21/01/2026": ["19h05 - 19h50", "20h05 - 20h50"],
210+
};
211+
212+
const officialTimeSlots: TimeSlots = {
213+
"24/01/2026": ["18h - 19h", "19h15 - 20h15"],
214+
"26/01/2026": ["18h - 19h", "19h15 - 20h15"],
215+
"27/01/2026": ["18h - 19h", "19h15 - 20h15"],
216+
"28/01/2026": ["18h - 19h", "19h15 - 20h15"],
217+
"29/01/2026": ["18h - 19h", "19h15 - 20h15"],
218+
"30/01/2026": ["18h - 19h", "19h15 - 20h15"],
219+
"31/01/2026": ["18h - 19h", "19h15 - 20h15"],
220+
};
221+
222+
const schedules = await teamRepository.findAllPresentationSchedules();
223+
224+
// Tách trialDate thành date và time slot
225+
const bookedTrialSlots = schedules
226+
.filter((s) => s.trialDate)
227+
.map((s) => {
228+
const [date, timeSlot] = s.trialDate.split("|");
229+
return { date, timeSlot };
230+
});
231+
232+
const availableTrialSchedules = Object.entries(trialTimeSlots).map(([date, slots]) => ({
233+
date,
234+
slots: slots.map((slot) => ({
235+
time: slot,
236+
disabled: bookedTrialSlots.some((booked) => booked.date === date && booked.timeSlot === slot),
237+
})),
238+
}));
239+
240+
const availableOfficialSchedules = Object.entries(officialTimeSlots).map(([date, slots]) => ({
241+
date,
242+
slots: slots.map((slot) => ({
243+
time: slot,
244+
// disabled: bookedOfficialDates.includes(date),
245+
})),
246+
}));
247+
248+
return {
249+
trialSchedules: availableTrialSchedules,
250+
officialSchedules: availableOfficialSchedules,
251+
};
252+
}
182253
}
183254

184255
const teamService = new TeamService();

frontend/src/api-requests/team.requests.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import type { ResponseDetailData, SchedulePresentType, TeamType } from "~/types/team.types";
1+
import type {
2+
OfficialScheduleDateType,
3+
ResponseDetailData,
4+
ScheduleDateType,
5+
SchedulePresentType,
6+
TeamType,
7+
} from "~/types/team.types";
28
import { privateApi } from "~/utils/axiosInstance";
39

410
class TeamApi {
@@ -40,8 +46,16 @@ class TeamApi {
4046
const res = await privateApi.post(`/teams/present`, { teamId, trialDate, officialDate });
4147
return res.data;
4248
}
43-
44-
static async getSchedulePresentation(teamId: string) {
49+
static async getSchedulePresentationAll() {
50+
const res = await privateApi.get<
51+
ResponseDetailData<{
52+
trialSchedules: ScheduleDateType[];
53+
officialSchedules: OfficialScheduleDateType[];
54+
}>
55+
>(`/teams/get-schedule-all`);
56+
return res.data;
57+
}
58+
static async getSchedulePresentationInTeam(teamId: string) {
4559
const res = await privateApi.get<ResponseDetailData<SchedulePresentType>>(`/teams/get-schedule/${teamId}`);
4660
return res.data;
4761
}

frontend/src/components/Header/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BadgeQuestionMark, ChevronDown, House, Menu, ServerCrash, Users, X } from "lucide-react";
1+
import { BadgeQuestionMark, ChevronDown, House, Menu, Users, X } from "lucide-react";
22
import { Link, useLocation } from "react-router";
33
import useAuth from "~/hooks/useAuth";
44
import { useState, useRef, useEffect } from "react";

0 commit comments

Comments
 (0)