diff --git a/services/admin/src/apis/files/index.ts b/services/admin/src/apis/files/index.ts index ed2f2fb5..7c47756c 100644 --- a/services/admin/src/apis/files/index.ts +++ b/services/admin/src/apis/files/index.ts @@ -21,3 +21,23 @@ export const studentAccountIssuance = async (file: FileList[0]) => { ); return data; }; + +export const studentEditRoom = async (file: FileList[0]) => { + const reqeustFile = new FormData(); + reqeustFile.append('file', file); + const { data } = await instance.post( + `${router}/students/file/room`, + reqeustFile, + ); + return data; +}; + +export const studentEditGrade = async (file: FileList[0]) => { + const reqeustFile = new FormData(); + reqeustFile.append('file', file); + const { data } = await instance.post( + `${router}/students/file/gcn`, + reqeustFile, + ); + return data; +}; \ No newline at end of file diff --git a/services/admin/src/apis/managers/index.ts b/services/admin/src/apis/managers/index.ts index 49d7ba99..52fc0a89 100644 --- a/services/admin/src/apis/managers/index.ts +++ b/services/admin/src/apis/managers/index.ts @@ -8,6 +8,9 @@ import { import { ResetPasswordRequest } from './request'; import { PointType } from '../points'; import { TagType } from '../tags/response'; +import { useMutation } from '@tanstack/react-query'; +import fileSaver from 'file-saver'; +import { getFileNameFromContentDisposition } from '@/utils/decoder'; const router = '/managers'; @@ -69,3 +72,41 @@ export const getMyProfile = async () => { ); return data; }; + +export const getStudentInfoExcel = () => + useMutation( + () => + instance.get(`${router}/students/file`, { + responseType: 'blob', + }), + { + onSuccess: (res) => { + const blob = new Blob([res.data], { + type: res.headers['content-type'], + }); + const fileName = res.headers['content-disposition']; + + fileSaver.saveAs(blob, getFileNameFromContentDisposition(fileName)); + }, + }, + ); + +export const uploadStudentInfoFile = async (file: FileList[0]) => { + const reqeustFile = new FormData(); + reqeustFile.append('file', file); + const { data } = await instance.post( + `${router}/students/file/gcn`, + reqeustFile, + ); + return data; +}; + +export const uploadRoomInfoFile = async (file: FileList[0]) => { + const reqeustFile = new FormData(); + reqeustFile.append('file', file); + const { data } = await instance.post( + `${router}/students/file/room`, + reqeustFile, + ); + return data; +}; \ No newline at end of file diff --git a/services/admin/src/components/modals/LogOut.tsx b/services/admin/src/components/modals/LogOut.tsx index 6d2fee61..8a0f6c92 100644 --- a/services/admin/src/components/modals/LogOut.tsx +++ b/services/admin/src/components/modals/LogOut.tsx @@ -1,14 +1,12 @@ +import { useModal } from '@/hooks/useModal'; import { pagePath } from '@/utils/pagePath'; import { Button, Modal } from '@team-aliens/design-system'; import { useCookies } from 'react-cookie'; import { useNavigate } from 'react-router-dom'; -interface PropsType { - closeModal: () => void; -} - -export function LogOutModal({ closeModal }: PropsType) { +export function LogOutModal() { const navigate = useNavigate(); + const { closeModal } = useModal(); const [cookies, setCookie, removeCookie] = useCookies([ 'refresh_token', diff --git a/services/admin/src/components/modals/SchoolCheckingCode.tsx b/services/admin/src/components/modals/SchoolCheckingCode.tsx index db2e8104..e463070f 100644 --- a/services/admin/src/components/modals/SchoolCheckingCode.tsx +++ b/services/admin/src/components/modals/SchoolCheckingCode.tsx @@ -1,11 +1,13 @@ +import { useModal } from '@/hooks/useModal'; import { Button, Modal } from '@team-aliens/design-system'; interface PropsType { - closeModal: () => void; onClick: () => void; } -export function SchoolCheckingCodeModal({ closeModal, onClick }: PropsType) { +export function SchoolCheckingCodeModal({ onClick }: PropsType) { + const { closeModal } = useModal(); + const confirm = () => { onClick(); closeModal(); diff --git a/services/admin/src/components/modals/StudentEditGrade.tsx b/services/admin/src/components/modals/StudentEditGrade.tsx new file mode 100644 index 00000000..5fa7842d --- /dev/null +++ b/services/admin/src/components/modals/StudentEditGrade.tsx @@ -0,0 +1,127 @@ +import { Button, Modal, Text } from '@team-aliens/design-system'; +import styled from 'styled-components'; +import { useState } from 'react'; +import { useUploadStudentInfoFile } from '@/hooks/useMangersApis'; +import { getStudentInfoExcel } from '@/apis/managers/index'; +import { useModal } from '@/hooks/useModal'; + +export const StudentEditGrade = () => { + const [uploadedFile, setUplodaedFile] = useState(null); + const studentAccount = useUploadStudentInfoFile(uploadedFile); + const { mutate: getStudentInfo } = getStudentInfoExcel(); + const { closeModal } = useModal(); + + const onFileUpload = (e: React.ChangeEvent) => { + setUplodaedFile(e.target.files[0]); + }; + + return ( + + 취소 + , + , + ]} + > + + <_Text margin={[15, 0, 14, 0]}> + 학생 정보 엑셀을 다운로드 받은 후
+ 학번을 수정하여 업로드 해주세요. + + {uploadedFile ? ( + <> + <_UploadedFile htmlFor="input-file"> + + + +
+ + {uploadedFile.name} + + + {uploadedFile.size / 1000}KB + +
+ + <_Upload + id="input-file" + type="file" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + onChange={onFileUpload} + /> + + ) : ( + <> + <_Load htmlFor="input-file"> + + + + 여기에 파일을 끌어다 놓아주세요. + + <_Upload + id="input-file" + type="file" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + onChange={onFileUpload} + /> + + )} +
+ ); +}; + +const _UploadedFile = styled.label` + background: #eeeeee; + border-radius: 4px; + height: 58px; + display: flex; + align-items: center; + gap: 14px; + padding-left: 13px; + margin-bottom: 158px; +`; + +const _Load = styled.label` + padding: 6px 25px; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + border-radius: 4px; + color: white; + cursor: pointer; + margin-bottom: 62px; +`; + +const _Upload = styled.input` + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + display: none; +`; + +const _Text = styled(Text)` + width: 324px; +`; diff --git a/services/admin/src/components/modals/StudentEditRoom.tsx b/services/admin/src/components/modals/StudentEditRoom.tsx new file mode 100644 index 00000000..e1830b25 --- /dev/null +++ b/services/admin/src/components/modals/StudentEditRoom.tsx @@ -0,0 +1,128 @@ +import { Button, Modal, Text } from '@team-aliens/design-system'; +import styled from 'styled-components'; +import { useState } from 'react'; +import { useUploadRoomInfoFile } from '@/hooks/useMangersApis'; +import { getStudentInfoExcel } from '@/apis/managers/index'; +import { useModal } from '@/hooks/useModal'; + +export const StudentEditRoom = () => { + const [uploadedFile, setUplodaedFile] = useState(null); + const studentAccount = useUploadRoomInfoFile(uploadedFile); + const { mutate: getStudentInfo } = getStudentInfoExcel(); + + const onFileUpload = (e: React.ChangeEvent) => { + setUplodaedFile(e.target.files[0]); + }; + + const { closeModal } = useModal(); + + return ( + + 취소 + , + , + ]} + > + + <_Text margin={[15, 0, 14, 0]}> + 학생 정보 엑셀을 다운로드 받은 후
+ 호실, 자리 위치를 수정하여 업로드 해주세요. + + {uploadedFile ? ( + <> + <_UploadedFile htmlFor="input-file"> + + + +
+ + {uploadedFile.name} + + + {uploadedFile.size / 1000}KB + +
+ + <_Upload + id="input-file" + type="file" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + onChange={onFileUpload} + /> + + ) : ( + <> + <_Load htmlFor="input-file"> + + + + 여기에 파일을 끌어다 놓아주세요. + + <_Upload + id="input-file" + type="file" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + onChange={onFileUpload} + /> + + )} +
+ ); +}; + +const _UploadedFile = styled.label` + background: #eeeeee; + border-radius: 4px; + height: 58px; + display: flex; + align-items: center; + gap: 14px; + padding-left: 13px; + margin-bottom: 158px; +`; + +const _Load = styled.label` + padding: 6px 25px; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + border-radius: 4px; + color: white; + cursor: pointer; + margin-bottom: 62px; +`; + +const _Upload = styled.input` + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + display: none; +`; + +const _Text = styled(Text)` + width: 324px; +`; diff --git a/services/admin/src/components/modals/StudentRegistrationExcel.tsx b/services/admin/src/components/modals/StudentRegistrationExcel.tsx index 8c18612a..6ec2fc2e 100644 --- a/services/admin/src/components/modals/StudentRegistrationExcel.tsx +++ b/services/admin/src/components/modals/StudentRegistrationExcel.tsx @@ -3,14 +3,12 @@ import styled from 'styled-components'; import { useState } from 'react'; import { useStudentAccountIssuance } from '@/hooks/useStudentRegistrationExcel'; import { download } from '@/utils/excel'; +import { useModal } from '@/hooks/useModal'; -interface PropsType { - closeModal: () => void; -} - -export const StudentRegistrationExcel = ({ closeModal }: PropsType) => { +export const StudentRegistrationExcel = () => { const [uploadedFile, setUplodaedFile] = useState(null); - const studentAccount = useStudentAccountIssuance(uploadedFile, closeModal); + const studentAccount = useStudentAccountIssuance(uploadedFile); + const { closeModal } = useModal(); const onFileUpload = (e: React.ChangeEvent) => { setUplodaedFile(e.target.files[0]); diff --git a/services/admin/src/context/modal.tsx b/services/admin/src/context/modal.tsx index c458e532..5e7a632a 100644 --- a/services/admin/src/context/modal.tsx +++ b/services/admin/src/context/modal.tsx @@ -29,6 +29,8 @@ export type SelectedModalType = | 'GIVE_TAG_OPTIONS' | 'VIEW_TAG_OPTIONS' | 'DELETE_TAG' + | 'STUDENT_EDIT_ROOM_EXCEL' + | 'STUDENT_EDIT_GRADE_EXCEL' | ''; interface ModalState { diff --git a/services/admin/src/hooks/useMangersApis.tsx b/services/admin/src/hooks/useMangersApis.tsx index 95da38e2..0178191a 100644 --- a/services/admin/src/hooks/useMangersApis.tsx +++ b/services/admin/src/hooks/useMangersApis.tsx @@ -15,6 +15,11 @@ import { pagePath } from '@/utils/pagePath'; import { TagType } from '@/apis/tags/response'; import { useSelectedStudentIdStore } from '@/store/useSelectedStudentIdStore'; import { useQueryClient } from '@tanstack/react-query'; +import { + uploadRoomInfoFile, + uploadStudentInfoFile, +} from '@/apis/managers/index'; +import { AxiosError } from 'axios'; interface PropsType { selectedId: string; @@ -91,3 +96,49 @@ export const useDeleteStudent = (student_id: string) => { }, }); }; + +export const useUploadStudentInfoFile = (file: FileList[0]) => { + const { toastDispatch } = useToast(); + const { closeModal } = useModal(); + + return useMutation(() => uploadStudentInfoFile(file), { + onSuccess: () => { + toastDispatch({ + actionType: 'APPEND_TOAST', + toastType: 'SUCCESS', + message: '엑셀이 업로드 되었습니다.', + }); + closeModal(); + }, + onError: (e: AxiosError<{ message: string }>) => { + toastDispatch({ + actionType: 'APPEND_TOAST', + toastType: 'ERROR', + message: e.response.data.message, + }); + }, + }); +}; + +export const useUploadRoomInfoFile = (file: FileList[0]) => { + const { toastDispatch } = useToast(); + const { closeModal } = useModal(); + + return useMutation(() => uploadRoomInfoFile(file), { + onSuccess: () => { + toastDispatch({ + actionType: 'APPEND_TOAST', + toastType: 'SUCCESS', + message: '엑셀이 업로드 되었습니다.', + }); + closeModal(); + }, + onError: (e: AxiosError<{ message: string }>) => { + toastDispatch({ + actionType: 'APPEND_TOAST', + toastType: 'ERROR', + message: e.response.data.message, + }); + }, + }); +}; diff --git a/services/admin/src/hooks/useStudentRegistrationExcel.ts b/services/admin/src/hooks/useStudentRegistrationExcel.ts index d0e62ad9..6479f909 100644 --- a/services/admin/src/hooks/useStudentRegistrationExcel.ts +++ b/services/admin/src/hooks/useStudentRegistrationExcel.ts @@ -2,12 +2,11 @@ import { studentAccountIssuance } from '@/apis/files'; import { AxiosError } from 'axios'; import { useMutation } from '@tanstack/react-query'; import { useToast } from './useToast'; +import { useModal } from './useModal'; -export const useStudentAccountIssuance = ( - file: FileList[0], - closeModal: () => void, -) => { +export const useStudentAccountIssuance = (file: FileList[0]) => { const { toastDispatch } = useToast(); + const { closeModal } = useModal(); return useMutation(() => studentAccountIssuance(file), { onSuccess: () => { diff --git a/services/admin/src/pages/myPage/index.tsx b/services/admin/src/pages/myPage/index.tsx index 695d34bf..2f039205 100644 --- a/services/admin/src/pages/myPage/index.tsx +++ b/services/admin/src/pages/myPage/index.tsx @@ -15,12 +15,17 @@ import { useMyProfileInfo } from '@/hooks/useMangersApis'; import { StudentRegistrationExcel } from '@/components/modals/StudentRegistrationExcel'; import { pagePath } from '@/utils/pagePath'; import { SchoolCheckingCodeModal } from '@/components/modals/SchoolCheckingCode'; +import { StudentEditRoom } from '@/components/modals/StudentEditRoom'; +import { StudentEditGrade } from '@/components/modals/StudentEditGrade'; export function MyPage() { - const { modalState, selectModal, closeModal } = useModal(); + const { modalState, selectModal } = useModal(); const openNewQuestionModal = () => selectModal('NEW_QNA'); const openLogoutModal = () => selectModal('LOGOUT'); const openStudentExelModal = () => selectModal('STUDENT_EXEL'); + const openStudentEditRoomExcel = () => selectModal('STUDENT_EDIT_ROOM_EXCEL'); + const openStudentEditGradeExcel = () => + selectModal('STUDENT_EDIT_GRADE_EXCEL'); const { onHandleChange: onChange, state: qnaState } = useForm({ @@ -79,12 +84,26 @@ export function MyPage() { answer={myProfileData?.answer} /> - <_StudentIssuance onClick={openStudentExelModal}> - - 학생 등록 - - - + <_StudentExcelWrapper> + <_StudentIssuance onClick={openStudentExelModal}> + + 학생 등록 + + + + <_StudentEditWrapper> + <_StudentEdit onClick={openStudentEditRoomExcel}> + + 호실 정보 변경 + + + <_StudentGcnEdit onClick={openStudentEditGradeExcel}> + + 학번 정보 변경 + + + + {modalState.selectedModal === 'NEW_QNA' && ( @@ -96,16 +115,17 @@ export function MyPage() { /> )} {modalState.selectedModal === 'SCHOOL_CHECKING_CODE' && ( - - )} - {modalState.selectedModal === 'LOGOUT' && ( - + )} + {modalState.selectedModal === 'LOGOUT' && } {modalState.selectedModal === 'STUDENT_EXEL' && ( - + + )} + {modalState.selectedModal === 'STUDENT_EDIT_ROOM_EXCEL' && ( + + )} + {modalState.selectedModal === 'STUDENT_EDIT_GRADE_EXCEL' && ( + )} ); @@ -137,6 +157,11 @@ const _PasswordChange = styled(Link)` } `; +const _StudentExcelWrapper = styled.div` + display: flex; + justify-content: space-between; +`; + const _StudentIssuance = styled.div` width: 500px; height: 70px; @@ -150,6 +175,29 @@ const _StudentIssuance = styled.div` border-radius: 4px; `; +const _StudentEditWrapper = styled.div` + display: flex; + justify-content: space-between; + width: 500px; +`; + +const _StudentEdit = styled.div` + width: 233px; + height: 70px; + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + padding: 0 24px; + margin-top: 25px; + box-shadow: 0 1px 20px rgba(204, 204, 204, 0.24); + border-radius: 4px; +`; + +const _StudentGcnEdit = styled(_StudentEdit)` + width: 250px; +`; + const _Logout = styled(Text)` width: 250px; box-shadow: 0 1px 20px rgba(204, 204, 204, 0.24);