Skip to content

Commit

Permalink
Merge pull request #41 from Myongji-Graduate/grade-upload/#9
Browse files Browse the repository at this point in the history
grade upload/#9
  • Loading branch information
yougyung authored Mar 18, 2024
2 parents 8fbd295 + c54c917 commit 3fb4a86
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 11 deletions.
10 changes: 0 additions & 10 deletions app/(sub-page)/file-upload/page.tsx

This file was deleted.

32 changes: 32 additions & 0 deletions app/(sub-page)/grade-upload/components/manual.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export default function Manual() {
return (
<div className="flex justify-center">
<div className="flex flex-col gap-6">
<h1 className="text-center text-3xl font-bold p-4 border-b-2 border-gray-100 md:text-4xl">
한 번의 성적표 입력으로
<br /> 맞춤형 결과를 확인하세요 !
</h1>
<div className="text-base flex flex-col gap-2 md:text-lg">
<div>
1.
<a
target="_blank"
className="pl-1 text-primary hover:text-dark-hover"
href="https://msi.mju.ac.kr/servlet/security/MySecurityStart"
>
MyiWeb MSI
</a>
에 접속 후 로그인(PC환경 권장)
</div>
<div>2. 좌측 성적/졸업 메뉴 → 성적표(상담용,B4)클릭</div>
<div>3. 우측 상단 조회버튼 클릭 → 프린트 아이콘 클릭</div>
<div>4. 인쇄 정보의 대상(PDF로 저장) 설정 → 하단 저장 버튼 클릭 </div>
<div>5. 저장한 파일 업로드 </div>
<div className="text-xs md:text-sm text-primary">
• 회원 가입한 학번과 일치하는 학번의 성적표를 입력해야 합니다.
</div>
</div>
</div>
</div>
);
}
12 changes: 12 additions & 0 deletions app/(sub-page)/grade-upload/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ContentContainer className="flex flex-col justify-center gap-8 min-h-[70vh]">
<Manual />
<UploadTakenLecture />
</ContentContainer>
);
}
30 changes: 30 additions & 0 deletions app/__test__/ui/view/upload-pdf.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<UploadPdf />);

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(<UploadPdf />);

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();
});
});
2 changes: 2 additions & 0 deletions app/business/api-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
};
35 changes: 35 additions & 0 deletions app/business/lecture/taken-lecture.command.ts
Original file line number Diff line number Diff line change
@@ -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();
};
19 changes: 19 additions & 0 deletions app/hooks/usePdfFile.ts
Original file line number Diff line number Diff line change
@@ -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<FileType>(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 };
}
2 changes: 2 additions & 0 deletions app/mocks/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 13 additions & 1 deletion app/mocks/handlers.mock.ts
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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);
}),
Expand Down
16 changes: 16 additions & 0 deletions app/ui/lecture/upload-taken-lecture/upload-taken-lecture.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Form action={registerUserGrade} id="성적업로드">
<UploadPdf />
<Form.SubmitButton label="결과 보러가기" position="center" size="md" />
</Form>
);
}

export default UploadTakenLecture;
13 changes: 13 additions & 0 deletions app/ui/view/molecule/upload-pdf/upload-pdf.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof UploadPdf>;

export default meta;

export const Default: StoryObj<typeof meta> = {
render: () => <UploadPdf />,
};
40 changes: 40 additions & 0 deletions app/ui/view/molecule/upload-pdf/upload-pdf.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement>) => {
const { files } = event.target;
if (files) changeFile(files[0]);
};

return (
<div className="flex flex-col items-center gap-4">
<div
role="button"
className="relative p-2 m-auto w-96 flex flex-col justify-center items-center gap-2 border-dashed border-2 rounded-sm rounded-bl-xl border-light-blue-6 bg-light-blue-1 text-light-blue-6 max-lg:w-80"
>
<Image src={file ? checkedBox : uploadBox} width={40} height={28} className="mx-auto" alt="upload-button" />
<span className="text-center break-keep whitespace-pre-line max-w-48 truncate">
{file ? file.name : '마우스로 드래그 하거나 아이콘을 눌러 추가해주세요.'}
</span>
<input
onChange={handleChangeFileInput}
type="file"
className="absolute bg-black opacity-0 w-96 max-lg:w-80 h-full"
name="file"
accept=".pdf"
data-testid="upload-box"
required
/>
</div>
</div>
);
}

export default UploadPdf;
5 changes: 5 additions & 0 deletions public/assets/checked-box.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/assets/upload-box.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 3fb4a86

Please sign in to comment.