Skip to content

Commit 1173ac6

Browse files
committed
Merge branch 'main' into deploy-hj
2 parents cf197f0 + 8d6b15b commit 1173ac6

3 files changed

Lines changed: 119 additions & 17 deletions

File tree

src/pages/AssignmentPage/ProblemSolvePage/hooks/useProblemSolve.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,21 @@ export function useProblemSolve() {
425425
return;
426426
}
427427

428+
const deadlineRaw = assignmentInfo.endDate || assignmentInfo.dueDate;
429+
if (
430+
!isManager &&
431+
deadlineRaw &&
432+
Date.now() > new Date(deadlineRaw).getTime()
433+
) {
434+
if (
435+
!window.confirm(
436+
"마감 시간이 지났습니다. 지금 제출하면 지각으로 표시됩니다. 계속하시겠습니까?",
437+
)
438+
) {
439+
return;
440+
}
441+
}
442+
428443
setIsSubmitting(true);
429444
setSubmissionResult(null);
430445
try {
@@ -478,7 +493,18 @@ export function useProblemSolve() {
478493
} finally {
479494
setIsSubmitting(false);
480495
}
481-
}, [code, language, sectionId, problemId, clearSessionAfterSubmission, isAssignmentActive, navigate, userRole]);
496+
}, [
497+
code,
498+
language,
499+
sectionId,
500+
problemId,
501+
clearSessionAfterSubmission,
502+
isAssignmentActive,
503+
navigate,
504+
userRole,
505+
assignmentInfo.endDate,
506+
assignmentInfo.dueDate,
507+
]);
482508

483509
const handleSubmitWithOutput = useCallback(async () => {
484510
if (!code.trim()) {

src/pages/TutorPage/Grades/GradeManagement/hooks/useGradeManagement.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
CodeResponse,
1818
ProblemGrade,
1919
} from "../types";
20+
import { formatLateDurationForGradeCsv } from "../utils/gradeExportLateDuration";
2021

2122
export function useGradeManagement() {
2223
const { sectionId } = useParams<{ sectionId?: string }>();
@@ -774,16 +775,6 @@ export function useGradeManagement() {
774775
if (dueAt && new Date() > new Date(dueAt)) return '"미제출"';
775776
return '""';
776777
};
777-
/** 지각시간: ZIP submissions.csv와 동일하게 서버 계산값(lateDuration)만 사용 */
778-
const formatLateDurationForCSV = (
779-
pg: ProblemGrade | null | undefined,
780-
): string => {
781-
if (!pg?.submitted) return '""';
782-
const t = pg.lateDuration?.trim();
783-
if (!t) return '""';
784-
return `"${t.replace(/"/g, '""')}"`;
785-
};
786-
787778
// 전체 과제 보기: 과제만 필터한 courseGrades로 내보내기
788779
if (
789780
viewMode === "assignment" &&
@@ -838,7 +829,9 @@ export function useGradeManagement() {
838829
getSubmissionDisplayForCSV(problemGrade?.submittedAt, dueAt),
839830
);
840831
row.push(formatDateForCSV(dueAt));
841-
row.push(formatLateDurationForCSV(problemGrade));
832+
row.push(
833+
formatLateDurationForGradeCsv(problemGrade, dueAt),
834+
);
842835
if (typeof score === "number") {
843836
totalAllScore += score;
844837
}
@@ -929,7 +922,9 @@ export function useGradeManagement() {
929922
getSubmissionDisplayForCSV(problemGrade?.submittedAt, dueAt),
930923
);
931924
row.push(formatDateForCSV(dueAt));
932-
row.push(formatLateDurationForCSV(problemGrade));
925+
row.push(
926+
formatLateDurationForGradeCsv(problemGrade, dueAt),
927+
);
933928
if (typeof score === "number") {
934929
totalAllScore += score;
935930
}
@@ -1016,7 +1011,9 @@ export function useGradeManagement() {
10161011
getSubmissionDisplayForCSV(problemGrade?.submittedAt, dueAt),
10171012
);
10181013
row.push(formatDateForCSV(dueAt));
1019-
row.push(formatLateDurationForCSV(problemGrade));
1014+
row.push(
1015+
formatLateDurationForGradeCsv(problemGrade, dueAt),
1016+
);
10201017
if (score !== "" && score !== null && typeof score === "number") {
10211018
totalAllScore += score;
10221019
}
@@ -1045,7 +1042,9 @@ export function useGradeManagement() {
10451042
getSubmissionDisplayForCSV(problemGrade?.submittedAt, dueAt),
10461043
);
10471044
row.push(formatDateForCSV(dueAt));
1048-
row.push(formatLateDurationForCSV(problemGrade));
1045+
row.push(
1046+
formatLateDurationForGradeCsv(problemGrade, dueAt),
1047+
);
10491048
if (score !== "" && score !== null && typeof score === "number") {
10501049
totalAllScore += score;
10511050
}
@@ -1113,7 +1112,9 @@ export function useGradeManagement() {
11131112
row.push(String(score));
11141113
row.push(getSubmissionDisplayForCSV(problem.submittedAt, quizDueAt));
11151114
row.push(formatDateForCSV(quizDueAt));
1116-
row.push(formatLateDurationForCSV(problem));
1115+
row.push(
1116+
formatLateDurationForGradeCsv(problem, quizDueAt),
1117+
);
11171118
}
11181119
const totalScore = student.totalScore ?? 0;
11191120
const totalPoints = student.totalPoints ?? 0;
@@ -1177,7 +1178,9 @@ export function useGradeManagement() {
11771178
getSubmissionDisplayForCSV(problem.submittedAt, assignmentDueAt),
11781179
);
11791180
row.push(formatDateForCSV(assignmentDueAt));
1180-
row.push(formatLateDurationForCSV(problem));
1181+
row.push(
1182+
formatLateDurationForGradeCsv(problem, assignmentDueAt),
1183+
);
11811184
}
11821185
const totalScore = student.totalScore ?? 0;
11831186
const totalPoints = student.totalPoints ?? 0;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { ProblemGrade } from "../types";
2+
3+
/**
4+
* 백엔드 {@code SubmissionDeadlineComparison}과 동일한 전제:
5+
* - 제출 시각 문자열: KST 벽시계(타임존 없는 ISO)
6+
* - 마감/종료 문자열: UTC 벽시계(과제 endDate·퀴즈 endTime 저장 방식과 동일)
7+
*/
8+
function instantMsFromKstWall(raw: string): number | null {
9+
const trimmed = raw.trim().replace(" ", "T");
10+
if (!trimmed) return null;
11+
if (/Z$/i.test(trimmed) || /[+-]\d{2}:?\d{2}$/.test(trimmed)) {
12+
const t = Date.parse(trimmed);
13+
return Number.isNaN(t) ? null : t;
14+
}
15+
const base = trimmed.includes("T") ? trimmed : `${trimmed}T00:00:00`;
16+
const t = Date.parse(`${base}+09:00`);
17+
return Number.isNaN(t) ? null : t;
18+
}
19+
20+
function instantMsFromUtcWall(raw: string): number | null {
21+
const trimmed = raw.trim().replace(" ", "T");
22+
if (!trimmed) return null;
23+
if (/Z$/i.test(trimmed)) {
24+
const t = Date.parse(trimmed);
25+
return Number.isNaN(t) ? null : t;
26+
}
27+
if (/[+-]\d{2}:?\d{2}$/.test(trimmed)) {
28+
const t = Date.parse(trimmed);
29+
return Number.isNaN(t) ? null : t;
30+
}
31+
const base = trimmed.includes("T") ? trimmed : `${trimmed}T00:00:00`;
32+
const t = Date.parse(`${base}Z`);
33+
return Number.isNaN(t) ? null : t;
34+
}
35+
36+
/** Java {@code SubmissionDeadlineComparison.lateDurationText} 와 동일한 분 단위·문구 */
37+
function lateDurationTextKo(submittedMs: number, dueMs: number): string {
38+
if (submittedMs <= dueMs) return "";
39+
const minutes = Math.floor((submittedMs - dueMs) / 60000);
40+
const days = Math.floor(minutes / (24 * 60));
41+
const hours = Math.floor((minutes % (24 * 60)) / 60);
42+
const mins = minutes % 60;
43+
const parts: string[] = [];
44+
if (days > 0) parts.push(`${days}일`);
45+
if (hours > 0) parts.push(`${hours}시간`);
46+
if (mins > 0 || parts.length === 0) parts.push(`${mins}분`);
47+
return parts.join(" ");
48+
}
49+
50+
function csvQuote(text: string): string {
51+
return `"${text.replace(/"/g, '""')}"`;
52+
}
53+
54+
/**
55+
* 성적보내기 CSV의 지각시간 열 (ZIP submissions.csv lateDuration 과 동일 규칙).
56+
* 제출·마감 시각이 있으면 항상 위 규칙으로 계산하고, 파싱 실패 시에만 API lateDuration 사용.
57+
*/
58+
export function formatLateDurationForGradeCsv(
59+
pg: ProblemGrade | null | undefined,
60+
dueAt: string | undefined,
61+
): string {
62+
if (!pg?.submitted) return '""';
63+
const subMs = pg.submittedAt
64+
? instantMsFromKstWall(pg.submittedAt)
65+
: null;
66+
const dueMs = dueAt ? instantMsFromUtcWall(dueAt) : null;
67+
if (subMs != null && dueMs != null) {
68+
const text = lateDurationTextKo(subMs, dueMs);
69+
return text ? csvQuote(text) : '""';
70+
}
71+
const fallback = pg.lateDuration?.trim();
72+
return fallback ? csvQuote(fallback) : '""';
73+
}

0 commit comments

Comments
 (0)