본문 바로가기

Backend/Nestjs

[Nestjs] Nestjs + GraphQL 적용한 TodoList 만들기 (4)

Front Part 구성


1. 기본 Next.js 구성

구성에 앞서서 프로젝트를 backend, frontend로 폴더를 나눠보도록하겠습니다.

folder 구조

이제 해당 frontend 폴더안에 next.js의 기본 구성을 하겠습니다.

cd frontend
npx create-next-app@latest --typescript

명령어 실행 후
몇 초 후 frontend안에 todolist 프로젝트 생성

 

2. 실행

 cd todolist 
 npm run dev
 
 localhost:3000

실행 후 localhost:3000 접속

 

3. ESLint , Prettier 설정하기

ESLint는 JS문법에서 에러를 표시해주는 도구입니다. 가이드라인이라고 보면 쉽습니다. 코팅스타일과 에러의 기준을 지정할 수 있습니다. 협업시에는 정말 필수가 되기 때문에 설정합니다.

 

우선 충돌없이 동시에 사용하기 위하여 아래의 패키지를 설치합니다.

npm i -D eslint-config-prettier eslint-plugin-prettier

eslint-config-prettier는 Prettier의 설정 중 ESLint의 설정과 충돌이 나는 설정을 비활성화 해주는 라이브러리입니다.

eslint-plugin-prettier는 Prettier의 규칙을 ESLint에 적용시킬 수 있게 해줍니다.

 

기본적으로 next.js 프로젝트를 생성하면 .eslintrc.json 파일이 생성됩니다. 이 파일 안에 아래의 코드를 붙여 넣습니다. (최종 코드)

{
  "extends": ["next/core-web-vitals", "eslint:recommended", "plugin:prettier/recommended"]
}

 

next/core-web-vitals

next.js 프로젝트의 핵심적인 성능 향상을 위한 규칙들을 활성화합니다.

 

eslint:recommended

https://eslint.org/docs/rules/ 에 있는 체크 표시된 모든 규칙들을 활성화합니다.

 

 

plugin:prettier/recommended

권장 구성 참고 - Ref. https://github.com/prettier/eslint-plugin-prettier#recommended-configuration

 

GitHub - prettier/eslint-plugin-prettier: ESLint plugin for Prettier formatting

ESLint plugin for Prettier formatting. Contribute to prettier/eslint-plugin-prettier development by creating an account on GitHub.

github.com

 

다음으로 prettier 설정입니다. .prettierrc.json 을 생성합니다.

 

.prettierrc.json

{
	"semi": true,
	"trailingComma": "es5",
	"singleQuote": true,
	"tabWidth": 4,
	"printWidth": 120,
	"useTabs": true,
	"bracketSameLine": true,
	"jsxSingleQuote": false,
	"quoteProps": "as-needed",
	"arrowParens": "always",
	"bracketSpacing": true,
	"htmlWhitespaceSensitivity": "css",
	"endOfLine": "lf"
}

아래는 설정할 수 있는 전체 옵션입니다.

{
  "arrowParens": "avoid", // 화살표 함수 괄호 사용 방식
  "bracketSpacing": false, // 객체 리터럴에서 괄호에 공백 삽입 여부 
  "endOfLine": "auto", // EoF 방식, OS별로 처리 방식이 다름 
  "htmlWhitespaceSensitivity": "css", // HTML 공백 감도 설정
  "jsxBracketSameLine": false, // JSX의 마지막 `>`를 다음 줄로 내릴지 여부 
  "jsxSingleQuote": false, // JSX에 singe 쿼테이션 사용 여부
  "printWidth": 80, //  줄 바꿈 할 폭 길이
  "proseWrap": "preserve", // markdown 텍스트의 줄바꿈 방식 (v1.8.2)
  "quoteProps": "as-needed" // 객체 속성에 쿼테이션 적용 방식
  "semi": true, // 세미콜론 사용 여부
  "singleQuote": true, // single 쿼테이션 사용 여부
  "tabWidth": 2, // 탭 너비 
  "trailingComma": "all", // 여러 줄을 사용할 때, 후행 콤마 사용 방식
  "useTabs": false, // 탭 사용 여부
  "vueIndentScriptAndStyle": true, // Vue 파일의 script와 style 태그의 들여쓰기 여부 (v1.19.0)
  "parser": '', // 사용할 parser를 지정, 자동으로 지정됨
  "filepath": '', // parser를 유추할 수 있는 파일을 지정
  "rangeStart": 0, // 포맷팅을 부분 적용할 파일의 시작 라인 지정
  "rangeEnd": Infinity, // 포맷팅 부분 적용할 파일의 끝 라인 지정,
  "requirePragma": false, // 파일 상단에 미리 정의된 주석을 작성하고 Pragma로 포맷팅 사용 여부 지정 (v1.8.0)
  "insertPragma": false, // 미리 정의된 @format marker의 사용 여부 (v1.8.0)
  "overrides": [ 
    {
      "files": "*.json",
      "options": {
        "printWidth": 200
      }
    }
  ], // 특정 파일별로 옵션을 다르게 지정함, ESLint 방식 사용
}

 

 

4. husky & lint-staged 설정

이제 마지막으로 git hook을 통한 lint 자동화 작업을 진행하겠습니다. 다만 현재 project는 backend, frontend로 폴더를 나눴기 때문에 repo에 push할 때 backend, frontend를 모두 eslint, prettier 작업을 진행해야하기 때문에 이를 설정해보겠습니다.

 

가장 root에 아래의 패키지를 설치합니다.

npm i -D husky lint-staged

 

다음으로 git hook를 활성화합니다.

npx husky install

 

작업이 끝났다면 package.json 파일의 script 영역에 precommit, prepare를 추가합니다.

"scripts": {
    "precommit": "lint-staged",
    "prepare": "husky install"
 },

 

이제 hook을 만듭니다.

npx husky add .husky/pre-commit "npm test"

 

root에 .husky 폴더가 생기며 pre-commit을 확인할 수 있습니다.

 

 pre-commit 파일의 코드를 모두 지우고 아래의 코드를 붙여 넣습니다.

첫 번째 todolist(frontend)의 작업이 끝나면 다음으로 backend로 가야하기 때문에 ../../backend로 이동하여 진행합니다.

 

여기에서 갑자기 튀어나온 lint-staged는 git의 staged된 상태에 파일들에 특정 명령어를 실행할 수 있도록 해주는 툴입니다. 쉽게 변경된 파일에 대해서 특정 명령어를 실행시켜주는 툴입니다.

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

cd ./frontend/todolist && npx lint-staged
cd ../../backend && npx lint-staged

 

각 backend, frontend의 package.json에 아래의 코드를 추가로 넣습니다. ( lint-staged 실행 명령어)

"lint-staged": {
    "*.{js,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ]
 }

 

아래의 코드는 frontend의 pages/_app.tsx 안에 불필요한 코드를 넣어서 commit을 진행해보았습니다. 그러면 사용되지 않는 변수에 대하여 error가 발생됩니다.

결과

위의 error를 수정하고 다시 commit 해보겠습니다. 

결과

backend, frontend 모두 문제가 없어 정상적으로 commit이 되었습니다. 

 

 

 

컴포넌트 만들기


이제 기본 구성을 끝마쳤으니 컴포넌트 작업을 진행해보도록 하겠습니다.

 

 

1. 절대경로 설정

프로젝트가 커지면서 상대경로는 import하기 까다로워 지기 때문에 절대경로 설정을 진행하겠습니다.

tsconfig.json에 baseUrl과 paths를 추가합니다.

 

tsconfig.json

{
  "compilerOptions": {
 	...
    "baseUrl": ".",
    "paths": {
       "~/": ["."],
       "~/*": ["./*"]
     }
   },
  ...
  
}

 

 

2. 회색 배경 적용하기

npm i @mui/material @emotion/react @emotion/styled @mui/icons-material

 

pages/_app.tsx

import { GlobalStyles } from '@mui/material';

function MyApp() {
  return (
    <>
      <GlobalStyles styles={{ body: { backgroundColor: "#cdcdcd" } }} />
    </>
  ) 
}

export default MyApp

 

실행해서 보시면 body의 색이 회색으로 변경되었습니다. 이제 여기 위에 TodoBody를 만들어 보겠습니다.

실행 화면

 

3. TodoBody 만들기

 

components/TodoBody.tsx

import React from 'react';
import { styled } from '@mui/system'


type Props = {
    children: React.ReactNode;
};

const TodoBodyStyle = styled('div')`
  width: 512px;
  height: 768px;

  position: relative;
  background: white;
  border-radius: 15px;
  box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04);

  margin: 0 auto; 

  margin-top: 96px;
  margin-bottom: 32px;
  display: flex;
  flex-direction: column;
`;

const TodoBody : React.FC<Props> = ({ children }) =>  {
  return <TodoBodyStyle>{children}</TodoBodyStyle>;
}

export default TodoBody;

pages/_app.tsx

import { GlobalStyles } from '@mui/material';
import TodoBody from '../components/TodoBody';

function MyApp() {
  return (
    <>
      <GlobalStyles styles={{ body: { backgroundColor: "#cdcdcd" } }} />
      <TodoBody>TODO BODY</TodoBody>
    </>
  ) 
}

export default MyApp

 

실행을 시키면 Todo의 Body가 흰색으로 노출되는 것을 확인할 수 있습니다.

흰색 TodoBody

 

TodoBody에 Title(Todo List)를 넘겨주고 TodoTitle을 만들어 보겠습니다.

import React from 'react'
import { styled } from '@mui/system'

type Props = {
  children: React.ReactNode
  title: string
}

...

const TodoTitle = styled('h2')`
  text-align: center;
  padding: 60px 0;
  margin-bottom: 30px;
  border-bottom: 1px solid #e9ecef;
`

const TodoBody: React.FC<Props> = ({ title, children }) => {
  return (
    <TodoBodyStyle>
  	  <TodoTitle>{title}</TodoTitle>
  	  {children}
    </TodoBodyStyle>
  )
}

export default TodoBody

 

실행 화면

 

이제는 할일의 개수, 완료한 개수에 대해서 보여주는 TodoStatus를 만들어 보겠습니다.

import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded';
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';

....

const TodoTitle = styled('h2')`
	text-align: center;
	padding: 60px 0;
	margin-bottom: 0px;
	border-bottom: 1px solid #e9ecef;
`

const TodoStatus = styled("ul")`
    list-style-type: none;
    display: flex;
    gap: 20px;
    margin-right: 20px;
    justify-content: flex-end;

    & li {
        display: flex;
        align-items: flex-end;
    }

    & em {
        font-size : 19px;
        margin-left : 2px
    }

    & .done {
        color : #d50000
    }

    & .ing {
        color : #388e3c
    }

`

const TodoBody: React.FC<Props> = ({ title, children }) => {
	return (
		<TodoBodyStyle>
			<TodoTitle>{title}</TodoTitle>
			<TodoStatus>
				<li className='ing'>
					<CircleOutlinedIcon/>
					<em>{2}</em>
				</li>
				<li className='done'>
					<CheckCircleOutlineRoundedIcon/>
					<em>{5}</em>
				</li>
			</TodoStatus>
			{children}
		</TodoBodyStyle>
	)
}

export default TodoBody

실행 화면

4. TodoList 만들기

 

_app.tsx

import { GlobalStyles } from '@mui/material'
import TodoBody from '~/components/TodoBody'
import TodoList from '~/components/TodoList'

function MyApp() {
	return (
		<>
			<GlobalStyles styles={{ body: { backgroundColor: '#cdcdcd' } }} />
			<TodoBody title="Todo List">
				<TodoList/>
			</TodoBody>
		</>
	)
}

export default MyApp

 

components/TodoList.tsx

공간을 확인하기 위해 background color를 넣었습니다.

import React from 'react'
import { styled } from '@mui/system'

type Props = {
	children: React.ReactNode
}

const TodoListStyle = styled("div")`
    background: #e9ecef;
    flex : 1
`

const TodoList = () => {
	return (
		<TodoListStyle>
		</TodoListStyle>
	)
}

export default TodoList

실행 화면

5. TodoItem 만들기

 

components/TodoList.tsx

import React from 'react'
import { styled } from '@mui/system'
import TodoItem from './TodoItem'

type Props = {
	children: React.ReactNode
}

const TodoListStyle = styled("div")`
    flex : 1
`

const TodoList = () => {
	return (
		<TodoListStyle>
			<TodoItem status="done" >밥 먹기</TodoItem>
			<TodoItem status="ing" >숙제하기</TodoItem>
		</TodoListStyle>
	)
}

export default TodoList

 

components/TodoItem.tsx

import React from 'react'
import { styled } from '@mui/system'
import Checkbox from '@mui/material/Checkbox';
import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined';
import CheckCircleOutlineRoundedIcon from '@mui/icons-material/CheckCircleOutlineRounded';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import { red ,green ,grey } from '@mui/material/colors';


type Props = {
	children: React.ReactNode
	status : "done" | "ing"
}

const TodoItemStyle = styled("div")`
    font-size : 20px;
	display: flex;
    align-items: center;
`

const Remove = styled(IconButton)`
	order: 2;
	margin-left: auto;
	margin-right: 5px;
`


const TodoItemText = styled("div")`
	color: #cdcdcd;
    text-decoration: line-through;
`

const TodoItem : React.FC<Props> = ({ status , children}) => {

	return (
		<TodoItemStyle>
			<Checkbox
				icon={<CircleOutlinedIcon />}
				checked={status === "done" ? true : false}
				checkedIcon={<CheckCircleOutlineRoundedIcon />}
				sx={{
					'& .MuiSvgIcon-root': { fontSize: 35 },
					color: green[700],
					'&.Mui-checked': {
					  color: red["A700"],
					},
				  }}
			/>
			<TodoItemText sx={{
				color : status === "done" ? grey[400] : "black",
				textDecoration: status === "done" ? "line-through" : "none"
			}}>{children}</TodoItemText>
			<Remove aria-label="delete">
				<DeleteIcon />
			</Remove>
		</TodoItemStyle>
	)
}

export default TodoItem

실행 화면

 

 

5. TodoCreator 만들기

 

components/TodoCreator.tsx

import React, { useState } from 'react'
import { styled } from '@mui/system'
import { Box, IconButton, TextField } from '@mui/material'
import AddCircleIcon from '@mui/icons-material/AddCircle';

const TodoCreatorStyle = styled('div')`
	position : relative;
`
const TodoCreatorButton = styled(IconButton)`
	text-align: center;
	width : 80px;
	height : 80px;
	z-index : 5;

	position: absolute;
	left: 50%;
	bottom: 0;
	transform: translate(-50%, 50%);
	background: white;
	transition: 0.125s all ease-in;
	
	& svg {
		width : 80px;
		height : 80px;
	}

	&:hover {
		background : white
	}
`
const InputForm = styled("div")`
	width: 100%;
	height: 300px;
	bottom : 0px;
	background : #e0dddd;
	border-radius: 0px 0px 15px 15px;
`

const InputBox = styled(Box)`
	position : relative;
	height: 100%;
	padding : 0 20px;
	display : flex;
	flex-direction: column;
	justify-content: space-evenly;
}
`

const TodoCreator = () => {

	const [open, setOpen] = useState(false);

	const handleOpen = () => {
		setOpen(!open)
	}

	return <TodoCreatorStyle>
		<TodoCreatorButton size="large" color={open ? "secondary" : "primary"} sx={ {
				transform: open ? "translate(-50%, 50%) rotate(135deg)" : "translate(-50%, 50%) rotate(0deg)"
		}} onClick={handleOpen} >
			<AddCircleIcon /> 
		</TodoCreatorButton>

		{ open &&
			<InputForm sx={{
			}}>
				<InputBox component="div" >
					<TextField
							required
							label="title"
							defaultValue="제목"
							fullWidth={true}
							sx={{
							}}
					/>
					<TextField
							required
							label="description"
							defaultValue="설명"
							fullWidth={true}
							sx={{
							}}
					/>
				</InputBox>
			</InputForm>
		}
	</TodoCreatorStyle>
}

export default TodoCreator

 

_app.tsx

import { GlobalStyles } from '@mui/material'
import TodoBody from '~/components/TodoBody'
import TodoCreator from '~/components/TodoCreator'
import TodoList from '~/components/TodoList'

function MyApp() {
	return (
		<>
			<GlobalStyles styles={{ body: { backgroundColor: '#cdcdcd' } }} />
			<TodoBody title="Todo List">
				<TodoList />
				<TodoCreator />
			</TodoBody>
		</>
	)
}

export default MyApp

실행 화면

 

마감기간을 넣기 위해 DateTimePicker를 사용합니다. 사용하기 위해 아래의 패키지를 설치합니다.

npm install @mui/lab
npm install date-fns

 

components/TodoCreator.tsx

...
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import DateTimePicker from '@mui/lab/DateTimePicker';

...

const TodoCreator = () => {

	...
	const [deadline , setDeadline] = React.useState<Date | null>(new Date());
	...
    
	return <TodoCreatorStyle>
		...

		{ open &&
			<InputForm sx={{
			}}>
				<InputBox component="div" >
					<TextField
							required
							label="title"
							defaultValue="제목"
							fullWidth={true}
							sx={{
							}}
					/>
					<TextField
							required
							label="description"
							defaultValue="설명"
							fullWidth={true}
							sx={{
							}}
					/>
					<LocalizationProvider dateAdapter={AdapterDateFns}>
						<DateTimePicker
							renderInput={(props) => <TextField {...props} />}
							label="DateTimePicker"
							value={deadline}
							onChange={(newValue) => {
								setDeadline(newValue);
							}}
						/>
					</LocalizationProvider>
				</InputBox>
			</InputForm>
		}
	</TodoCreatorStyle>
}

export default TodoCreator

실행 화면

 

6. SnackBar 만들기

등록 버튼과 등록 버튼을 클릭했을 때 성공 혹은 실패(내용부족)에 대해 SnackBar를 만들고자 합니다.

 

components/TodoCreator.tsx

import { Box, IconButton, TextField, Button, Snackbar } from '@mui/material'
import MuiAlert, { AlertProps } from '@mui/material/Alert';
...



const Alert = React.forwardRef<HTMLDivElement, AlertProps>(function Alert(props,ref) {
	return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});

const TodoCreator = () => {
	const [open, setOpen] = useState(false)
	const [alertOpen, setAlertOpen] = useState(false)
	const [deadline, setDeadline] = React.useState<Date | null>(new Date())

	const handleOpen = () => {
		setOpen(!open)
	}

	const handleClick = () => {
		setAlertOpen(true);
	};
	
	const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
		if (reason === 'clickaway') {
			return;
		}
		setAlertOpen(false);
	};

	  

	return (
		<TodoCreatorStyle>
			<TodoCreatorButton
				size="large"
				color={open ? 'secondary' : 'primary'}
				sx={{
					transform: open ? 'translate(-50%, 50%) rotate(135deg)' : 'translate(-50%, 50%) rotate(0deg)',
				}}
				onClick={handleOpen}>
				<AddCircleIcon />
			</TodoCreatorButton>

			{open && (
				<InputForm sx={{}}>
					<InputBox component="div">
						<TextField required label="제목" defaultValue="제목" fullWidth={true} sx={{}} />
						<TextField required label="설명" defaultValue="설명" fullWidth={true} sx={{}} />
						<LocalizationProvider dateAdapter={AdapterDateFns}>
							<DateTimePicker
								renderInput={(props) => <TextField {...props} />}
								label="마간기한"
								value={deadline}
								onChange={(newValue) => {
									setDeadline(newValue)
								}}
							/>
						</LocalizationProvider>
						<Button variant="contained" onClick={handleClick} >등록</Button>
					</InputBox>
				</InputForm>
			)}

			<Snackbar open={alertOpen} autoHideDuration={6000} onClose={handleClose}>
				<Alert onClose={handleClose} severity="success" sx={{ width: '100%' }}>
				This is a success message!
				</Alert>
			</Snackbar>
		</TodoCreatorStyle>
	)
}

등록 버튼 클릭 시

배경이 조금 이상하여 색을 하얀색에 가깝게하고 border-top을 추가합니다.

const InputForm = styled('div')`
	width: 100%;
	height: 400px;
	bottom: 0px;
	background: #fcfafa;
	border-top: solid 1px #c7c7c7;
	border-radius: 0px 0px 15px 15px;
`

변경

이제 제목, 설명, 마감기한(스크린샷에는 오타..)을 입력받고 등록을 누르게 되면 검증을 하도록합니다. 

제목이 없을 때, 설명이 없을 때, 마감기한을 설정하지 않았을 때 등록을 누르면 SnackBar의 에러 메세지를 노출하고 제대로 입력하고 등록했을 때는 성공하도록 만들어 보겠습니다.

 

message는 SnackBar의 메세지, alertType은 SnackBar의 Type입니다.

todoForm은 todo를 등록하기 위한 Form Data 입니다.

 

components/TodoCreator.tsx

type FormType = {
	title: string,
	description : string,
	deadline : Date
};
type AlertType = "error" | "success"

const Alert = React.forwardRef<HTMLDivElement, AlertProps>(function Alert(
	props,
	ref,
  ) {
	return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});

const TodoCreator = () => {
	const [open, setOpen] = useState(false)
	const [alertOpen, setAlertOpen] = useState(false)
	const [message, setMessage] = React.useState<string>("")
	const [alertType, setAlertType] = React.useState<AlertType>("success")
	const [todoForm, setTodoForm] = React.useState<FormType>({
		title : "",
		description : "",
		deadline : new Date()
	})

	const handleOpen = () => {
		setOpen(!open)
	}

	const handleClick = () => {
		if(todoForm.title === ""){
			setAlertType("error")
			setMessage("제목을 입력해주세요.")
		}
		else if(todoForm.description === ""){
			setAlertType("error")
			setMessage("설명을 입력해주세요.")
		}
		else if( (todoForm.deadline).getTime() < new Date().getTime() ){
			setAlertType("error")
			setMessage("마감기한을 설정해주세요.")
		}
		else{
			setAlertType("success")
			setMessage(JSON.stringify(todoForm))
		}
		setAlertOpen(true);
	};
	
	const handleClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
		if (reason === 'clickaway') {
			return;
		}
		setAlertOpen(false);
	};

	  

	return (
		<TodoCreatorStyle>
			<TodoCreatorButton
				size="large"
				color={open ? 'secondary' : 'primary'}
				sx={{
					transform: open ? 'translate(-50%, 50%) rotate(135deg)' : 'translate(-50%, 50%) rotate(0deg)',
				}}
				onClick={handleOpen}>
				<AddCircleIcon />
			</TodoCreatorButton>

			{open && (
				<InputForm sx={{}}>
					<InputBox component="div">
						<TextField required label="제목" fullWidth={true} sx={{}} onChange={ e =>
							setTodoForm({ 
								...todoForm , 
								title : e.target.value
							}) 
						}/>
						<TextField required label="설명" fullWidth={true} sx={{}} onChange={ e =>
							setTodoForm({ 
								...todoForm , 
								description : e.target.value
							}) 
						} />
						<LocalizationProvider dateAdapter={AdapterDateFns}>
							<DateTimePicker
								renderInput={(props) => <TextField {...props} />}
								label="마감기한"
								value={todoForm.deadline}
								onChange={(newValue) => {
									if(newValue != null)
										setTodoForm({ 
											...todoForm , 
											deadline : newValue
										}) 
								}}
							/>
						</LocalizationProvider>
						<Button variant="contained" onClick={handleClick} >등록</Button>
					</InputBox>
				</InputForm>
			)}

			<Snackbar open={alertOpen} autoHideDuration={6000} onClose={handleClose}>
				<Alert onClose={handleClose} severity={alertType} sx={{ width: '100%' }}>
					{ message }
				</Alert>
			</Snackbar>
		</TodoCreatorStyle>
	)
}

export default TodoCreator

성공 시에는 message에 todoForm을 노출되도록 했습니다.

 

마무리


이제 Graphql을 통하여 처음에 만든 Nest.js 서버에 요청을 통해 DB에 입력되도록 설정해보겠습니다.