Skip to content

Commit 4037b5f

Browse files
committed
feat: add schedule presentation retrieval and update related components
1 parent 12819c0 commit 4037b5f

12 files changed

Lines changed: 172 additions & 130 deletions

File tree

backend/prisma/schema.prisma

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,9 @@ model SchedulePresent {
177177
id String @id @default(uuid()) @db.VarChar(36)
178178
teamId String @map("team_id") @db.VarChar(36)
179179
180-
trialDate String @unique @map("trial_date") @db.VarChar(255)
181-
officialDate Json @map("offical_date") @db.Json
182-
finalDate String @unique @map("final_date") @db.VarChar(255)
180+
trialDate String @unique @map("trial_date") @db.VarChar(255)
181+
officialDate Json @map("offical_date") @db.Json
182+
finalDate String? @unique @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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ export const createSchedulePresentation = async (
4848
}
4949
};
5050

51+
export const getSchedulePresentation = async (
52+
req: Request<ParamsDictionary, {}, { teamId: string }>,
53+
res: Response,
54+
next: NextFunction,
55+
) => {
56+
try {
57+
const userId = req.userId!;
58+
const { teamId } = req.params;
59+
console.log("teamId", userId, teamId);
60+
// chỉ có thành viên mới get dc
61+
const result = await teamService.getSchedulePresentation(userId, teamId);
62+
return res.status(HTTP_STATUS.OK).json(new ResponseClient({ result }));
63+
} catch (error) {
64+
return next(error);
65+
}
66+
};
67+
5168
export const getDetail = async (req: Request<{ id: string }>, res: Response, next: NextFunction) => {
5269
try {
5370
const result = await teamService.getDetail(req.params.id, req.role as RoleType);

backend/src/repositories/team.repository.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,20 @@ class TeamRepository {
230230
return !!team;
231231
};
232232

233+
isMember = async (teamId: string, userId: string) => {
234+
const team = await prisma.user.findUnique({
235+
where: {
236+
id: userId,
237+
candidate: {
238+
teamId: teamId,
239+
},
240+
},
241+
});
242+
return !!team;
243+
};
244+
233245
isTrialDateExists = async (trialDate: string) => {
234-
const team = await prisma.present.findFirst({
246+
const team = await prisma.schedulePresent.findFirst({
235247
where: {
236248
trialDate,
237249
},
@@ -248,6 +260,14 @@ class TeamRepository {
248260
}),
249261
});
250262
};
263+
264+
findSchedulePresentationByTeamId = async (teamId: string) => {
265+
return prisma.schedulePresent.findFirst({
266+
where: {
267+
teamId,
268+
},
269+
});
270+
};
251271
}
252272

253273
const teamRepository = new TeamRepository();

backend/src/routes/team.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ 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);
1819

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

2122
teamRouter.get("/mentor/:id", auth, validate(idParamSchema), teamController.getTeamByUserId);
2223

23-
// teamRouter.post("/", auth, isRole([RoleType.ADMIN]), teamController.create);
2424
teamRouter.patch(
2525
"/:id/change-name",
2626
auth,

backend/src/schemas/schedule-present.schema.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface SchedulePresentType {
55
teamId: string;
66
trialDate: string;
77
officialDate: string[];
8+
finalDate?: string;
89
createdAt?: Date;
910
updatedAt?: Date;
1011
}
@@ -14,13 +15,15 @@ class SchedulePresent {
1415
teamId: string;
1516
trialDate: string;
1617
officialDate: string[];
18+
finalDate?: string;
1719
createdAt: Date;
1820
updatedAt: Date;
1921
constructor(present: SchedulePresentType) {
2022
this.id = present.id || uuidv4();
2123
this.teamId = present.teamId;
2224
this.trialDate = present.trialDate;
2325
this.officialDate = present.officialDate;
26+
this.finalDate = present.finalDate || "";
2427
this.createdAt = present.createdAt || new Date();
2528
this.updatedAt = present.updatedAt || new Date();
2629
}

backend/src/services/team.service.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,24 @@ class TeamService {
161161
});
162162
return created;
163163
};
164-
// async getTeamByMentor(mentorId: string) {
165-
// const teams = await teamRepository.findByMentorId(mentorId);
166-
// return teams;
167-
// }
164+
165+
async getSchedulePresentation(userId: string, teamId: string) {
166+
const isMember = await teamRepository.isMember(teamId, userId);
167+
if (!isMember) {
168+
throw new ErrorWithStatus({
169+
status: HTTP_STATUS.FORBIDDEN,
170+
message: "Bạn không có quyền xem lịch trình thuyết trình của nhóm này.",
171+
});
172+
}
173+
const schedule = await teamRepository.findSchedulePresentationByTeamId(teamId);
174+
if (!schedule) {
175+
throw new ErrorWithStatus({
176+
status: HTTP_STATUS.NOT_FOUND,
177+
message: "Lịch trình thuyết trình của nhóm không tồn tại.",
178+
});
179+
}
180+
return schedule;
181+
}
168182
}
169183

170184
const teamService = new TeamService();

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ResponseDetailData, TeamType } from "~/types/team.types";
1+
import type { ResponseDetailData, SchedulePresentType, TeamType } from "~/types/team.types";
22
import { privateApi } from "~/utils/axiosInstance";
33

44
class TeamApi {
@@ -40,5 +40,10 @@ class TeamApi {
4040
const res = await privateApi.post(`/teams/present`, { teamId, trialDate, officialDate });
4141
return res.data;
4242
}
43+
44+
static async getSchedulePresentation(teamId: string) {
45+
const res = await privateApi.get<ResponseDetailData<SchedulePresentType>>(`/teams/present/${teamId}`);
46+
return res.data;
47+
}
4348
}
4449
export default TeamApi;

frontend/src/pages/Present/FormRegisterPresent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ const FormRegisterPresent = () => {
9191
</div>
9292

9393
<form onSubmit={handleSubmit} className="space-y-6 p-5 sm:p-6">
94-
{/* Trial Presentation */}
9594
<div className="space-y-4">
9695
<div className="flex items-center gap-2">
9796
<Clock className="h-5 w-5 text-blue-500" />
@@ -125,7 +124,8 @@ const FormRegisterPresent = () => {
125124
<RadioGroupItem
126125
value={`${date}|${slot}`}
127126
id={`trial-${date}-${slot}`}
128-
className="text-blue-600"
127+
className="text-blue-600 disabled:cursor-not-allowed disabled:opacity-50"
128+
// disabled={true}
129129
/>
130130
<Label
131131
htmlFor={`trial-${date}-${slot}`}
@@ -153,7 +153,7 @@ const FormRegisterPresent = () => {
153153
</div>
154154
<p className="text-sm text-gray-600">
155155
Chọn <span className="font-semibold text-green-700">NHIỀU</span> khung giờ bạn có thể tham gia
156-
thuyết trình chính thức
156+
thuyết trình chính thức. BTC sẽ sắp xếp và thông báo lịch cụ thể sau.
157157
</p>
158158
<div className="rounded-md border-l-4 border-green-400 bg-green-50 p-3">
159159
<p className="text-xs text-green-800">

frontend/src/pages/Present/HistoryPresent.tsx

Lines changed: 0 additions & 114 deletions
This file was deleted.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Calendar, Clock, CheckCircle } from "lucide-react";
2+
3+
const SchedulePresent = () => {
4+
const hasRegistered = true;
5+
const registrationData = {
6+
trialSlot: { date: "17/01/2026", time: "7:00 - 7:45" },
7+
officialSlots: [
8+
{ date: "17/01/2026", time: "13:00 - 13:45" },
9+
{ date: "18/01/2026", time: "14:00 - 14:45" },
10+
],
11+
};
12+
13+
if (!hasRegistered) {
14+
return (
15+
<div className="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-xs">
16+
<div className="border-b border-gray-200/70 bg-gradient-to-r from-gray-50/80 to-white px-5 py-4">
17+
<h3 className="flex items-center gap-2 text-sm font-semibold text-gray-900">
18+
<Calendar className="h-4 w-4 text-gray-600" />
19+
Lịch đã đăng ký
20+
</h3>
21+
</div>
22+
<div className="px-5 py-8 text-center">
23+
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-gray-100">
24+
<Calendar className="h-6 w-6 text-gray-400" />
25+
</div>
26+
<p className="mt-3 text-sm font-medium text-gray-900">Chưa đăng ký</p>
27+
<p className="mt-1 text-xs text-gray-500">Bạn chưa đăng ký thời gian thuyết trình</p>
28+
</div>
29+
</div>
30+
);
31+
}
32+
33+
return (
34+
<div className="overflow-hidden rounded-lg border-1 border-green-200/60 shadow-xs">
35+
<div className="border-b border-green-200/60 bg-gradient-to-br from-green-50/80 to-white px-5 py-4">
36+
<h3 className="flex items-center gap-2 text-sm font-semibold text-gray-900">
37+
<CheckCircle className="h-4 w-4 text-green-600" />
38+
Lịch đã đăng ký
39+
</h3>
40+
</div>
41+
<div className="space-y-4 px-5 py-4">
42+
{registrationData.trialSlot && (
43+
<div>
44+
<div className="mb-2 flex items-center gap-1.5">
45+
<Clock className="h-3.5 w-3.5 text-blue-600" />
46+
<p className="text-sm font-semibold text-blue-900">Thuyết trình thử</p>
47+
</div>
48+
<div className="rounded-md border-1 border-blue-400 bg-blue-50 px-3 py-2">
49+
<div className="flex items-center gap-2 text-sm">
50+
<Calendar className="h-3 w-3 text-blue-600" />
51+
<span className="font-medium text-blue-900">{registrationData.trialSlot.date}</span>
52+
<span className="text-blue-500"> - </span>
53+
<span className="text-blue-800">{registrationData.trialSlot.time}</span>
54+
</div>
55+
</div>
56+
</div>
57+
)}
58+
59+
<div>
60+
<div className="mb-2 flex items-center gap-1.5">
61+
<Clock className="h-3.5 w-3.5 text-green-600" />
62+
<p className="text-sm font-semibold text-green-900">Thuyết trình chính thức</p>
63+
</div>
64+
<div className="space-y-2">
65+
{registrationData.officialSlots.map((slot, index) => (
66+
<div key={index} className="bg-primary/10 border-primary rounded-md border-1 px-3 py-2">
67+
<div className="flex items-center gap-2 text-sm">
68+
<Calendar className="h-3 w-3 text-green-600" />
69+
<span className="font-medium text-green-900">{slot.date}</span>
70+
<span className="text-green-500"> - </span>
71+
<span className="text-green-800">{slot.time}</span>
72+
</div>
73+
</div>
74+
))}
75+
</div>
76+
</div>
77+
78+
<div className="rounded-md border border-amber-200/50 bg-amber-50/50 px-3 py-2">
79+
<p className="text-sm text-amber-800">
80+
<span className="font-semibold">Lưu ý:</span> Bạn chỉ được đăng ký 1 lần duy nhất. Liên hệ ban
81+
tổ chức nếu cần thay đổi.
82+
</p>
83+
</div>
84+
</div>
85+
</div>
86+
);
87+
};
88+
89+
export default SchedulePresent;

0 commit comments

Comments
 (0)