Skip to content

Commit 5d590a9

Browse files
authored
Merge pull request #66 from F-Code-Project-Mini/dev
Dev
2 parents 84075fc + 4037b5f commit 5d590a9

13 files changed

Lines changed: 359 additions & 157 deletions

File tree

backend/prisma/schema.prisma

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ model Team {
5757
topicId String @map("topic_id") @db.VarChar(36)
5858
mentorNote String? @map("mentor_note") @db.Text
5959
60-
mentorship Mentorship @relation("MentorshipToTeam", fields: [mentorshipId], references: [id])
61-
leader Candidate? @relation("TeamLeader", fields: [leaderId], references: [id])
62-
topic Topic @relation(fields: [topicId], references: [id])
63-
candidates Candidate[] @relation("CandidateToTeam")
64-
submissions Submission[]
65-
reports Report[]
66-
presents Present[]
60+
mentorship Mentorship @relation("MentorshipToTeam", fields: [mentorshipId], references: [id])
61+
leader Candidate? @relation("TeamLeader", fields: [leaderId], references: [id])
62+
topic Topic @relation(fields: [topicId], references: [id])
63+
candidates Candidate[] @relation("CandidateToTeam")
64+
submissions Submission[]
65+
reports Report[]
66+
schedulePresents SchedulePresent[]
6767
6868
@@map("teams")
6969
}
@@ -173,20 +173,20 @@ model BaremScore {
173173
@@map("scores")
174174
}
175175

176-
model Present {
176+
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
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)
182183
183184
createdAt DateTime @default(now()) @map("created_at")
184185
updatedAt DateTime @updatedAt @map("updated_at")
185186
186-
// Quan hệ với model Team
187187
team Team @relation(fields: [teamId], references: [id])
188188
189-
@@map("presents")
189+
@@map("schedule_presents")
190190
}
191191

192192
enum Role {

backend/src/controllers/team.controllers.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const createSchedulePresentation = async (
3232
) => {
3333
const userId = req.userId!;
3434
const { teamId, trialDate, officialDate } = req.body;
35+
console.log("teamId, trialDate, officialDate", teamId, trialDate, officialDate);
3536
try {
3637
const result = await teamService.createSchedulePresentation({
3738
userId,
@@ -47,6 +48,23 @@ export const createSchedulePresentation = async (
4748
}
4849
};
4950

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+
5068
export const getDetail = async (req: Request<{ id: string }>, res: Response, next: NextFunction) => {
5169
try {
5270
const result = await teamService.getDetail(req.params.id, req.role as RoleType);

backend/src/repositories/team.repository.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import prisma from "~/configs/prisma";
22
import { paginate } from "~/utils/pagination";
33
import userRespository from "./user.repository";
44
import { RoleType } from "~/constants/enums";
5-
import Present from "~/schemas/present.schema";
5+
import Present from "~/schemas/schedule-present.schema";
6+
import SchedulePresent from "~/schemas/schedule-present.schema";
67

78
class TeamRepository {
89
findWithPagination = async () => {
@@ -229,8 +230,20 @@ class TeamRepository {
229230
return !!team;
230231
};
231232

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+
232245
isTrialDateExists = async (trialDate: string) => {
233-
const team = await prisma.present.findFirst({
246+
const team = await prisma.schedulePresent.findFirst({
234247
where: {
235248
trialDate,
236249
},
@@ -239,14 +252,22 @@ class TeamRepository {
239252
};
240253

241254
createPresentationSchedule = async (data: { teamId: string; trialDate: string; officialDate: string[] }) => {
242-
return prisma.present.create({
243-
data: new Present({
255+
return prisma.schedulePresent.create({
256+
data: new SchedulePresent({
244257
teamId: data.teamId,
245258
trialDate: data.trialDate,
246259
officialDate: data.officialDate,
247260
}),
248261
});
249262
};
263+
264+
findSchedulePresentationByTeamId = async (teamId: string) => {
265+
return prisma.schedulePresent.findFirst({
266+
where: {
267+
teamId,
268+
},
269+
});
270+
};
250271
}
251272

252273
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,
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
import { v4 as uuidv4 } from "uuid";
22

3-
interface PresentType {
3+
interface SchedulePresentType {
44
id?: string;
55
teamId: string;
66
trialDate: string;
77
officialDate: string[];
8+
finalDate?: string;
89
createdAt?: Date;
910
updatedAt?: Date;
1011
}
1112

12-
class Present {
13+
class SchedulePresent {
1314
id: string;
1415
teamId: string;
1516
trialDate: string;
1617
officialDate: string[];
18+
finalDate?: string;
1719
createdAt: Date;
1820
updatedAt: Date;
19-
constructor(present: PresentType) {
21+
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
}
2730
}
2831

29-
export default Present;
32+
export default SchedulePresent;

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: 18 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 {
@@ -28,5 +28,22 @@ class TeamApi {
2828
});
2929
return res.data;
3030
}
31+
static async createSchedulePresentation({
32+
teamId,
33+
trialDate,
34+
officialDate,
35+
}: {
36+
teamId: string;
37+
trialDate: string;
38+
officialDate: string[];
39+
}) {
40+
const res = await privateApi.post(`/teams/present`, { teamId, trialDate, officialDate });
41+
return res.data;
42+
}
43+
44+
static async getSchedulePresentation(teamId: string) {
45+
const res = await privateApi.get<ResponseDetailData<SchedulePresentType>>(`/teams/present/${teamId}`);
46+
return res.data;
47+
}
3148
}
3249
export default TeamApi;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Calendar, Clock } from "lucide-react";
2+
import {
3+
AlertDialog,
4+
AlertDialogAction,
5+
AlertDialogCancel,
6+
AlertDialogContent,
7+
AlertDialogDescription,
8+
AlertDialogFooter,
9+
AlertDialogHeader,
10+
AlertDialogTitle,
11+
} from "~/components/ui/alert-dialog";
12+
13+
const ConfirmRegister = ({
14+
handleConfirmSubmit,
15+
showConfirmDialog,
16+
setShowConfirmDialog,
17+
trialSlot,
18+
officialSlots,
19+
}: {
20+
handleConfirmSubmit: () => void;
21+
showConfirmDialog: boolean;
22+
setShowConfirmDialog: React.Dispatch<React.SetStateAction<boolean>>;
23+
trialSlot: string;
24+
officialSlots: string[];
25+
}) => {
26+
return (
27+
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
28+
<AlertDialogContent className="max-w-2xl">
29+
<AlertDialogHeader>
30+
<AlertDialogTitle>Xác nhận đăng ký thời gian thuyết trình</AlertDialogTitle>
31+
<AlertDialogDescription>
32+
Kiểm tra lại thông tin đăng ký của bạn trước khi xác nhận.
33+
</AlertDialogDescription>
34+
</AlertDialogHeader>
35+
36+
<div className="space-y-4 pb-4">
37+
<div className="space-y-2">
38+
<div className="flex items-center gap-2">
39+
<Clock className="h-4 w-4 text-blue-500" />
40+
<h4 className="font-semibold text-gray-900">Thuyết trình thử</h4>
41+
</div>
42+
{trialSlot ? (
43+
<div className="rounded-md border-1 border-blue-400 bg-blue-50 p-3">
44+
<div className="flex items-center gap-2 text-sm">
45+
<Calendar className="h-3.5 w-3.5 text-blue-600" />
46+
<span className="font-medium text-blue-900">{trialSlot.split("|")[0]}</span>
47+
<span className="text-blue-600"> - </span>
48+
<span className="text-blue-800">{trialSlot.split("|")[1]}</span>
49+
</div>
50+
</div>
51+
) : (
52+
<div className="">
53+
<p className="text-sm text-gray-500 italic">Không đăng ký</p>
54+
</div>
55+
)}
56+
</div>
57+
58+
<div className="space-y-2">
59+
<div className="flex items-center gap-2">
60+
<Clock className="h-4 w-4 text-green-600" />
61+
<h4 className="font-semibold text-green-700">Thuyết trình chính thức</h4>
62+
</div>
63+
<div className="space-y-2">
64+
{officialSlots.map((slot) => (
65+
<div key={slot} className="bg-primary/10 border-primary rounded-md border-1 p-3">
66+
<div className="flex items-center gap-2 text-sm">
67+
<Calendar className="h-3.5 w-3.5 text-green-600" />
68+
<span className="font-medium text-green-900">{slot.split("|")[0]}</span>
69+
<span className="text-green-600"> - </span>
70+
<span className="text-green-800">{slot.split("|")[1]}</span>
71+
</div>
72+
</div>
73+
))}
74+
</div>
75+
</div>
76+
77+
<div className="rounded-md border border-amber-200 bg-amber-50 p-3">
78+
<p className="text-sm text-amber-800">
79+
<span className="font-semibold">Lưu ý:</span> Sau khi xác nhận, thông tin sẽ được gửi đến
80+
ban tổ chức. Đảm bảo tất cả thành viên có thể tham gia các khung giờ đã chọn.
81+
</p>
82+
</div>
83+
</div>
84+
85+
<AlertDialogFooter>
86+
<AlertDialogCancel>Hủy</AlertDialogCancel>
87+
<AlertDialogAction onClick={handleConfirmSubmit}>Xác nhận</AlertDialogAction>
88+
</AlertDialogFooter>
89+
</AlertDialogContent>
90+
</AlertDialog>
91+
);
92+
};
93+
94+
export default ConfirmRegister;

0 commit comments

Comments
 (0)