From a5f26fcaab430a94444f65590a6ed6288879f07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=84=EC=84=9D?= Date: Fri, 3 May 2024 12:49:08 +0900 Subject: [PATCH] =?UTF-8?q?qa=20=EB=B0=B0=ED=8F=AC=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20=EB=A8=B8=EC=A7=80=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#?= =?UTF-8?q?160)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(pages): QA 오류 수정 * feat(pages): 관리자가 수정지시사항 확인 여부를 수정할 수 있도록 변경 * fix: 무한 요청 오류 수정 * fix: showNotificationSuccess 위치 변경 * fix: (chore) issn 옵셔널로 변경후 undefined여서 생기는 오류 수정 * fix: 학생등록 폼 논문제목/연락처/이메일 옵셔널 변경 및 학과로 필터링 제거 * fix: 교수 등록 폼 수정 및 학생/교수 로그인하기 기능 오류 수정 디벨롭 브랜치와 머지, #118 브랜치에서 이어서 작업 (메인에서 작업해서..ㅠㅠ) * fix: 본심 전환 모달 오류 수정 * chore: 포맷팅 * fix: 심사결과페이지 심사 중에도 확인 가능하도록 수정 * fix: 논문투고 페이지 예심/본심 뱃지 추가 * fix: 논문투고 페이지 일정 예심/본심에 따라 불러오도록 수정 * fix: 학생 심사 결과 페이지 본심 정보도 불러오도록 수정 * fix: AchievementForm isAdmin 수정 * fix: 논문정보 수정 api 스펠링 변경 저희쪽에서 변경하는게 빠를 것 같아서 수정하였습니다. * fix: 심사 정보 수정 심사의견 및 파일 업로드가능하도록 수정 * fix: 심사의견/심사의견파일 둘중 하나만 선택하도록 수정 * feat: 교수/학생 일괄 삭제 기능 추가 * feat: main workflow 추가 * chore: 주석달기 * chore: 코드 중복 제거 * fix: 테이블 헤더 수정 * fix: 로그인 안내문구 수정 * chore: 서명 미업로드 설명 추가 * 프로덕션 배포를 위해 머지합니다. (#135) * fix: 로그인 안내문구 수정 * chore: 서명 미업로드 설명 추가 * 프로덕션 배포를 위해 머지합니다. (#136) * fix: 로그인 안내문구 수정 * chore: 서명 미업로드 설명 추가 * fix: 로그인 안내문구 수정 (#146) * fix: department null인 경우 예외처리 (#148) * [FIX] 심사 내용 없을때 등록 안되도록 수정 (#158) * fix: 심사시 심사의견파일 혹은 심사의견 필수 수정 * fix: 일괄등록 안내문구 추가 * fix: 최종심사 모달창 문구 수정 * fix: 관리자 심사 수정시 심사 내용 필수로 수정 * fix: 학생 심사 결과 페이지 심사의견 볼수있도록 수정 (#156) * feat: 학생 개별삭제, 교수 개별삭제 기능 추가 (#150) * fix: department null인 경우 예외처리 (#151) * fix: 교수/학생 로그인하기 router.refresh 추가 (#152) * fix: PhaseReady 시스템 단계 기간 주석처리 (#153) * fix: PhaseEditFormatRow Date 오류수정 (#154) * fix: 수정지시사항 단계 제외 오류 해결 및 학과관리 페이지로 이동 (#155) --------- Co-authored-by: lhwdev --- src/api/apiRoute.ts | 2 +- .../results/[thesisId]/AdminReviewContent.tsx | 18 +++- .../[thesisId]/AdminReviewListContent.tsx | 19 +++- .../prof/final/[id]/ProfessorFinalForm.tsx | 9 +- .../prof/review/[id]/ProfessorReviewForm.tsx | 22 +++-- src/app/student/result/page.tsx | 20 +++- src/components/common/AppShell/AppShell.tsx | 10 +- .../common/AppShell/_elements/Navbar.tsx | 4 +- .../common/AppShell/_elements/NavbarList.tsx | 4 +- .../_elements/constant/navbarList.tsx | 2 +- .../AdminExcelRegister/AdminExcelRegister.tsx | 8 +- .../pages/AdminProfForm/AdminProfForm.tsx | 91 +++++++++++++----- .../AdminStudentForm/AdminStudentForm.tsx | 41 +++++++- .../_sections/AssignReviewerSection.tsx | 8 +- .../_sections/ThesisInfoSection.tsx | 4 +- src/components/pages/LoginForm/LoginForm.tsx | 10 +- .../pages/PhaseReady/PhaseReady.tsx | 3 +- .../pages/PhaseReady/PhaseReadyAlertRow.tsx | 4 +- .../pages/account/UserInfoEditSection.tsx | 2 +- .../department/DepartmentManageSection.tsx | 96 ++++++++++++++++++- .../ProfessorListSection.tsx | 2 +- .../pages/review/Review/FinalReview.tsx | 2 + .../pages/review/Review/ReviewResult.tsx | 8 +- .../ReviewConfirmModal/ReviewConfirmModal.tsx | 3 + .../pages/system/PhaseEditFormRow.tsx | 14 ++- src/components/pages/system/SystemSection.tsx | 93 +----------------- 26 files changed, 329 insertions(+), 170 deletions(-) diff --git a/src/api/apiRoute.ts b/src/api/apiRoute.ts index ee30445c..c5ed2229 100644 --- a/src/api/apiRoute.ts +++ b/src/api/apiRoute.ts @@ -56,7 +56,7 @@ export const API_ROUTES = { `/students/${studentId}/headReviewer/${reviewerId}`, // PUT: 학생 심사위원장 배정 deleteReviewer: (studentId: ApiId, reviewerId: ApiId) => `/students/${studentId}/reviewers/${reviewerId}`, // PUT: 학생 심사위원 배정 취소 - delete: () => "/students", // DELETE: 학생 삭제 + delete: (studentId?: ApiId) => `/students/${studentId ?? ""}`, // DELETE: 학생 삭제 }, review: { // 학생 본인의 논문 조회 diff --git a/src/app/admin/results/[thesisId]/AdminReviewContent.tsx b/src/app/admin/results/[thesisId]/AdminReviewContent.tsx index 093e3171..e6a41697 100644 --- a/src/app/admin/results/[thesisId]/AdminReviewContent.tsx +++ b/src/app/admin/results/[thesisId]/AdminReviewContent.tsx @@ -8,7 +8,7 @@ import { AdminReviewResponse, ThesisReview, UpdateReviewRequestBody } from "@/ap import { transactionTask } from "@/api/_utils/task"; import { API_ROUTES } from "@/api/apiRoute"; import { ApiDownloadButton } from "@/components/common/Buttons"; -import { showNotificationSuccess } from "@/components/common/Notifications"; +import { showNotificationError, showNotificationSuccess } from "@/components/common/Notifications"; import SectionTitle from "@/components/common/SectionTitle"; import { BasicRow, @@ -159,12 +159,26 @@ function ModalContent({ open, setOpen, data, current }: ModalProps) { task.onComplete(() => setLoading(false)); let fileUUID; - if (reviewFile) { + if (reviewFile && commentType === "심사 의견 파일") { fileUUID = (await uploadFile(reviewFile)).uuid; } else if (current.file) { fileUUID = current.file.uuid ?? undefined; } + if (thesis === "UNEXAMINED" || presentation === "UNEXAMINED") { + showNotificationError({ message: "합격 여부를 선택해주세요." }); + return; + } + + if ( + commentType === undefined || + (commentType === "심사 의견" && (comment === undefined || !comment)) || + (commentType === "심사 의견 파일" && fileUUID === undefined) + ) { + showNotificationError({ message: "심사 의견이나 심사 의견 파일을 첨부해주세요." }); + return; + } + await ClientAxios.put( current.isFinal ? API_ROUTES.review.final.put(current.id) diff --git a/src/app/admin/reviews/[thesisId]/AdminReviewListContent.tsx b/src/app/admin/reviews/[thesisId]/AdminReviewListContent.tsx index 2644ac30..4c7110dc 100644 --- a/src/app/admin/reviews/[thesisId]/AdminReviewListContent.tsx +++ b/src/app/admin/reviews/[thesisId]/AdminReviewListContent.tsx @@ -6,7 +6,7 @@ import { ClientAxios } from "@/api/ClientAxios"; import { AdminReviewResponse, ThesisReview, UpdateReviewRequestBody } from "@/api/_types/reviews"; import { transactionTask } from "@/api/_utils/task"; import { API_ROUTES } from "@/api/apiRoute"; -import { showNotificationSuccess } from "@/components/common/Notifications"; +import { showNotificationError, showNotificationSuccess } from "@/components/common/Notifications"; import SectionTitle from "@/components/common/SectionTitle"; import { BasicRow, @@ -91,12 +91,27 @@ function ModalContent({ open, setOpen, data, current }: ModalProps) { task.onComplete(() => setLoading(false)); let fileUUID; - if (reviewFile) { + + if (reviewFile && commentType === "심사 의견 파일") { fileUUID = (await uploadFile(reviewFile)).uuid; } else if (current.file) { fileUUID = current.file.uuid ?? undefined; } + if (thesis === "UNEXAMINED" || presentation === "UNEXAMINED") { + showNotificationError({ message: "합격 여부를 선택해주세요." }); + return; + } + + if ( + commentType === undefined || + (commentType === "심사 의견" && (comment === undefined || !comment)) || + (commentType === "심사 의견 파일" && fileUUID === undefined) + ) { + showNotificationError({ message: "심사 의견이나 심사 의견 파일을 첨부해주세요." }); + return; + } + await ClientAxios.put( data.stage === "REVISION" ? API_ROUTES.review.revision.put(current.id) diff --git a/src/app/prof/final/[id]/ProfessorFinalForm.tsx b/src/app/prof/final/[id]/ProfessorFinalForm.tsx index ef494c99..371227d3 100644 --- a/src/app/prof/final/[id]/ProfessorFinalForm.tsx +++ b/src/app/prof/final/[id]/ProfessorFinalForm.tsx @@ -87,8 +87,8 @@ export function ProfessorFinalForm({ }했습니다.`, }); + router.push("/prof/final"); router.refresh(); - router.push("../final"); }); return ( @@ -100,10 +100,10 @@ export function ProfessorFinalForm({ handleSubmit(input); } else { const hasCommentFile = previous.reviewFile - ? values.commentFile === null + ? values.commentFile !== null : !!values.commentFile; - if (values.comment === "" && hasCommentFile) { - showNotificationError({ message: <>심사 의견이나 심사 의견 파일을 첨부해주세요. }); + if (!values.comment && !hasCommentFile) { + showNotificationError({ message: "심사 의견이나 심사 의견 파일을 첨부해주세요." }); return; } setShowConfirmDialog(true); @@ -130,6 +130,7 @@ export function ProfessorFinalForm({ onClose={() => { setShowConfirmDialog(false); }} + isFinal /> ); diff --git a/src/app/prof/review/[id]/ProfessorReviewForm.tsx b/src/app/prof/review/[id]/ProfessorReviewForm.tsx index e7dd44f6..ad19ed0d 100644 --- a/src/app/prof/review/[id]/ProfessorReviewForm.tsx +++ b/src/app/prof/review/[id]/ProfessorReviewForm.tsx @@ -69,12 +69,20 @@ export function ProfessorReviewForm({ const isPending = input.thesis === "PENDING" || input.presentation === "PENDING"; let fileUUID; - if (input.commentFile) { + if (input.commentFile && commentType === "심사 의견 파일") { fileUUID = (await uploadFile(input.commentFile)).uuid; } else if (input.commentFile !== null) { fileUUID = previous.reviewFile?.uuid ?? undefined; } + if ( + (commentType === "심사 의견" && !input.comment) || + (commentType === "심사 의견 파일" && fileUUID === undefined) + ) { + showNotificationError({ message: "심사 의견이나 심사 의견 파일을 첨부해주세요." }); + return; + } + await ClientAxios.put( API_ROUTES.review.put(reviewId), { @@ -98,8 +106,8 @@ export function ProfessorReviewForm({ // TODO: 불필요한 fetch를 추가하긴 하지만, 이것 말고 적당한 방법이 있는지 모르겠음... // https://github.com/vercel/next.js/discussions/54075 참고: 현재는 클라이언트측 Router Cache를 완전히 비활성화할 방법이 없음 + router.push("/prof/review"); router.refresh(); - router.push("../review"); }); return ( @@ -111,10 +119,10 @@ export function ProfessorReviewForm({ handleSubmit(input); } else { const hasCommentFile = previous.reviewFile - ? values.commentFile === null + ? values.commentFile !== null : !!values.commentFile; - if (values.comment === "" && hasCommentFile) { - showNotificationError({ message: <>심사 의견이나 심사 의견 파일을 첨부해주세요. }); + if (!values.comment && !hasCommentFile) { + showNotificationError({ message: "심사 의견이나 심사 의견 파일을 첨부해주세요." }); return; } setShowConfirmDialog(true); @@ -134,8 +142,8 @@ export function ProfessorReviewForm({ review={{ thesis: values.thesis, presentation: values.presentation, - comment: values.comment, - commentFile: values.commentFile?.name ?? null, + comment: commentType === "심사 의견" ? values.comment : "", + commentFile: commentType === "심사 의견 파일" ? values.commentFile?.name ?? null : null, }} opened={showConfirmDialog} onConfirm={() => handleSubmit(values)} diff --git a/src/app/student/result/page.tsx b/src/app/student/result/page.tsx index 9ad71187..69f4bf5e 100644 --- a/src/app/student/result/page.tsx +++ b/src/app/student/result/page.tsx @@ -12,12 +12,24 @@ import { UserResponse } from "@/api/_types/user"; export default async function StudentResultPage() { const { token } = await AuthSSR({ userType: "STUDENT" }); const user = (await fetcher({ url: API_ROUTES.user.get(), token })) as UserResponse; - const { "0": pre, "1": main } = (await fetcher({ url: API_ROUTES.review.getMe(), token })) as { - "0": MyReviewResponse; - "1": MyReviewResponse; + + const response = (await fetcher({ url: API_ROUTES.review.getMe(), token })) as { + [key: string]: MyReviewResponse; }; - const thesisRes: MyReviewResponse = user.currentPhase === "PRELIMINARY" ? pre : main; + let main: MyReviewResponse; + let pre: MyReviewResponse; + + for (let i = 0; i < 3; i += 1) { + if (response[i.toString()]?.stage === "MAIN") { + main = response[i.toString()]; + } else if (response[i.toString()]?.stage === "PRELIMINARY") { + pre = response[i.toString()]; + } + } + + const thesisRes: MyReviewResponse = user.currentPhase === "PRELIMINARY" ? pre! : main!; + const thesisInfo: ThesisInfoData = { title: thesisRes.title, stage: thesisRes.stage, diff --git a/src/components/common/AppShell/AppShell.tsx b/src/components/common/AppShell/AppShell.tsx index 1a56fa17..0af48896 100644 --- a/src/components/common/AppShell/AppShell.tsx +++ b/src/components/common/AppShell/AppShell.tsx @@ -14,7 +14,10 @@ function AppShell({ children }: Props) { const { user } = useAuth(); const pathname = usePathname(); const disabledAppShell = user?.type === null || pathname === "/login"; - + const modificationFlag = + user?.type === "STUDENT" + ? user?.department.modificationFlag && user.currentPhase !== "PRELIMINARY" + : false; return ( - + {children} ); diff --git a/src/components/common/AppShell/_elements/Navbar.tsx b/src/components/common/AppShell/_elements/Navbar.tsx index 092783c5..e80eb1c8 100644 --- a/src/components/common/AppShell/_elements/Navbar.tsx +++ b/src/components/common/AppShell/_elements/Navbar.tsx @@ -30,9 +30,7 @@ function Navbar({ userType, modificationFlag }: Props) { - {userType && modificationFlag !== undefined && ( - - )} + {userType && } ); } diff --git a/src/components/common/AppShell/_elements/NavbarList.tsx b/src/components/common/AppShell/_elements/NavbarList.tsx index db9a8d12..1485bc7d 100644 --- a/src/components/common/AppShell/_elements/NavbarList.tsx +++ b/src/components/common/AppShell/_elements/NavbarList.tsx @@ -11,13 +11,13 @@ import { interface Props { userType: Role; - modificationFlag: boolean; + modificationFlag?: boolean; } const USER_TYPE_NAVBAR_LIST = { ADMIN: ADMIN_NAVBAR_LIST, PROFESSOR: PROF_NAVBAR_LIST, - STUDENT: (modificationFlag: boolean) => STUDENT_NAVBAR_LIST(modificationFlag), + STUDENT: (modificationFlag?: boolean) => STUDENT_NAVBAR_LIST(modificationFlag), }; function NavbarList({ userType, modificationFlag }: Props) { diff --git a/src/components/common/AppShell/_elements/constant/navbarList.tsx b/src/components/common/AppShell/_elements/constant/navbarList.tsx index 904383b1..a6adfa68 100644 --- a/src/components/common/AppShell/_elements/constant/navbarList.tsx +++ b/src/components/common/AppShell/_elements/constant/navbarList.tsx @@ -78,7 +78,7 @@ export const ADMIN_NAVBAR_LIST: Props[] = [ icon: , }, ]; -export const STUDENT_NAVBAR_LIST = (modificationFlag: boolean) => { +export const STUDENT_NAVBAR_LIST = (modificationFlag?: boolean) => { const navbarList: Props[] = [ { label: "메인", href: "/", icon: }, { label: "논문 투고", href: "/student/write", icon: }, diff --git a/src/components/pages/AdminExcelRegister/AdminExcelRegister.tsx b/src/components/pages/AdminExcelRegister/AdminExcelRegister.tsx index 9e734f1f..a9893364 100644 --- a/src/components/pages/AdminExcelRegister/AdminExcelRegister.tsx +++ b/src/components/pages/AdminExcelRegister/AdminExcelRegister.tsx @@ -75,7 +75,13 @@ function AdminExcelRegister({ isProf = false }: Props) { + <>* 이미 등록된 중복 아이디의 경우 데이터가 수정됩니다. +
+ <>* 한 번에 많은 사용자를 업로드할 시 로딩 시간이 길어질 수 있습니다. + + } /> diff --git a/src/components/pages/AdminProfForm/AdminProfForm.tsx b/src/components/pages/AdminProfForm/AdminProfForm.tsx index 1028d68a..5614162f 100644 --- a/src/components/pages/AdminProfForm/AdminProfForm.tsx +++ b/src/components/pages/AdminProfForm/AdminProfForm.tsx @@ -1,7 +1,7 @@ "use client"; import { useState, useEffect } from "react"; -import { Button, Stack, Space, TextInput, PasswordInput, Group } from "@mantine/core"; +import { Button, Stack, Space, TextInput, PasswordInput, Group, Modal, Text } from "@mantine/core"; import { isNotEmpty, useForm } from "@mantine/form"; import { API_ROUTES } from "@/api/apiRoute"; import { useRouter } from "next/navigation"; @@ -12,6 +12,8 @@ import { useAuth } from "@/components/common/AuthProvider/AuthProvider"; import { showNotificationError, showNotificationSuccess } from "@/components/common/Notifications"; import { DepartmentSelect } from "@/components/common/selects/DepartmentSelect"; import { User } from "@/api/_types/user"; +import { useDisclosure } from "@mantine/hooks"; +import { IconAlertTriangle } from "@tabler/icons-react"; interface Props { professorId?: number | string; @@ -32,26 +34,33 @@ function AdminProfForm({ professorId }: Props) { const [isPwEditing, setIsPwEditing] = useState(false); const [defaultDepartmentId, setDefaultDepartmentId] = useState(null); const [previousProf, setPreviousProf] = useState(); + const [deleteOpened, { open: deleteOpen, close: deleteClose }] = useDisclosure(false); - const { onSubmit, getInputProps, setValues, isDirty, setFieldValue } = - useForm({ - initialValues: { - loginId: "", - password: "", - name: "", - }, - validate: { - loginId: isNotEmpty("아이디를 입력해주세요."), - name: isNotEmpty("이름을 입력해주세요."), - email: (value) => - value - ? /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value) - ? null - : "이메일 형식이 맞지 않습니다." - : undefined, - phone: undefined, - }, - }); + const { + onSubmit, + getInputProps, + setValues, + isDirty, + setFieldValue, + values: profValues, + } = useForm({ + initialValues: { + loginId: "", + password: "", + name: "", + }, + validate: { + loginId: isNotEmpty("아이디를 입력해주세요."), + name: isNotEmpty("이름을 입력해주세요."), + email: (value) => + value + ? /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value) + ? null + : "이메일 형식이 맞지 않습니다." + : undefined, + phone: undefined, + }, + }); useEffect(() => { const fetchProfessorDetails = async () => { @@ -66,9 +75,9 @@ function AdminProfForm({ professorId }: Props) { name: professorDetails.name, email: professorDetails.email, phone: professorDetails.phone, - deptId: String(professorDetails.department.id), + deptId: String(professorDetails.department?.id), }); - setDefaultDepartmentId(String(professorDetails.department.id)); + setDefaultDepartmentId(String(professorDetails.department?.id)); } } catch (err) { console.error(err); @@ -121,6 +130,8 @@ function AdminProfForm({ professorId }: Props) { ); login(accessToken); router.push("/"); + router.refresh(); + } catch (err) { console.error(err); } @@ -130,12 +141,48 @@ function AdminProfForm({ professorId }: Props) { router.back(); }; + const handleDelete = async () => { + try { + await ClientAxios.delete(API_ROUTES.professor.delete(professorId)); + showNotificationSuccess({ message: "교수 삭제 완료" }); + deleteClose(); + router.push("/admin/professors"); + } catch (error) { + /* empty */ + } + }; + return ( + + + + + {profValues.name} 교수를 삭제하시겠습니까? + + + + + + + {professorId && ( + 교수 삭제 + , , diff --git a/src/components/pages/AdminStudentForm/AdminStudentForm.tsx b/src/components/pages/AdminStudentForm/AdminStudentForm.tsx index 147f5144..5b268e2f 100644 --- a/src/components/pages/AdminStudentForm/AdminStudentForm.tsx +++ b/src/components/pages/AdminStudentForm/AdminStudentForm.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { Stack, Button } from "@mantine/core"; +import { Stack, Button, Modal, Group, Text } from "@mantine/core"; import { ClientAxios } from "@/api/ClientAxios"; import { useRouter } from "next/navigation"; import { API_ROUTES } from "@/api/apiRoute"; @@ -11,6 +11,7 @@ import { CommonApiResponse } from "@/api/_types/common"; import { showNotificationError, showNotificationSuccess } from "@/components/common/Notifications"; import { useAuth } from "@/components/common/AuthProvider/AuthProvider"; import { useDisclosure } from "@mantine/hooks"; +import { IconAlertTriangle } from "@tabler/icons-react"; import BasicInfoSection from "./_sections/BasicInfoSection"; import AssignReviewerSection from "./_sections/AssignReviewerSection"; import ThesisTitleSection from "./_sections/ThesisTitleSection"; @@ -29,6 +30,7 @@ function AdminStudentForm({ studentId }: Props) { const [isPwEditing, setIsPwEditing] = useState(false); const [isAdmin, setIsAdmin] = useState(false); const [opened, { open, close }] = useDisclosure(); + const [deleteOpened, { open: deleteOpen, close: deleteClose }] = useDisclosure(false); const { headReviewer, advisors, @@ -84,6 +86,7 @@ function AdminStudentForm({ studentId }: Props) { setIsAdmin(false); login(accessToken); router.push("/"); + router.refresh(); } catch (err) { console.error(err); } @@ -93,6 +96,17 @@ function AdminStudentForm({ studentId }: Props) { router.back(); }; + const handleDelete = async () => { + try { + await ClientAxios.delete(API_ROUTES.student.delete(studentId)); + showNotificationSuccess({ message: "학생 삭제 완료" }); + deleteClose(); + router.push("/admin/students"); + } catch (error) { + /* empty */ + } + }; + const handleSubmit = async () => { try { if (isPwEditing && form.values.basicInfo.password === undefined) { @@ -234,6 +248,28 @@ function AdminStudentForm({ studentId }: Props) { return ( <> + + + + + {form.values.basicInfo.name} 학생을 삭제하시겠습니까? + + + + + + + {studentId && ( )} @@ -243,6 +279,9 @@ function AdminStudentForm({ studentId }: Props) { + 학생 삭제 + , , diff --git a/src/components/pages/AdminStudentForm/_sections/AssignReviewerSection.tsx b/src/components/pages/AdminStudentForm/_sections/AssignReviewerSection.tsx index a8d1fbff..b43ef0a1 100644 --- a/src/components/pages/AdminStudentForm/_sections/AssignReviewerSection.tsx +++ b/src/components/pages/AdminStudentForm/_sections/AssignReviewerSection.tsx @@ -79,7 +79,9 @@ function AssignReviewerSection({ const assignedReviewerLabel = (role: ReviewerRole, professorId: number) => { if (!isLoadingProf) { const professor = professors?.find((prof) => prof.id === professorId) || ({} as Professor); - const name = professor.department ? `${professor.name} (${professor.department.name})` : ""; + const name = professor.department + ? `${professor.name} (${professor.department.name})` + : `${professor.name}`; switch (role) { case "HEAD": return `[심사위원장] ${name}`; @@ -129,7 +131,9 @@ function AssignReviewerSection({ if (!isLoadingProf) { data = professors ? professors.map((professor) => ({ - label: `${professor.name}(${professor.department.name})`, + label: professor.department + ? `${professor.name} (${professor.department.name})` + : `${professor.name}`, value: String(professor.id), })) : []; diff --git a/src/components/pages/AdminStudentForm/_sections/ThesisInfoSection.tsx b/src/components/pages/AdminStudentForm/_sections/ThesisInfoSection.tsx index 3820d13f..31353ea0 100644 --- a/src/components/pages/AdminStudentForm/_sections/ThesisInfoSection.tsx +++ b/src/components/pages/AdminStudentForm/_sections/ThesisInfoSection.tsx @@ -33,9 +33,9 @@ function ThesisInfoSection({ studentId, token }: Props) { const thesisDetail = response.data as ThesisInfo; setThesisTitle(thesisDetail.title); if (thesisDetail) { - setThesisFile({ name: thesisDetail.thesisFile.name, file: thesisDetail.thesisFile }); + setThesisFile({ name: thesisDetail.thesisFile?.name, file: thesisDetail?.thesisFile }); setPresentationFile({ - name: thesisDetail.presentationFile.name, + name: thesisDetail.presentationFile?.name, file: thesisDetail.presentationFile, }); } diff --git a/src/components/pages/LoginForm/LoginForm.tsx b/src/components/pages/LoginForm/LoginForm.tsx index 2852a284..ec368cc1 100644 --- a/src/components/pages/LoginForm/LoginForm.tsx +++ b/src/components/pages/LoginForm/LoginForm.tsx @@ -87,7 +87,7 @@ function LoginForm() {
- + - + @@ -112,9 +112,9 @@ function LoginForm() { {...getInputProps("password")} /> - - {type === "student" && "아이디는 학번, 비밀번호는 생년월일입니다."} - {type === "professor" && "아이디는 킹고아이디, 비밀번호는 생년월일입니다."} + + {type === "student" && "아이디는 학번, 초기 비밀번호는 생년월일입니다."} + {type === "professor" && "아이디는 킹고아이디, 초기 비밀번호는 생년월일입니다."} {type === "admin" && "관리자 계정은 행정실에 문의해주세요."} diff --git a/src/components/pages/PhaseReady/PhaseReady.tsx b/src/components/pages/PhaseReady/PhaseReady.tsx index d000f9a0..caffd7e1 100644 --- a/src/components/pages/PhaseReady/PhaseReady.tsx +++ b/src/components/pages/PhaseReady/PhaseReady.tsx @@ -15,9 +15,10 @@ export default function PhaseReady({ title, start, end, after = false }: PhaseRe 지금은 {after ? `${title} 일정이 진행 중입니다.` : `${title} 기간이 아닙니다.`} + {/* {after ? `${end}에 ${title}가 완료됩니다.` : `해당 기간은 ${start} ~ ${end} 입니다.`} - + */} 오류가 있을 시 행정실로 문의해주세요. ); diff --git a/src/components/pages/PhaseReady/PhaseReadyAlertRow.tsx b/src/components/pages/PhaseReady/PhaseReadyAlertRow.tsx index 8907a840..002a5576 100644 --- a/src/components/pages/PhaseReady/PhaseReadyAlertRow.tsx +++ b/src/components/pages/PhaseReady/PhaseReadyAlertRow.tsx @@ -15,9 +15,9 @@ export default function PhaseReadyAlertRow({ title, start, end }: PhaseReadyAler {title} 기간이 아닙니다. - + {/* (기간: {start} ~ {end}) - + */} diff --git a/src/components/pages/account/UserInfoEditSection.tsx b/src/components/pages/account/UserInfoEditSection.tsx index dba76924..7d12bdec 100644 --- a/src/components/pages/account/UserInfoEditSection.tsx +++ b/src/components/pages/account/UserInfoEditSection.tsx @@ -110,7 +110,7 @@ function UserInfoEditSection() { - {user?.department.name} + {user?.department?.name} )} diff --git a/src/components/pages/department/DepartmentManageSection.tsx b/src/components/pages/department/DepartmentManageSection.tsx index d37f97e9..c87cc2bf 100644 --- a/src/components/pages/department/DepartmentManageSection.tsx +++ b/src/components/pages/department/DepartmentManageSection.tsx @@ -3,21 +3,62 @@ import useDepartments from "@/api/SWR/useDepartments"; import { DepartmentTable, DepartmentTableRow } from "@/components/pages/department/DepartmentTable"; import { Section } from "@/components/common/Section"; -import { Stack } from "@mantine/core"; -import { RowGroup, TitleRow } from "@/components/common/rows"; +import { Button, Group, Select, Stack } from "@mantine/core"; +import { BasicRow, RowGroup, TitleRow } from "@/components/common/rows"; +import { isNotEmpty, useForm } from "@mantine/form"; +import { ClientAxios } from "@/api/ClientAxios"; +import { API_ROUTES } from "@/api/apiRoute"; +import { showNotificationSuccess } from "@/components/common/Notifications"; import DepartmentCreateRow from "./DepartmentCreateRow"; +interface ModificationEditFormInputs { + id: string; +} + function DepartmentManageSection() { - const { data, isLoading, mutate } = useDepartments(); + const { data: DepartmentsData, isLoading: isDeptLoading, mutate } = useDepartments(); + + const { onSubmit: onExcludeSubmit, getInputProps: getExcludeInputProps } = + useForm({ + validate: { + id: isNotEmpty("학과를 선택해주세요."), + }, + }); + const { onSubmit: onCancelSubmit, getInputProps: getCancelInputProps } = + useForm({ + validate: { + id: isNotEmpty("학과를 선택해주세요."), + }, + }); + + const handleExcludeSubmit = async (value: ModificationEditFormInputs) => { + try { + await ClientAxios.put(API_ROUTES.department.put(value.id, false)); + showNotificationSuccess({ message: `학과를 제외하였습니다.` }); + mutate(); + } catch (error) { + /* empty */ + } + }; + + const handleCancelSubmit = async (value: ModificationEditFormInputs) => { + try { + await ClientAxios.put(API_ROUTES.department.put(value.id, true)); + showNotificationSuccess({ message: `학과 제외를 취소하였습니다.` }); + mutate(); + } catch (error) { + /* empty */ + } + }; return (
- {!isLoading && - data?.departments.map((department, index) => ( + {!isDeptLoading && + DepartmentsData?.departments.map((department, index) => ( + + + + + + + !department.modificationFlag) + .map((department) => ({ + label: department.name, + value: String(department.id), + }))} + {...getCancelInputProps("id")} + /> + + + + + +
); diff --git a/src/components/pages/lists/admin/ProfessorListSection/ProfessorListSection.tsx b/src/components/pages/lists/admin/ProfessorListSection/ProfessorListSection.tsx index 444ec5e4..d0dbd8a5 100644 --- a/src/components/pages/lists/admin/ProfessorListSection/ProfessorListSection.tsx +++ b/src/components/pages/lists/admin/ProfessorListSection/ProfessorListSection.tsx @@ -235,7 +235,7 @@ function ProfessorListSection() { {professor.name} {professor.email} {professor.phone} - {professor.department.name} + {professor.department?.name} ))} diff --git a/src/components/pages/review/Review/FinalReview.tsx b/src/components/pages/review/Review/FinalReview.tsx index 39be6167..c5581050 100644 --- a/src/components/pages/review/Review/FinalReview.tsx +++ b/src/components/pages/review/Review/FinalReview.tsx @@ -4,6 +4,7 @@ import { UseFormReturnType } from "@mantine/form"; import { BasicRow, ButtonRow, + CommentTypeRow, FileUploadRow, RowGroup, TextAreaRow, @@ -54,6 +55,7 @@ export function FinalReview({
+ + 최종 {nameForStatus(thesis!)} + + ) : ( 내용심사 {nameForStatus(thesis!)}, 구두심사 {nameForStatus(presentation!)} diff --git a/src/components/pages/review/ReviewConfirmModal/ReviewConfirmModal.tsx b/src/components/pages/review/ReviewConfirmModal/ReviewConfirmModal.tsx index 34712fcd..27fa4924 100644 --- a/src/components/pages/review/ReviewConfirmModal/ReviewConfirmModal.tsx +++ b/src/components/pages/review/ReviewConfirmModal/ReviewConfirmModal.tsx @@ -18,6 +18,7 @@ export interface ReviewConfirmModalProps extends PropsWithChildren { opened: boolean; onConfirm: () => void; onClose: () => void; + isFinal?: boolean; } export function ReviewConfirmModal({ @@ -26,6 +27,7 @@ export function ReviewConfirmModal({ opened, onConfirm, onClose, + isFinal, }: ReviewConfirmModalProps) { return (
diff --git a/src/components/pages/system/PhaseEditFormRow.tsx b/src/components/pages/system/PhaseEditFormRow.tsx index 4ca4f1bc..a8b24a61 100644 --- a/src/components/pages/system/PhaseEditFormRow.tsx +++ b/src/components/pages/system/PhaseEditFormRow.tsx @@ -18,11 +18,17 @@ interface Props { end: string; } +function GetDateTime(t: string): Date { + const offset = 9 * 60 * 60 * 1000; + const ctime = new Date(t).getTime() - offset; + return new Date(ctime); +} + function PhaseEditFormRow({ id, title, start, end }: Props) { const { onSubmit, getInputProps } = useForm({ initialValues: { - start: start !== null ? new Date(start) : null, - end: end !== null ? new Date(end) : null, + start: start !== null ? GetDateTime(start) : null, + end: end !== null ? GetDateTime(end) : null, }, validate: { start: isNotEmpty("년월일시를 입력해주세요."), @@ -49,12 +55,12 @@ function PhaseEditFormRow({ id, title, start, end }: Props) { {" "} - - - - -
-
);