Skip to content

Commit f74b68f

Browse files
committed
feat: implement submission handling in team API; update FormSubmit and HistorySubmit components for submission management
1 parent ad4e022 commit f74b68f

4 files changed

Lines changed: 95 additions & 73 deletions

File tree

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
ResponseDetailData,
44
ScheduleDateType,
55
SchedulePresentType,
6+
SubmissionRepositoryType,
67
SubmissionType,
78
TeamType,
89
} from "~/types/team.types";
@@ -62,15 +63,14 @@ class TeamApi {
6263
}
6364

6465
static async getSubmissionInTeam(teamId: string) {
65-
const res = await privateApi.get<ResponseDetailData<{ submissionUrl: string }>>(`/teams/${teamId}/submissions`);
66+
const res = await privateApi.get<ResponseDetailData<SubmissionRepositoryType[]>>(
67+
`/teams/${teamId}/submissions`,
68+
);
6669
return res.data;
6770
}
6871

6972
static async submissions(teamId: string, data: SubmissionType) {
70-
const res = await privateApi.post<ResponseDetailData<{ submissionUrl: string }>>(
71-
`/teams/${teamId}/submissions`,
72-
data,
73-
);
73+
const res = await privateApi.post<ResponseDetailData<string>>(`/teams/${teamId}/submissions`, data);
7474
return res.data;
7575
}
7676
}

frontend/src/pages/Submissions/FormSubmit.tsx

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,49 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import { FileText, Github, Link2, Send } from "lucide-react";
33
import { Button } from "~/components/ui/button";
44
import { Input } from "~/components/ui/input";
55
import { Textarea } from "~/components/ui/textarea";
6+
import { useMutation, useQueryClient } from "@tanstack/react-query";
7+
import TeamApi from "~/api-requests/team.requests";
8+
import Notification from "~/utils/notification";
9+
import { useAppSelector } from "~/hooks/useRedux";
10+
import type { AxiosError } from "axios";
611

712
const FormSubmit = () => {
13+
const userInfo = useAppSelector((state) => state.user.userInfo);
14+
const teamId = userInfo.candidate?.teamId || "";
15+
const queryClient = useQueryClient();
16+
17+
const [presentationLink, setPresentationLink] = useState("");
18+
const [productLink, setProductLink] = useState("");
19+
const [note, setNote] = useState("");
20+
21+
const submitMutation = useMutation({
22+
mutationFn: (data: { presentationLink: string; productLink: string; note: string }) =>
23+
TeamApi.submissions(teamId, data),
24+
onError: (error: AxiosError<{ message?: string }>) => {
25+
Notification.error({
26+
text: error.response?.data?.message || "Nộp bài thất bại!",
27+
});
28+
},
29+
onSuccess: () => {
30+
queryClient.invalidateQueries({ queryKey: ["submissions", teamId] });
31+
Notification.success({
32+
text: "Nộp bài thành công!",
33+
});
34+
setPresentationLink("");
35+
setProductLink("");
36+
setNote("");
37+
},
38+
});
39+
840
const handleSubmit = async (e: React.FormEvent) => {
941
e.preventDefault();
42+
submitMutation.mutate({
43+
presentationLink,
44+
productLink,
45+
note,
46+
});
1047
};
1148

1249
return (
@@ -34,6 +71,8 @@ const FormSubmit = () => {
3471
type="url"
3572
placeholder="https://drive.google.com/..."
3673
className="focus:ring-primary/20 transition-all focus:ring-2"
74+
value={presentationLink}
75+
onChange={(e) => setPresentationLink(e.target.value)}
3776
required
3877
/>
3978
<p className="flex items-center gap-1.5 text-xs text-gray-500">
@@ -53,6 +92,8 @@ const FormSubmit = () => {
5392
type="url"
5493
placeholder="GitHub, GitLab, Figma..."
5594
className="focus:ring-primary/20 transition-all focus:ring-2"
95+
value={productLink}
96+
onChange={(e) => setProductLink(e.target.value)}
5697
/>
5798
</div>
5899

@@ -66,7 +107,9 @@ const FormSubmit = () => {
66107
id="note"
67108
placeholder="Mô tả ngắn về sản phẩm, tính năng nổi bật, công nghệ sử dụng..."
68109
className="focus:ring-primary/20 min-h-[120px] resize-none transition-all focus:ring-2"
69-
// rows={5}
110+
value={note}
111+
onChange={(e) => setNote(e.target.value)}
112+
maxLength={500}
70113
/>
71114
<p className="flex items-center gap-1.5 text-xs text-gray-500">
72115
<span className="mt-0.5 h-1 w-1 flex-shrink-0 rounded-full bg-gray-400"></span>
@@ -77,9 +120,13 @@ const FormSubmit = () => {
77120

78121
{/* Submit Button */}
79122
<div className="flex items-center gap-3 border-t border-gray-200/70 pt-5">
80-
<Button type="submit" className="group flex items-center gap-2 transition-all hover:shadow-md">
123+
<Button
124+
type="submit"
125+
className="group flex items-center gap-2 transition-all hover:shadow-md"
126+
disabled={submitMutation.isPending}
127+
>
81128
<Send className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
82-
Nộp bài
129+
{submitMutation.isPending ? "Đang nộp..." : "Nộp bài"}
83130
</Button>
84131
<p className="text-xs text-gray-500">
85132
<span className="font-semibold text-red-600">Lưu ý:</span> Kiểm tra kỹ thông tin trước khi nộp

frontend/src/pages/Submissions/HistorySubmit.tsx

Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,24 @@
1-
import { ExternalLink, History, Clock, CheckCircle2, AlertCircle, XCircle } from "lucide-react";
2-
import { useState } from "react";
3-
interface Submission {
4-
id: number;
5-
phoneNumber: string;
6-
productLink: string;
7-
codeLink: string;
8-
submittedAt: string;
9-
status: "pending" | "approved" | "rejected";
10-
}
1+
import { ExternalLink, History, Clock } from "lucide-react";
2+
import { useQuery } from "@tanstack/react-query";
3+
import TeamApi from "~/api-requests/team.requests";
4+
import { useAppSelector } from "~/hooks/useRedux";
5+
import Loading from "~/components/Loading";
116

127
const HistorySubmit = () => {
13-
const [submissions] = useState<Submission[]>([
14-
{
15-
id: 1,
16-
phoneNumber: "0123456789",
17-
productLink: "https://demo.example.com",
18-
codeLink: "https://github.com/user/project",
19-
submittedAt: "2025-12-19 14:30:00",
20-
status: "approved",
21-
},
22-
{
23-
id: 2,
24-
phoneNumber: "0123456789",
25-
productLink: "https://demo-v2.example.com",
26-
codeLink: "https://github.com/user/project-v2",
8+
const userInfo = useAppSelector((state) => state.user.userInfo);
9+
const teamId = userInfo.candidate?.teamId || "";
2710

28-
submittedAt: "2025-12-19 16:45:00",
29-
status: "pending",
11+
const { data: submissions, isLoading } = useQuery({
12+
queryKey: ["submissions", teamId],
13+
queryFn: async () => {
14+
if (!teamId) return [];
15+
const res = await TeamApi.getSubmissionInTeam(teamId);
16+
return res.result;
3017
},
31-
]);
18+
enabled: !!teamId,
19+
});
3220

33-
const getStatusBadge = (status: Submission["status"]) => {
34-
switch (status) {
35-
case "approved":
36-
return (
37-
<span className="inline-flex items-center gap-1.5 rounded-md border border-green-200/50 bg-green-50 px-2.5 py-1 text-xs font-medium text-green-700">
38-
<CheckCircle2 className="h-3.5 w-3.5" />
39-
Đã ghi nhận
40-
</span>
41-
);
42-
case "rejected":
43-
return (
44-
<span className="inline-flex items-center gap-1.5 rounded-md border border-red-200/50 bg-red-50 px-2.5 py-1 text-xs font-medium text-red-700">
45-
<XCircle className="h-3.5 w-3.5" />
46-
Không hợp lệ
47-
</span>
48-
);
49-
case "pending":
50-
default:
51-
return (
52-
<span className="inline-flex items-center gap-1.5 rounded-md border border-yellow-200/50 bg-yellow-50 px-2.5 py-1 text-xs font-medium text-yellow-700">
53-
<AlertCircle className="h-3.5 w-3.5" />
54-
Chờ xác nhận
55-
</span>
56-
);
57-
}
58-
};
21+
if (isLoading) return <Loading />;
5922

6023
return (
6124
<div className="overflow-hidden rounded-md border border-gray-200/70 bg-white">
@@ -67,7 +30,7 @@ const HistorySubmit = () => {
6730
</div>
6831

6932
<div className="overflow-x-auto">
70-
{submissions.length === 0 ? (
33+
{!submissions || submissions.length === 0 ? (
7134
<div className="py-16 text-center">
7235
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-gray-100">
7336
<History className="h-8 w-8 text-gray-400" />
@@ -88,9 +51,8 @@ const HistorySubmit = () => {
8851
<th className="px-4 py-3 text-left text-xs font-semibold tracking-wide text-gray-600 uppercase sm:px-6 sm:py-3.5">
8952
Link sản phẩm
9053
</th>
91-
9254
<th className="px-4 py-3 text-left text-xs font-semibold tracking-wide text-gray-600 uppercase sm:px-6 sm:py-3.5">
93-
Trạng thái
55+
Ghi chú
9456
</th>
9557
</tr>
9658
</thead>
@@ -123,30 +85,33 @@ const HistorySubmit = () => {
12385
<td className="px-4 py-3.5 text-sm sm:px-6 sm:py-4">
12486
<div className="flex flex-col gap-2">
12587
<a
126-
href={submission.productLink}
88+
href={submission.presentationLink}
12789
target="_blank"
12890
rel="noopener noreferrer"
12991
className="text-primary hover:text-primary/80 flex items-center gap-1.5 font-medium transition-colors"
13092
>
13193
<ExternalLink className="h-3.5 w-3.5" />
132-
<span>Xem sản phẩm</span>
94+
<span>Slide & Sheet</span>
13395
</a>
134-
{submission.codeLink && (
96+
{submission.productLink && (
13597
<a
136-
href={submission.codeLink}
98+
href={submission.productLink}
13799
target="_blank"
138100
rel="noopener noreferrer"
139101
className="flex items-center gap-1.5 text-gray-600 transition-colors hover:text-gray-900"
140102
>
141103
<ExternalLink className="h-3.5 w-3.5" />
142-
<span>Mã nguồn</span>
104+
<span>Source/Figma</span>
143105
</a>
144106
)}
145107
</div>
146108
</td>
147-
148-
<td className="px-4 py-3.5 text-sm whitespace-nowrap sm:px-6 sm:py-4">
149-
{getStatusBadge(submission.status)}
109+
<td className="px-4 py-3.5 text-sm text-gray-600 sm:px-6 sm:py-4">
110+
<p className="max-w-xs truncate">
111+
{submission.note || (
112+
<span className="text-gray-400 italic">Không có ghi chú</span>
113+
)}
114+
</p>
150115
</td>
151116
</tr>
152117
);

frontend/src/types/team.types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,13 @@ export type SubmissionType = {
9999
productLink: string;
100100
note: string;
101101
};
102+
103+
export type SubmissionRepositoryType = {
104+
id: string;
105+
teamId: string;
106+
userId: string;
107+
presentationLink: string;
108+
productLink: string;
109+
note: string;
110+
submittedAt: string;
111+
};

0 commit comments

Comments
 (0)