From e2db3bb0242dbea0ba7320735d7963bfc2a2a185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=81=EC=9D=80?= Date: Thu, 16 Nov 2023 15:19:01 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + README.md | 336 ++++++++++------------------------------- __tests__/DateTest.js | 18 +++ __tests__/MenuTest.js | 25 +++ package-lock.json | 4 +- src/App.js | 81 +++++++++- src/ErrorCases.js | 17 +++ src/InputView.js | 7 - src/Model/Date.js | 55 +++++++ src/Model/Event.js | 127 ++++++++++++++++ src/Model/Food.js | 43 ++++++ src/Model/Menu.js | 98 ++++++++++++ src/OutputView.js | 7 - src/View/InputView.js | 13 ++ src/View/OutputView.js | 64 ++++++++ src/constants.js | 113 ++++++++++++++ src/menus.js | 88 +++++++++++ 17 files changed, 827 insertions(+), 272 deletions(-) create mode 100644 __tests__/DateTest.js create mode 100644 __tests__/MenuTest.js create mode 100644 src/ErrorCases.js delete mode 100644 src/InputView.js create mode 100644 src/Model/Date.js create mode 100644 src/Model/Event.js create mode 100644 src/Model/Food.js create mode 100644 src/Model/Menu.js delete mode 100644 src/OutputView.js create mode 100644 src/View/InputView.js create mode 100644 src/View/OutputView.js create mode 100644 src/constants.js create mode 100644 src/menus.js diff --git a/.gitignore b/.gitignore index d5f19d8..7e8a741 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules package-lock.json +.DS_Store +package-lock.json +.gitignore \ No newline at end of file diff --git a/README.md b/README.md index d974517..bcfae5f 100644 --- a/README.md +++ b/README.md @@ -1,286 +1,112 @@ # 미션 - 크리스마스 프로모션 -## 🔍 진행 방식 +## 💻 기능 목록 -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. -- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. -- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. +1. MVC 패턴을 활용하여 클래스 나누기 -## 📮 미션 제출 방법 +2. 사용자로부터 입력값읇 받는다. -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://docs.google.com/document/d/1cmg0VpPkuvdaetxwp4hnyyFC_G-1f2Gr8nIDYIWcKC8/edit?usp=sharing) 문서를 참고해 - 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고 - - **지원 플랫폼을 통해 과제를 제출하지 않으면 최종 제출되지 않은 것으로 처리되므로 주의한다.** + 2-1. 입력값이 유효한 값인지 검증한다. -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 + 가능한 에러 상황들 -- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. -- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. + - 날짜 -### 테스트 실행 가이드 + - 빈 값 + + - 정수가 아닌 값 + + - 정수이지만 1~31 사이의 값이 아닐 때 + + - 메뉴목록 + + - 빈 값 + + - 형식( "메뉴-수량,메뉴-수량,...")에 맞지 않는 값 + + - 형식은 맞지만 주어진 메뉴에 없는 음식일 때 + + - 각 메뉴당 가능한 수량이 1~20 이고, 전체 수량의 최댓값이 20이다. + + - 메뉴가 중복될 때 -- 테스트 패키지 설치를 위해 `Node.js` 버전 `18.17.1` 이상이 필요하다. -- 다음 명령어를 입력해 패키지를 설치한다. +4. 사용자에게 출력값을 보여준다. -```bash -npm install -``` +5. 정해진 기간동안 해당하는 이벤트를 구분해서 할인과 혜택을 계산할 수 있다. -- 설치가 완료되었다면, 다음 명령어를 입력해 테스트를 실행한다. + 5-1. 디데이 이벤트 할인 가격을 계산할 수 있다. -```bash -npm test -``` + 5-2. 요일별로 맞는 메뉴 할인할 수 있다. ---- + 5-3. 특별 할인을 할 수 있다. -## 🚀 기능 요구 사항 - -> 이번 미션은 이메일 형식의 기능 요구 사항입니다. 문제를 구현하는 데 필요한 요구사항과 배경지식은 이메일 내용에 전부 담겨있으니, 꼼꼼하게 확인하고 필요하다면 주어진 문제의 내용을 통해 유추하고 스스로 판단해 -> 구현해 주시면 됩니다. 문제의 모든 내용은 충분히 검토되었으며, 출제 의도를 담은 내용임을 알려드립니다. - -보낸 사람: 비즈니스팀 \<`biz@woowacourse.io`\> -받는 사람: 개발팀 \<`dev@woowacourse.io`\> - -제목: 12월 이벤트를 위한 개발 요청 - -안녕하세요. 비즈니스팀입니다! - -다가오는 2023년 12월에 우테코 식당에서 1년 중 제일 큰 이벤트를 개최하려고 합니다. -12월을 위해 이벤트 예산을 넉넉히 확보해 두었으니, 예산은 걱정하지 마세요~ - -특별히 이번 12월 이벤트를 진행하기 위해서, 개발팀의 도움이 많이 필요합니다. -아래 메뉴와 달력 이미지를 보면서 12월 이벤트 계획과 요청 내용을 본격적으로 설명해 드릴게요. - -#### 메뉴 - -``` -<애피타이저> -양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000) - -<메인> -티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000) - -<디저트> -초코케이크(15,000), 아이스크림(5,000) - -<음료> -제로콜라(3,000), 레드와인(60,000), 샴페인(25,000) -``` - -#### 달력 - -![](image.png) - -#### 이벤트 목표 + 5-4. 할인 전 총 금액을 계산할 수 있다. -1. 중복된 할인과 증정을 허용해서, 고객들이 혜택을 많이 받는다는 것을 체감할 수 있게 하는 것 -2. 올해 12월에 지난 5년 중 최고의 판매 금액을 달성 -3. 12월 이벤트 참여 고객의 5%가 내년 1월 새해 이벤트에 재참여하는 것 + 5-5. 총 가격이 12만원 이상일 때 샴페인 1병을 증정한다. -#### 12월 이벤트 계획 + 5-6. 총 혜택 가격을 계산할 수 있다. -- 크리스마스 디데이 할인 - - 이벤트 기간: 2023.12.1 ~ 2023.12.25 - - 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가 - - 총주문 금액에서 해당 금액만큼 할인 - (e.g. 시작일인 12월 1일에 1,000원, 2일에 1,100원, ..., 25일엔 3,400원 할인) -- 평일 할인(일요일~목요일): 평일에는 디저트 메뉴를 메뉴 1개당 2,023원 할인 -- 주말 할인(금요일, 토요일): 주말에는 메인 메뉴를 메뉴 1개당 2,023원 할인 -- 특별 할인: 이벤트 달력에 별이 있으면 총주문 금액에서 1,000원 할인 -- 증정 이벤트: 할인 전 총주문 금액이 12만 원 이상일 때, 샴페인 1개 증정 -- 이벤트 기간: '크리스마스 디데이 할인'을 제외한 다른 이벤트는 2023.12.1 ~ 2023.12.31 동안 적용 + 5-7. 총 할인 가격 및 계산해야 할 가격을 계산할 수 있다. -#### 혜택 금액에 따른 12월 이벤트 배지 부여 +7. 에러 상황 때 에러를 내고, 에러 문구를 보여준다. (시작은 [ERROR] ) -- 총혜택 금액에 따라 다른 이벤트 배지를 부여합니다. 이 배지는 2024 새해 이벤트에서 활용할 예정입니다. - 배지에 따라 새해 이벤트 참여 시, 각각 다른 새해 선물을 증정할 예정입니다. - - 5천 원 이상: 별 - - 1만 원 이상: 트리 - - 2만 원 이상: 산타 -#### 고객에게 안내할 이벤트 주의 사항 - -- 총주문 금액 10,000원 이상부터 이벤트가 적용됩니다. -- 음료만 주문 시, 주문할 수 없습니다. -- 메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다. - (e.g. 시저샐러드-1, 티본스테이크-1, 크리스마스파스타-1, 제로콜라-3, 아이스크림-1의 총개수는 7개) +## 🚀 기능 요구 사항 #### '12월 이벤트 플래너' 개발 요청 사항 -- 고객들이 식당에 방문할 날짜와 메뉴를 미리 선택하면 이벤트 플래너가 주문 메뉴, 할인 전 총주문 금액, 증정 메뉴, 혜택 내역, 총혜택 금액, 할인 후 예상 결제 금액, 12월 이벤트 배지 내용을 보여주기를 기대합니다. -- 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) - - 방문할 날짜는 1 이상 31 이하의 숫자로만 입력받아 주세요. - - 1 이상 31 이하의 숫자가 아닌 경우, "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. - - 모든 에러 메시지는 "[ERROR]"로 시작하도록 작성해 주세요. -- 주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) - - 고객이 메뉴판에 없는 메뉴를 입력하는 경우, "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. - - 메뉴의 개수는 1 이상의 숫자만 입력되도록 해주세요. 이외의 입력값은 "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. - - 메뉴 형식이 예시와 다른 경우, "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. - - 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1), "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. - - 모든 에러 메시지는 "[ERROR]"로 시작하도록 작성해 주세요. -- 주문 메뉴의 출력 순서는 자유롭게 출력해 주세요. -- 총혜택 금액에 따라 이벤트 배지의 이름을 다르게 보여 주세요. -- 총혜택 금액 = 할인 금액의 합계 + 증정 메뉴의 가격 -- 할인 후 예상 결제 금액 = 할인 전 총주문 금액 - 할인 금액 -- 증정 메뉴 - - 증정 이벤트에 해당하지 않는 경우, 증정 메뉴 "없음"으로 보여 주세요. -- 혜택 내역 - - 고객에게 적용된 이벤트 내역만 보여 주세요. - - 적용된 이벤트가 하나도 없다면 혜택 내역 "없음"으로 보여 주세요. - - 혜택 내역에 여러 개의 이벤트가 적용된 경우, 출력 순서는 자유롭게 출력해주세요. -- 이벤트 배지 - - 이벤트 배지가 부여되지 않는 경우, "없음"으로 보여 주세요. -- 적용된 이벤트가 하나도 없는 경우는 아래 예시를 참고해 주세요. - -``` -안녕하세요! 우테코 식당 12월 이벤트 플래너입니다. -12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) -26 -주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) -타파스-1,제로콜라-1 -12월 26일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! - -<주문 메뉴> -타파스 1개 -제로콜라 1개 - -<할인 전 총주문 금액> -8,500원 - -<증정 메뉴> -없음 - -<혜택 내역> -없음 - -<총혜택 금액> -0원 - -<할인 후 예상 결제 금액> -8,500원 - -<12월 이벤트 배지> -없음 -``` - -#### 기대하는 '12월 이벤트 플래너'의 예시 모습 - -``` -안녕하세요! 우테코 식당 12월 이벤트 플래너입니다. -12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) -3 -주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) -티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1 -12월 3일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! - -<주문 메뉴> -티본스테이크 1개 -바비큐립 1개 -초코케이크 2개 -제로콜라 1개 - -<할인 전 총주문 금액> -142,000원 - -<증정 메뉴> -샴페인 1개 - -<혜택 내역> -크리스마스 디데이 할인: -1,200원 -평일 할인: -4,046원 -특별 할인: -1,000원 -증정 이벤트: -25,000원 - -<총혜택 금액> --31,246원 - -<할인 후 예상 결제 금액> -135,754원 - -<12월 이벤트 배지> -산타 -``` - -기대하는 예시를 한 개만 들어서 설명했지만, 더 다양한 사례가 있을 것으로 예상됩니다. -개발이 완료되는 대로 공유해 주시면, 비즈니스팀에서 1주일간 테스트를 진행하고 오픈할 예정입니다. -1주일 뒤에 예정된 '12월 이벤트 플래너' 개발 회의에서 더 자세한 얘기를 해보면 좋겠습니다. - -감사합니다. :) +- [X] 고객들이 식당에 방문할 날짜와 메뉴를 미리 선택하면 이벤트 플래너가 주문 메뉴, 할인 전 총주문 금액, 증정 메뉴, 혜택 내역, 총혜택 금액, 할인 후 예상 결제 금액, 12월 이벤트 배지 내용을 보여주기를 기대합니다. +- [X] 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!) + - [X] 방문할 날짜는 1 이상 31 이하의 숫자로만 입력받아 주세요. + - [X] 1 이상 31 이하의 숫자가 아닌 경우, "[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. + - [X] 모든 에러 메시지는 "[ERROR]"로 시작하도록 작성해 주세요. +- [X] 주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1) + - [X] 고객이 메뉴판에 없는 메뉴를 입력하는 경우, "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. + - [X] 메뉴의 개수는 1 이상의 숫자만 입력되도록 해주세요. 이외의 입력값은 "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. + - [X] 메뉴 형식이 예시와 다른 경우, "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. + - [ ] 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1), "[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요."라는 에러 메시지를 보여 주세요. + - [X] 모든 에러 메시지는 "[ERROR]"로 시작하도록 작성해 주세요. +- [X] 주문 메뉴의 출력 순서는 자유롭게 출력해 주세요. +- [X] 총혜택 금액에 따라 이벤트 배지의 이름을 다르게 보여 주세요. +- [X] 총혜택 금액 = 할인 금액의 합계 + 증정 메뉴의 가격 +- [X] 할인 후 예상 결제 금액 = 할인 전 총주문 금액 - 할인 금액 +- [X] 증정 메뉴 + - [X] 증정 이벤트에 해당하지 않는 경우, 증정 메뉴 "없음"으로 보여 주세요. +- [X] 혜택 내역 + - [X] 고객에게 적용된 이벤트 내역만 보여 주세요. + - [X] 적용된 이벤트가 하나도 없다면 혜택 내역 "없음"으로 보여 주세요. + - [X] 혜택 내역에 여러 개의 이벤트가 적용된 경우, 출력 순서는 자유롭게 출력해주세요. +- [X] 이벤트 배지 + - [X] 이벤트 배지가 부여되지 않는 경우, "없음"으로 보여 주세요. ---- ## 🎯 프로그래밍 요구 사항 -- Node.js 18.17.1 버전에서 실행 가능해야 한다. **Node.js 18.17.1에서 정상적으로 동작하지 않을 경우 0점 처리한다.** -- 프로그램 실행의 시작점은 `App.js`의 `run` 메서드이다. 아래와 같이 프로그램을 실행시킬 수 있어야 한다. - -**예시** - -```javascript -const app = new App(); -app.run(); -``` - -- `package.json`을 변경할 수 없고 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않는다. 순수 Vanilla JS로만 구현한다. -- [JavaScript 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/javascript)을 지키면서 프로그래밍 한다 -- 프로그램 종료 시 `process.exit()`를 호출하지 않는다. -- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** -- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. -- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. - - 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. -- else를 지양한다. - - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. - - 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. -- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLineAsync, Console.print) 로직에 대한 단위 테스트는 제외한다. - - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. -- 사용자가 잘못된 값을 입력할 경우 `throw`문을 사용해 예외를 발생시킨다. 그런 다음, "[ERROR]"로 시작하는 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다. +- [X] Node.js 18.17.1 버전에서 실행 가능해야 한다. **Node.js 18.17.1에서 정상적으로 동작하지 않을 경우 0점 처리한다.** +- [X] 프로그램 실행의 시작점은 `App.js`의 `run` 메서드이다. 아래와 같이 프로그램을 실행시킬 수 있어야 한다. +- [X] `package.json`을 변경할 수 없고 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않는다. 순수 Vanilla JS로만 구현한다. +- [X] [JavaScript 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/javascript)을 지키면서 프로그래밍 한다 +- [X] 프로그램 종료 시 `process.exit()`를 호출하지 않는다. +- [X] 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** +- [X] 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. +- [X] indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - [X] 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - [X] 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- [X] Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. +- [X] 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - [X] 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- [X] else를 지양한다. + - [X] 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - [X] 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다. +- [X] 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLineAsync, Console.print) 로직에 대한 단위 테스트는 제외한다. + - [X] 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다. +- [X] 사용자가 잘못된 값을 입력할 경우 `throw`문을 사용해 예외를 발생시킨다. 그런 다음, "[ERROR]"로 시작하는 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다. ### 추가된 요구 사항 -- 아래에 제공되는 `InputView`, `OutputView` 객체를 활용해 구현한다. - - 입력과 출력을 담당하는 객체를 별도로 구현한다. - - `InputView`, `OutputView`의 파일 경로는 변경할 수 있다. - - `InputView`, `OutputView`의 메서드의 이름과 인자는 필요에 따라 추가하거나 변경할 수 있다. - - 값 출력을 위해 필요한 메서드를 추가할 수 있다. - ```javascript - export default InputView = { - async readDate() { - const input = await Console.readLineAsync("12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)"); - // ... - }, - // ... - }; - ``` - ```javascript - export default OutputView = { - printMenu() { - Console.print("<주문 메뉴>"); - // ... - }, - // ... - }; - ``` - -### 라이브러리 - -- `@woowacourse/mission-utils`에서 제공하는 `Console` API를 사용하여 구현해야 한다. - - 사용자의 값을 입력 받고 출력하기 위해서는 `Console.readLineAsync`, `Console.print`를 활용한다. - ---- - -## ✏️ 과제 진행 요구 사항 +- [X] 아래에 제공되는 `InputView`, `OutputView` 객체를 활용해 구현한다. + - [X] 입력과 출력을 담당하는 객체를 별도로 구현한다. + - [X] `InputView`, `OutputView`의 파일 경로는 변경할 수 있다. + - [X] `InputView`, `OutputView`의 메서드의 이름과 인자는 필요에 따라 추가하거나 변경할 수 있다. + - [X] 값 출력을 위해 필요한 메서드를 추가할 수 있다. -- 미션은 [javascript-christmas-6](https://github.com/woowacourse-precourse/javascript-christmas-6) 저장소를 비공개 저장소로 생성해 시작한다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. - - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://docs.google.com/document/d/1cmg0VpPkuvdaetxwp4hnyyFC_G-1f2Gr8nIDYIWcKC8/edit?usp=sharing) 문서를 참고한다. diff --git a/__tests__/DateTest.js b/__tests__/DateTest.js new file mode 100644 index 0000000..96a9126 --- /dev/null +++ b/__tests__/DateTest.js @@ -0,0 +1,18 @@ +import Date from '../src/Model/Date'; +describe('날짜 입력값 테스트', () => { + const INVALID_DATE_MESSAGE = '[ERROR]'; + test('0 이하 숫자 입력', () => { + const INVALID_DATE = '0'; + expect(() => new Date(INVALID_DATE)).toThrow(INVALID_DATE_MESSAGE); + }); + + test('32 이상 숫자 입력', () => { + const INVALID_DATE = '1000'; + expect(() => new Date(INVALID_DATE)).toThrow(INVALID_DATE_MESSAGE); + }); + + test('숫자가 아닌 문자 입력', () => { + const INVALID_DATE = 'abcd'; + expect(() => new Date(INVALID_DATE)).toThrow(INVALID_DATE_MESSAGE); + }); +}); diff --git a/__tests__/MenuTest.js b/__tests__/MenuTest.js new file mode 100644 index 0000000..81e0315 --- /dev/null +++ b/__tests__/MenuTest.js @@ -0,0 +1,25 @@ +import Menu from '../src/Model/Menu'; + +describe('메뉴 이름과 수량 입력값 테스트', () => { + const INVALID_MENU_MESSAGE = '[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.'; + test('형식에 맞지 않은 입력값', () => { + const INVALID_MENU = '크리스마스파스타4'; + expect(() => new Menu(INVALID_MENU)).toThrow(INVALID_MENU_MESSAGE); + }); + test('메뉴 개수 0개 입력', () => { + const INVALID_MENU = '크리스마스파스타-0,와인-0'; + expect(() => new Menu(INVALID_MENU)).toThrow(INVALID_MENU_MESSAGE); + }); + test('음료 1개 입력', () => { + const INVALID_MENU = '와인-1'; + expect(() => new Menu(INVALID_MENU)).toThrow(INVALID_MENU_MESSAGE); + }); + test('매뉴 총 개수 20개 초과', () => { + const INVALID_MENU = '크리스마스파스타-10,와인-20'; + expect(() => new Menu(INVALID_MENU)).toThrow(INVALID_MENU_MESSAGE); + }); + test('중복 메뉴 입력', () => { + const INVALID_MENU = '크리스마스파스타-1,크리스마스파스타-2,와인-3'; + expect(() => new Menu(INVALID_MENU)).toThrow(INVALID_MENU_MESSAGE); + }); +}); diff --git a/package-lock.json b/package-lock.json index 779fba3..5fac301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "javascript-lotto", + "name": "javascript-christmas-6", "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "javascript-lotto", + "name": "javascript-christmas-6", "version": "1.0.0", "license": "MIT", "dependencies": { diff --git a/src/App.js b/src/App.js index 091aa0a..5d24339 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,84 @@ +import InputView from './View/InputView.js'; +import OutputView from './View/OutputView.js'; +import Date from './Model/Date.js'; +import Menu from './Model/Menu.js'; +import Event from './Model/Event.js'; +import { AMOUNT, ASK, DIVIDER_HYPHEN, GREETING, HEADER, MENU, SEE_MORE_ABOUT_EVENTS } from './constants.js'; +import menusData from './menus.js'; + class App { - async run() {} + constructor() { + this.inputView = new InputView(); + this.outputView = new OutputView(); + } + async run() { + this.outputView.print(GREETING); + let dateInput; + try { + dateInput = await this.#getDateInput(); + } catch (error) { + this.outputView.print(error.message); + dateInput = await this.#getDateInput(); + } + const date = new Date(dateInput); + let menuInput; + try { + menuInput = await this.#getMenuInput(); + } catch (error) { + this.outputView.print(error.message); + menuInput = await this.#getMenuInput(); + } + const menu = new Menu(menuInput); + this.outputView.print(SEE_MORE_ABOUT_EVENTS(date.getDate())); + this.outputView.print(HEADER.MENU); + const foodArr = menu.getFoodArr(); + foodArr.forEach((food) => { + const name = food.getName(); + const amount = food.getAmount(); + this.outputView.printMenu(name, amount, DIVIDER_HYPHEN); + }); + this.outputView.print(HEADER.TOTAL_PRICE_BEFORE_DISCOUNT); + const totalPricePreDiscount = menu.getTotalPrice(); + this.outputView.printMoney(totalPricePreDiscount); + const event = new Event(date, menu); + this.outputView.print(HEADER.GIVEAWAY_MENU); + if (event.validateGiveAwayEvent(totalPricePreDiscount)) { + const [giveAwayFoodName, giveAwayFoodAmount] = event.getGiveAwayInfo(); + this.outputView.printMenu(giveAwayFoodName, giveAwayFoodAmount); + } else { + this.outputView.print(AMOUNT.NONE); + } + this.outputView.print(HEADER.BENEFITS_LIST); + const discountArr = event.getDiscountList(); + const giveAwayPrizePrice = event.validateGiveAwayEvent(totalPricePreDiscount) + ? menusData.get(MENU.CHAMPAGNE).price + : AMOUNT.ZERO; + this.outputView.printDiscountList([...discountArr, giveAwayPrizePrice], date); + this.outputView.print(HEADER.TOTAL_BENEFITS_PRICE); + const totalBenefits = event.getTotalBenefits(totalPricePreDiscount); + this.outputView.printMoney(totalBenefits, DIVIDER_HYPHEN); + this.outputView.print(HEADER.TOTAL_PRICE_AFTER_DISCOUNT); + const totalDiscount = event.getTotalDiscount(); + this.outputView.printMoney(totalPricePreDiscount - totalDiscount); + this.outputView.print(HEADER.EVENT_BADGE); + const badge = event.getBadge(totalBenefits); + this.outputView.print(badge); + return; + } + async #getDateInput() { + this.outputView.print(ASK.SCHEDULE); + const dateInput = await this.inputView.readData(ASK.SCHEDULE); + this.outputView.print(dateInput); + new Date(dateInput); + return dateInput; + } + async #getMenuInput() { + this.outputView.print(ASK.MENU_INFO); + const menuInput = await this.inputView.readData(ASK.MENU_INFO); + this.outputView.print(menuInput); + new Menu(menuInput); + return menuInput; + } } export default App; diff --git a/src/ErrorCases.js b/src/ErrorCases.js new file mode 100644 index 0000000..8b0e4f5 --- /dev/null +++ b/src/ErrorCases.js @@ -0,0 +1,17 @@ +import { ERROR } from './constants'; +class DateError extends Error { + constructor(...params) { + super(...params); + this.message = ERROR.message.NOT_VALID_NUMBER; + this.name = ERROR.name.DATE; + } +} +class MenuError extends Error { + constructor(...params) { + super(...params); + this.message = ERROR.message.NOT_VALID_ORDER; + this.name = ERROR.name.ORDER; + } +} + +export { DateError, MenuError }; diff --git a/src/InputView.js b/src/InputView.js deleted file mode 100644 index 532bd87..0000000 --- a/src/InputView.js +++ /dev/null @@ -1,7 +0,0 @@ -export default InputView = { - async readDate() { - const input = await Console.readLineAsync("12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)"); - // ... - } - // ... -} diff --git a/src/Model/Date.js b/src/Model/Date.js new file mode 100644 index 0000000..ea7ab9d --- /dev/null +++ b/src/Model/Date.js @@ -0,0 +1,55 @@ +import { FIRST_DAY_OF_MONTH, LAST_DAY_OF_MONTH, EVENT_DAYS } from '../constants.js'; +import { DateError } from '../ErrorCases.js'; +class Date { + #date; + constructor(string) { + this.#date = this.#validateDate(string); + this.getDate = this.getDate; + this.getIsWeekend = this.getIsWeekend; + this.getIsStarred = this.getIsStarred; + } + #validateDate(string) { + if (string) { + const number = Number(string); + this.#validateInteger(number); + this.#validateRange(number); + return number; + } + this.#throwError(); + } + #validateInteger(number) { + if (Number.isInteger(number)) { + return number; + } + this.#throwError(); + } + #validateRange(number) { + if (number >= FIRST_DAY_OF_MONTH && number <= LAST_DAY_OF_MONTH) { + return number; + } + this.#throwError(); + } + #validateIfWeekend(date) { + if (EVENT_DAYS.WEEKENDS.includes(date)) { + return true; + } + return false; + } + getDate() { + return this.#date; + } + getIsWeekend() { + return this.#validateIfWeekend(this.#date); + } + getIsStarred(date) { + if (EVENT_DAYS.STARRED_DAYS.includes(date.getDate())) { + return true; + } + return false; + } + #throwError() { + throw new DateError(); + } +} + +export default Date; diff --git a/src/Model/Event.js b/src/Model/Event.js new file mode 100644 index 0000000..85c8541 --- /dev/null +++ b/src/Model/Event.js @@ -0,0 +1,127 @@ +import { + EVENT_BADGE, + AMOUNT, + D_DAY_EVENT_STARTING_PRICE, + D_DAY_EVENT_INCREASING_PRICE, + STARRED_DAY_EVENT_DISCOUNT, + WEEK_EVENT_DISCOUNT, + COURSE, + MENU, + CHRISTMAS_D_DAY, + GIVEAWAY_EVENT_MIN_PRICE, +} from '../constants.js'; +import menusData from '../menus.js'; + +class Event { + #discount; + constructor(date, menu) { + this.#discount = [ + this.#calculateDDayEventDiscount(date), + this.#calculateWeekEventDiscount(date, menu), + this.#calculateStarredDayEventDiscount(date), + ]; + this.getDiscountList = this.getDiscountList; + this.validateGiveAwayEvent = this.validateGiveAwayEvent; + this.getGiveAwayInfo = this.getGiveAwayInfo; + this.getBadge = this.getBadge; + } + #calculateDDayEventDiscount(date) { + const dDayDate = date.getDate(); + if (dDayDate > CHRISTMAS_D_DAY) { + return AMOUNT.ZERO; + } + const dDayDiscount = D_DAY_EVENT_STARTING_PRICE + D_DAY_EVENT_INCREASING_PRICE * (date.getDate() - 1); + return dDayDiscount; + } + #calculateWeekEventDiscount(date, menu) { + const isWeekend = date.getIsWeekend(); + + if (!isWeekend) { + //dessert + const dessertFoodArr = this.#getDessertFoodArr(menu); + let weekDiscount = 0; + if (dessertFoodArr.length > 0) { + dessertFoodArr.forEach((food) => { + weekDiscount += food.getAmount() * WEEK_EVENT_DISCOUNT; + }); + return weekDiscount; + } + return AMOUNT.ZERO; + } + if (isWeekend) { + //main + const mainFoodArr = this.#getMainFoodArr(menu); + let weekDiscount = 0; + if (mainFoodArr.length > 0) { + mainFoodArr.forEach((food) => { + weekDiscount += food.getAmount() * WEEK_EVENT_DISCOUNT; + }); + return weekDiscount; + } + return AMOUNT.ZERO; + } + return AMOUNT.ZERO; + } + #calculateStarredDayEventDiscount(date) { + if (date.getIsStarred(date)) { + return STARRED_DAY_EVENT_DISCOUNT; + } + return AMOUNT.ZERO; + } + #getDessertFoodArr(menu) { + const orderedFoodArr = menu.getFoodArr(); + const dessertFoodArr = []; + orderedFoodArr.forEach((orderedFood) => { + const orderedFoodCourse = orderedFood.getCourse(); + if (orderedFoodCourse === COURSE.DESSERT) { + dessertFoodArr.push(orderedFood); + } + }); + return dessertFoodArr; + } + #getMainFoodArr(menu) { + const orderedFoodArr = menu.getFoodArr(); + const mainFoodArr = []; + orderedFoodArr.forEach((orderedFood) => { + const orderedFoodCourse = orderedFood.getCourse(); + if (orderedFoodCourse === COURSE.MAIN) { + mainFoodArr.push(orderedFood); + } + }); + return mainFoodArr; + } + validateGiveAwayEvent(totalPricePreDiscount) { + if (totalPricePreDiscount >= GIVEAWAY_EVENT_MIN_PRICE) { + return true; + } + return false; + } + getTotalBenefits(totalPricePreDiscount) { + if (this.validateGiveAwayEvent(totalPricePreDiscount)) { + return this.#discount[0] + this.#discount[1] + this.#discount[2] + menusData.get(MENU.CHAMPAGNE).price; + } + return this.#discount[0] + this.#discount[1] + this.#discount[2] + AMOUNT.ZERO; + } + getDiscountList() { + return this.#discount; + } + getTotalDiscount() { + return this.#discount[0] + this.#discount[1] + this.#discount[2]; + } + getGiveAwayInfo() { + return [MENU.CHAMPAGNE, AMOUNT.ONE]; + } + getBadge(totalBenefits) { + if (totalBenefits >= EVENT_BADGE.SANTA_MIN) { + return EVENT_BADGE.SANTA; + } + if (totalBenefits >= EVENT_BADGE.TREE_MIN) { + return EVENT_BADGE.TREE; + } + if (totalBenefits >= EVENT_BADGE.STAR_MIN) { + return EVENT_BADGE.STAR; + } + return AMOUNT.NONE; + } +} +export default Event; diff --git a/src/Model/Food.js b/src/Model/Food.js new file mode 100644 index 0000000..e7129c5 --- /dev/null +++ b/src/Model/Food.js @@ -0,0 +1,43 @@ +import { MenuError } from '../ErrorCases.js'; +import { MAX_AMOUNT, MIN_AMOUNT } from '../constants.js'; +import menusData from '../menus.js'; +class Food { + #name; + #amount; + constructor(name, amount) { + this.#name = this.#validateMenuName(name); + this.#amount = this.#validateAmount(amount); + this.getName = this.getName; + this.getAmount = this.getAmount; + this.getPrice = this.getPrice; + this.getCourse = this.getCourse; + } + #validateMenuName(name) { + if (menusData.has(name)) { + return name; + } + this.#throwError(); + } + #validateAmount(amount) { + if (amount >= MIN_AMOUNT && amount <= MAX_AMOUNT) { + return amount; + } + this.#throwError(); + } + getName() { + return this.#name; + } + getAmount() { + return this.#amount; + } + getPrice() { + return menusData.get(this.#name).price; + } + getCourse() { + return menusData.get(this.#name).course; + } + #throwError() { + throw new MenuError(); + } +} +export default Food; diff --git a/src/Model/Menu.js b/src/Model/Menu.js new file mode 100644 index 0000000..667e69a --- /dev/null +++ b/src/Model/Menu.js @@ -0,0 +1,98 @@ +import Food from './Food.js'; +import { MenuError } from '../ErrorCases.js'; +import { AMOUNT, COURSE, DIVIDER_COMMA, DIVIDER_HYPHEN, ERROR, MAX_AMOUNT, MIN_AMOUNT, REG_EXP } from '../constants.js'; +import menusData from '../menus.js'; +class Menu { + #orderedFoodArr; + constructor(input) { + this.#orderedFoodArr = this.#validateInput(input); + this.getFoodArr = this.getFoodArr; + this.getTotalAmount = this.getTotalAmount; + this.getTotalPrice = this.getTotalPrice; + } + #validateInput(input) { + if (input) { + const inputArr = input.split(DIVIDER_COMMA); + const foodArr = []; + inputArr.forEach((item) => { + this.#validateRegExp(item); + const [name, amount] = this.#validateMenu(item); + const food = new Food(name, amount); + foodArr.push(food); + }); + return foodArr; + } + this.#throwError(); + } + #validateDuplicateMenu(foodArr) { + const foodNameArr = foodArr.map((food) => food.getName()); + if (new Map(foodNameArr).size !== foodNameArr.length) { + this.#throwError(); + } + return foodArr; + } + #validateSingleDrink(foodArr) { + if (foodArr.length === 1) { + const foodName = foodArr[0].getName(); + if (menusData.get(foodName).course === COURSE.DRINKS) { + this.#throwError(); + } + } + return foodArr; + } + + #validateRegExp(string) { + if (!string.match(REG_EXP)) { + this.#throwError(); + } + } + #validateMenu(string) { + const menu = string.split(DIVIDER_HYPHEN); + this.#validateRange(Number(menu[1])); + return [menu[0], Number(menu[1])]; + } + #validateRange(number) { + if (Number.isInteger(number)) { + if (number >= MIN_AMOUNT && number <= MAX_AMOUNT) { + return number; + } + this.#throwError(); + } + } + #calculateTotalAmount() { + const foodArr = this.#orderedFoodArr; + let total = 0; + foodArr.forEach((food) => { + const foodAmount = food.getAmount(); + total += foodAmount; + }); + if (total < MIN_AMOUNT) { + this.#throwError(); + } + if (total > MAX_AMOUNT) { + this.#throwError(); + } + return total; + } + #calculateTotalPrice() { + const foodArr = this.#orderedFoodArr; + let total = 0; + foodArr.forEach((food) => { + total += food.getPrice() * food.getAmount(); + }); + return total; + } + getFoodArr() { + return this.#orderedFoodArr; + } + getTotalPrice() { + return this.#calculateTotalPrice(); + } + getTotalAmount() { + return this.#calculateTotalAmount(); + } + #throwError() { + throw new MenuError(); + } +} +export default Menu; diff --git a/src/OutputView.js b/src/OutputView.js deleted file mode 100644 index 5328108..0000000 --- a/src/OutputView.js +++ /dev/null @@ -1,7 +0,0 @@ -export default OutputView = { - printMenu() { - Console.print("<주문 메뉴>"); - // ... - } - // ... -} diff --git a/src/View/InputView.js b/src/View/InputView.js new file mode 100644 index 0000000..1fe6fb2 --- /dev/null +++ b/src/View/InputView.js @@ -0,0 +1,13 @@ +import { MissionUtils } from '@woowacourse/mission-utils'; + + +const InputView = class { + constructor() { + this.readData = this.readData; + } + async readData(message) { + return await MissionUtils.Console.readLineAsync(message); + } +}; + +export default InputView; diff --git a/src/View/OutputView.js b/src/View/OutputView.js new file mode 100644 index 0000000..30953c9 --- /dev/null +++ b/src/View/OutputView.js @@ -0,0 +1,64 @@ +import { MissionUtils } from '@woowacourse/mission-utils'; +import { UNIT, DISCOUNT_LIST, AMOUNT, DIVIDER_HYPHEN, NOTHING, BLANK } from '../constants.js'; +const OutputView = class { + constructor() { + this.print = this.print; + this.printMenu = this.printMenu; + this.printMoney = this.printMoney; + this.printAmount = this.printAmount; + this.printDiscountList = this.printDiscountList; + } + print(message) { + MissionUtils.Console.print(message); + } + printMenu(menuName, menuAmount, hyphen = BLANK) { + MissionUtils.Console.print(`${menuName}${hyphen}${menuAmount}${UNIT.GAE}`); + } + printMoney(number, hyphen = NOTHING) { + if (number === AMOUNT.ZERO) { + MissionUtils.Console.print(`${AMOUNT.ZERO}${UNIT.WON}`); + } + MissionUtils.Console.print(`${hyphen}${number.toLocaleString()}${UNIT.WON}`); + } + printAmount(number) { + MissionUtils.Console.print(`${number}${UNIT.GAE}`); + } + printDiscountList(discountList, date) { + if (discountList[0] === AMOUNT.ZERO && discountList[1] === AMOUNT.ZERO && discountList[2] === AMOUNT.ZERO) { + MissionUtils.Console.print(AMOUNT.NONE); + return; + } + const isWeekend = date.getIsWeekend(); + MissionUtils.Console.print( + `${DISCOUNT_LIST.D_DAY_EVENT}${discountList[0] === AMOUNT.ZERO ? NOTHING : DIVIDER_HYPHEN}${discountList[0]}${ + UNIT.WON + }` + ); + if (isWeekend) { + MissionUtils.Console.print( + `${DISCOUNT_LIST.WEEKEND_EVENT}${ + discountList[1] === AMOUNT.ZERO ? NOTHING : DIVIDER_HYPHEN + }${discountList[1].toLocaleString()}${UNIT.WON}` + ); + } + if (!isWeekend) { + MissionUtils.Console.print( + `${DISCOUNT_LIST.WEEKDAY_EVENT}${ + discountList[1] === AMOUNT.ZERO ? NOTHING : DIVIDER_HYPHEN + }${discountList[1].toLocaleString()}${UNIT.WON}` + ); + } + MissionUtils.Console.print( + `${DISCOUNT_LIST.STAR_EVENT}${ + discountList[2] === AMOUNT.ZERO ? NOTHING : DIVIDER_HYPHEN + }${discountList[2].toLocaleString()}${UNIT.WON}` + ); + MissionUtils.Console.print( + `${DISCOUNT_LIST.GIVEAWAY_EVENT}${ + discountList[3] === AMOUNT.ZERO ? NOTHING : DIVIDER_HYPHEN + }${discountList[3].toLocaleString()}${UNIT.WON}` + ); + } +}; + +export default OutputView; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..8fd1c3b --- /dev/null +++ b/src/constants.js @@ -0,0 +1,113 @@ +export const NOTHING = ''; +export const BLANK = ' ' +export const DIVIDER_COMMA = ','; +export const DIVIDER_HYPHEN = '-'; +export const MONTH = 12; +export const FIRST_DAY_OF_MONTH = 1; +export const LAST_DAY_OF_MONTH = 31; +export const CHRISTMAS_D_DAY = 25; +export const EVENT_MIN_PRICE = 10000; +export const D_DAY_EVENT_STARTING_PRICE = 1000; +export const D_DAY_EVENT_INCREASING_PRICE = 100; +export const STARRED_DAY_EVENT_DISCOUNT = 1000; +export const WEEK_EVENT_DISCOUNT = 2023; +export const GIVEAWAY_EVENT_MIN_PRICE = 120000; +export const MIN_AMOUNT = 1; +export const MAX_AMOUNT = 20; +export const COURSE = { + STARTER: '에피타이저', + MAIN: '메인', + DESSERT: '디저트', + DRINKS: '음료', +}; +export const MENU = { + MUSHROOM_SOUP: '양송이수프', + TAPAS: '타파스', + SALAD: '시저샐러드', + STEAK: '티본스테이크', + BBQ: '바비큐립', + SEAFOOD_PASTA: '해산물파스타', + X_MAS_PASTA: '크리스마스파스타', + CHOCOLATE_CAKE: '초코케이크', + ICE_CREAM: '아이스크림', + ZERO_COKE: '제로콜라', + RED_WINE: '레드와인', + CHAMPAGNE: '샴페인', +}; + +export const EVENT_DAYS = { + WEEKENDS: [1, 2, 8, 9, 15, 16, 23, 24, 30, 31], + STARRED_DAYS: [3, 10, 17, 24, 25, 31], +}; +export const UNIT = { + WON: '원', + GAE: '개', +}; +export const AMOUNT = { + NONE: '없음', + ZERO: 0, + ONE: 1, + TWO: 2, + THREE: 3, + FOUR: 4, + FIVE: 5, + SIX: 6, + SEVEN: 7, + EIGHT: 8, + NINE: 9, + TEN: 10, + ELEVEN: 11, + TWELVE: 12, + THIRTEEN: 13, + FOURTEEN: 14, + FIFTEEN: 15, + SIXTEEN: 16, + SEVENTEEN: 17, + EIGHTEEN: 18, + NINETEEN: 19, + TWENTY: 20, +}; + +export const EVENT_BADGE = { + STAR: '별', + TREE: '트리', + SANTA: '산타', + STAR_MIN: 5000, + TREE_MIN: 10000, + SANTA_MIN: 20000, +}; +export const GREETING = `안녕하세요! 우테코 식당 ${MONTH}월 이벤트 플래너입니다.`; +export const ASK = { + SCHEDULE: `${MONTH}월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)`, + MENU_INFO: `주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. ${MENU.SEAFOOD_PASTA}-${AMOUNT.TWO},${MENU.RED_WINE}-${AMOUNT.ONE},${MENU.CHOCOLATE_CAKE}-${AMOUNT.ONE})'`, +}; +export const SEE_MORE_ABOUT_EVENTS = (scheduledDay) => + `${MONTH}월 ${scheduledDay}일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!`; +export const HEADER = Object.freeze({ + MENU: '\n<주문 메뉴>', + TOTAL_PRICE_BEFORE_DISCOUNT: '\n<할인 전 총주문 금액>', + GIVEAWAY_MENU: '\n<증정 메뉴>', + BENEFITS_LIST: '\n<혜택 내역>', + TOTAL_BENEFITS_PRICE: '\n<총혜택 금액>', + TOTAL_PRICE_AFTER_DISCOUNT: '\n<할인 후 예상 결제 금액>', + EVENT_BADGE: `\n<${MONTH}월 이벤트 배지>`, +}); +export const DISCOUNT_LIST = { + D_DAY_EVENT: '크리스마스 디데이 할인: ', + WEEKDAY_EVENT: '평일 할인: ', + WEEKEND_EVENT: '주말 할인: ', + STAR_EVENT: '특별 할인: ', + GIVEAWAY_EVENT: `증정 이벤트: `, +}; +export const ERROR = { + name: { + DATE: '[DATE_ERROR]', + ORDER: '[ORDER_ERROR]', + }, + message: { + NOT_VALID_NUMBER: '[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.', + NOT_VALID_ORDER: '[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.', + }, +}; + +export const REG_EXP = /[가-힣]-\d/g; diff --git a/src/menus.js b/src/menus.js new file mode 100644 index 0000000..41cfa0c --- /dev/null +++ b/src/menus.js @@ -0,0 +1,88 @@ +const menusData = new Map([ + [ + '양송이수프', + { + course: '에피타이저', + price: 6000, + }, + ], + [ + '타파스', + { + course: '에피타이저', + price: 5500, + }, + ], + [ + '시저샐러드', + { + course: '에피타이저', + price: 8000, + }, + ], + [ + '티본스테이크', + { + course: '메인', + price: 55000, + }, + ], + [ + '바비큐립', + { + course: '메인', + price: 54000, + }, + ], + [ + '해산물파스타', + { + course: '메인', + price: 35000, + }, + ], + [ + '크리스마스파스타', + { + course: '메인', + price: 25000, + }, + ], + [ + '초코케이크', + { + course: '디저트', + price: 15000, + }, + ], + [ + '아이스크림', + { + course: '디저트', + price: 5000, + }, + ], + [ + '제로콜라', + { + course: '음료', + price: 3000, + }, + ], + [ + '레드와인', + { + course: '음료', + price: 60000, + }, + ], + [ + '샴페인', + { + course: '음료', + price: 25000, + }, + ], +]); + +export default menusData;