diff --git a/app/(sub-page)/file-upload/page.tsx b/app/(sub-page)/file-upload/page.tsx deleted file mode 100644 index 12983fe8..00000000 --- a/app/(sub-page)/file-upload/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import ContentContainer from '../../ui/view/atom/content-container'; - -export default function Page() { - return ( - -
요소1
-
요소2
-
- ); -} diff --git a/app/(sub-page)/grade-upload/components/manual.tsx b/app/(sub-page)/grade-upload/components/manual.tsx new file mode 100644 index 00000000..ebcd9199 --- /dev/null +++ b/app/(sub-page)/grade-upload/components/manual.tsx @@ -0,0 +1,32 @@ +export default function Manual() { + return ( +
+
+

+ 한 번의 성적표 입력으로 +
맞춤형 결과를 확인하세요 ! +

+
+
+ 1. + + MyiWeb MSI + + 에 접속 후 로그인(PC환경 권장) +
+
2. 좌측 성적/졸업 메뉴 → 성적표(상담용,B4)클릭
+
3. 우측 상단 조회버튼 클릭 → 프린트 아이콘 클릭
+
4. 인쇄 정보의 대상(PDF로 저장) 설정 → 하단 저장 버튼 클릭
+
5. 저장한 파일 업로드
+
+ • 회원 가입한 학번과 일치하는 학번의 성적표를 입력해야 합니다. +
+
+
+
+ ); +} diff --git a/app/(sub-page)/grade-upload/page.tsx b/app/(sub-page)/grade-upload/page.tsx new file mode 100644 index 00000000..5f536634 --- /dev/null +++ b/app/(sub-page)/grade-upload/page.tsx @@ -0,0 +1,12 @@ +import ContentContainer from '../../ui/view/atom/content-container'; +import Manual from './components/manual'; +import UploadTakenLecture from '../../ui/lecture/upload-taken-lecture/upload-taken-lecture'; + +export default function GradeUploadPage() { + return ( + + + + + ); +} diff --git a/app/__test__/ui/view/upload-pdf.test.tsx b/app/__test__/ui/view/upload-pdf.test.tsx new file mode 100644 index 00000000..265786ee --- /dev/null +++ b/app/__test__/ui/view/upload-pdf.test.tsx @@ -0,0 +1,30 @@ +import UploadPdf from '@/app/ui/view/molecule/upload-pdf/upload-pdf'; +import { render, screen } from '@testing-library/react'; +import { userEvent } from '@testing-library/user-event'; + +describe('성적 업로드', () => { + it('파일이 업로드 될 때, pdf file을 업로드 하면 file명이 노출된다.', async () => { + render(); + + const targetFile = new File(['grade'], 'grade.pdf', { + type: 'text/plain', + }); + + const uploadBox = await screen.findByTestId('upload-box'); + await userEvent.upload(uploadBox, targetFile); + + expect(screen.getByText(targetFile.name)).toBeInTheDocument(); + }); + + it('파일이 업로드 될 때, pdf가 아닌 file을 업로드 하면 변화가 발생하지않는다.', async () => { + render(); + + const targetFile = new File(['grade'], 'grade.png', { + type: 'text/plain', + }); + + const uploadBox = await screen.findByTestId('upload-box'); + await userEvent.upload(uploadBox, targetFile); + expect(screen.queryByText('마우스로 드래그 하거나 아이콘을 눌러 추가해주세요.')).toBeInTheDocument(); + }); +}); diff --git a/app/business/api-path.ts b/app/business/api-path.ts index 59b3c2f3..c05f51fe 100644 --- a/app/business/api-path.ts +++ b/app/business/api-path.ts @@ -2,5 +2,7 @@ const BASE_URL = process.env.API_MOCKING === 'enable' ? 'http://localhost:9090' export const API_PATH = { revenue: `${BASE_URL}/revenue`, + registerUserGrade: `${BASE_URL}/registerUserGrade`, + parsePDFtoText: `${BASE_URL}/parsePDFtoText`, takenLectures: `${BASE_URL}/taken-lectures`, }; diff --git a/app/business/lecture/taken-lecture.command.ts b/app/business/lecture/taken-lecture.command.ts new file mode 100644 index 00000000..9bbd43f4 --- /dev/null +++ b/app/business/lecture/taken-lecture.command.ts @@ -0,0 +1,35 @@ +'use server'; +import { FormState } from '@/app/ui/view/molecule/form/form-root'; +import { API_PATH } from '../api-path'; + +export const registerUserGrade = async (prevState: FormState, formData: FormData) => { + const parsingText = await parsePDFtoText(formData); + + const res = await fetch(API_PATH.registerUserGrade, { + method: 'POST', + body: JSON.stringify({ parsingText }), + }); + + if (!res.ok) { + return { + errors: {}, + message: 'fail upload grade', + }; + } + + return { + errors: {}, + message: '', + }; +}; + +export const parsePDFtoText = async (formData: FormData) => { + const res = await fetch(API_PATH.parsePDFtoText, { method: 'POST', body: formData }); + if (!res.ok) { + return { + errors: {}, + message: 'fail parsing to text', + }; + } + return await res.json(); +}; diff --git a/app/hooks/usePdfFile.ts b/app/hooks/usePdfFile.ts new file mode 100644 index 00000000..b86b1056 --- /dev/null +++ b/app/hooks/usePdfFile.ts @@ -0,0 +1,19 @@ +import { useState } from 'react'; +import { z } from 'zod'; + +export type FileType = File | null; + +export default function usePdfFile() { + const [file, setFile] = useState(null); + + const changeFile = (file: File) => { + if (!validate.parse(file.name)) return; + setFile(file); + }; + + const validate = z.string().refine((value) => value.endsWith('.pdf'), { + message: 'File must be a PDF', + }); + + return { file, changeFile }; +} diff --git a/app/mocks/data.mock.ts b/app/mocks/data.mock.ts index 5000e959..5e6e69a7 100644 --- a/app/mocks/data.mock.ts +++ b/app/mocks/data.mock.ts @@ -12,6 +12,8 @@ export const revenue = [ { month: 'Nov', revenue: 3000 }, { month: 'Dec', revenue: 4800 }, ]; +export const parsePDF = + '출력일자 : 2022/10/05|1/1|ICT융합대학 융합소프트웨어학부 응용소프트웨어전공, 모유경(60201671), 현학적 - 재학, 이수 - 4, 입학 - 신입학(2020/03/02)|토익 - 440, 영어교과목면제 - 면제없음, 최종학적변동 - 불일치복학(2022/07/12)|편입생 인정학점 - 교양 0, 전공 0, 자유선택 0, 성경과인간이해 0|교환학생 인정학점 - 학문기초교양 0, 일반교양 0, 전공 0, 복수전공학문기초교양 0, 복수전공 0, 연계전공 0, 부전공 0, 자유선택 0|공통교양 17, 핵심교양 12, 학문기초교양 12, 일반교양 3, 전공 33, 복수전공 0, 연계전공 0, 부전공 0, 교직 0, 자유선택 0|총 취득학점 - 77, 총점 - 296.5, 평균평점 - 4.06|이수구분|수강년도/학기|한글코드|과목코드|과목명|학점|등급|중복|공통교양 (구 필수교양)|2020년 2학기|교필141|KMA02141|4차산업혁명과미래사회진로선택|2|P|공통교양 (구 필수교양)|2021년 1학기|교필104|KMA02104|글쓰기|3|A+|공통교양 (구 필수교양)|2021년 2학기|교필122|KMA02122|기독교와문화|2|A0|공통교양 (구 필수교양)|2020년 1학기|교필106|KMA02106|영어1|2|A0|공통교양 (구 필수교양)|2021년 1학기|교필107|KMA02107|영어2|2|A0|공통교양 (구 필수교양)|2021년 1학기|교필108|KMA02108|영어회화1|1|B+|공통교양 (구 필수교양)|2021년 2학기|교필109|KMA02109|영어회화2|1|B+|공통교양 (구 필수교양)|2020년 1학기|교필101|KMA02101|채플|0.5|P|공통교양 (구 필수교양)|2020년 2학기|교필101|KMA02101|채플|0.5|P|공통교양 (구 필수교양)|2021년 1학기|교필101|KMA02101|채플|0.5|P|공통교양 (구 필수교양)|2021년 2학기|교필101|KMA02101|채플|0.5|P|공통교양 (구 필수교양)|2020년 2학기|교필102|KMA02102|현대사회와기독교윤리|2|A+|핵심교양 (구 선택교양)|2021년 1학기|교선130|KMA02130|고전으로읽는인문학|3|B+|핵심교양 (구 선택교양)|2020년 1학기|교선127|KMA02127|창업입문|3|A+|핵심교양 (구 선택교양)|2021년 2학기|교선110|KMA02110|철학과인간|3|A+|핵심교양 (구 선택교양)|2020년 2학기|교선142|KMA02142|현대사회와심리학|3|A+|학문기초교양 (구 기초교양)|2020년 2학기|기사133|KMD02133|ICT비즈니스와경영|3|A+|학문기초교양 (구 기초교양)|2020년 1학기|기사134|KMD02134|마케팅과ICT융합기술|3|A+|학문기초교양 (구 기초교양)|2020년 1학기|기사135|KMD02135|저작권과소프트웨어|3|A+|학문기초교양 (구 기초교양)|2020년 2학기|기컴112|KMI02112|컴퓨터논리의이해|3|A+|일반교양 (구 균형교양)|2020년 2학기|기컴125|KMI02125|생활속의스마트IT(KCU)|3|A+|전공1단계|2021년 1학기|응소204|HEC01204|DB설계및구현1|3|B+|전공1단계|2021년 2학기|응소305|HEC01305|DB설계및구현2|3|B+|전공1단계|2020년 2학기|융소103|HEB01103|객체지향적사고와프로그래밍|3|B0|전공1단계|2021년 1학기|응소208|HEC01208|데이터구조와알고리즘1|3|B+|전공1단계|2021년 2학기|응소207|HEC01207|데이터구조와알고리즘2|3|B+|전공1단계|2021년 2학기|응소212|HEC01212|시스템프로그래밍1|3|B+|전공1단계|2021년 1학기|응소211|HEC01211|웹프로그래밍1|3|A+|전공1단계|2021년 2학기|응소209|HEC01209|웹프로그래밍2|3|A0|전공1단계|2020년 1학기|융소101|HEB01101|절차적사고와프로그래밍|3|A+|전공1단계|2021년 2학기|응소210|HEC01210|클라이언트서버프로그래밍|3|A0|전공1단계|2021년 1학기|응소202|HEC01202|패턴중심사고와프로그래밍|3|A0||'; export const takenLectures = JSON.parse(`{ "totalCredit": 115, diff --git a/app/mocks/handlers.mock.ts b/app/mocks/handlers.mock.ts index 12684d6a..b4bd6cca 100644 --- a/app/mocks/handlers.mock.ts +++ b/app/mocks/handlers.mock.ts @@ -1,5 +1,5 @@ import { HttpResponse, http, delay } from 'msw'; -import { revenue, takenLectures } from './data.mock'; +import { revenue, parsePDF, takenLectures } from './data.mock'; import { API_PATH } from '../business/api-path'; export const handlers = [ @@ -8,6 +8,18 @@ export const handlers = [ console.log(revenue); return HttpResponse.json(revenue); }), + + http.post(API_PATH.parsePDFtoText, async () => { + await delay(1000); + console.log(parsePDF); + return HttpResponse.json(parsePDF); + }), + + http.post(API_PATH.registerUserGrade, async () => { + await delay(1000); + throw new HttpResponse(null, { status: 200 }); + }), + http.get(API_PATH.takenLectures, () => { return HttpResponse.json(takenLectures); }), diff --git a/app/ui/lecture/upload-taken-lecture/upload-taken-lecture.tsx b/app/ui/lecture/upload-taken-lecture/upload-taken-lecture.tsx new file mode 100644 index 00000000..69a87870 --- /dev/null +++ b/app/ui/lecture/upload-taken-lecture/upload-taken-lecture.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { registerUserGrade } from '@/app/business/lecture/taken-lecture.command'; +import UploadPdf from '@/app/ui/view/molecule/upload-pdf/upload-pdf'; +import Form from '../../view/molecule/form'; + +function UploadTakenLecture() { + return ( +
+ + + + ); +} + +export default UploadTakenLecture; diff --git a/app/ui/view/molecule/upload-pdf/upload-pdf.stories.tsx b/app/ui/view/molecule/upload-pdf/upload-pdf.stories.tsx new file mode 100644 index 00000000..b5b14490 --- /dev/null +++ b/app/ui/view/molecule/upload-pdf/upload-pdf.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import UploadPdf from './upload-pdf'; + +const meta = { + title: 'ui/view/molecule/UploadFile', + component: UploadPdf, +} satisfies Meta; + +export default meta; + +export const Default: StoryObj = { + render: () => , +}; diff --git a/app/ui/view/molecule/upload-pdf/upload-pdf.tsx b/app/ui/view/molecule/upload-pdf/upload-pdf.tsx new file mode 100644 index 00000000..4b37baca --- /dev/null +++ b/app/ui/view/molecule/upload-pdf/upload-pdf.tsx @@ -0,0 +1,40 @@ +'use client'; +import Image from 'next/image'; +import uploadBox from '@/public/assets/upload-box.svg'; +import checkedBox from '@/public/assets/checked-box.svg'; +import usePdfFile from '@/app/hooks/usePdfFile'; +import { ChangeEvent } from 'react'; + +function UploadPdf() { + const { file, changeFile } = usePdfFile(); + + const handleChangeFileInput = (event: ChangeEvent) => { + const { files } = event.target; + if (files) changeFile(files[0]); + }; + + return ( +
+
+ upload-button + + {file ? file.name : '마우스로 드래그 하거나 아이콘을 눌러 추가해주세요.'} + + +
+
+ ); +} + +export default UploadPdf; diff --git a/public/assets/checked-box.svg b/public/assets/checked-box.svg new file mode 100644 index 00000000..e59675e7 --- /dev/null +++ b/public/assets/checked-box.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/upload-box.svg b/public/assets/upload-box.svg new file mode 100644 index 00000000..9b3047e4 --- /dev/null +++ b/public/assets/upload-box.svg @@ -0,0 +1,5 @@ + + + + +