Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2단계 - 자주 가는 음식점] - 초코(강다빈) 미션 제출합니다. #176

Merged
merged 53 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
fe8d13f
docs: 기능 요구 사항 정리
llqqssttyy Mar 5, 2024
7316bf0
chore: 프로젝트 초기 설정
llqqssttyy Mar 5, 2024
8b5338a
feat: 카테고리, 이름, 거리(도보 이동 시간), 설명, 참고 링크를 저장한다.
llqqssttyy Mar 5, 2024
3a6fa2a
feat: 음식점 목록에 새로운 음식점을 추가
llqqssttyy Mar 5, 2024
bfad01e
feat: 음식점 목록을 확인
llqqssttyy Mar 5, 2024
e2e2c74
chore: typescript 모듈 테스트를 위한 jest 설정
llqqssttyy Mar 5, 2024
c774a31
test: RestaurantManager 기능 테스트
llqqssttyy Mar 5, 2024
d850a9a
chore: template 이미지 리소스 view로 복사
llqqssttyy Mar 5, 2024
01385ce
feat: 모달 열고 닫는 기능
llqqssttyy Mar 5, 2024
3196696
feat: 식당 등록 폼 커스텀 이벤트
llqqssttyy Mar 5, 2024
a9dd0f8
feat: Form 제출 시 localStorage에 저장
llqqssttyy Mar 5, 2024
e506495
chore: 정적 파일 import
llqqssttyy Mar 6, 2024
e725ba4
feat: Select 컴포넌트 제작
llqqssttyy Mar 6, 2024
119204a
feat: GNB를 웹 컴포넌트로 전환
llqqssttyy Mar 6, 2024
b8a5211
chore: Select 컴포넌트 위치 변경
llqqssttyy Mar 6, 2024
2fb67ab
feat: RestaurantForm를 웹 컴포넌트로 전환
llqqssttyy Mar 6, 2024
dd9e533
feat: Modal을 웹 컴포넌트 AppModal로 전환
llqqssttyy Mar 6, 2024
a0c8731
chore: AppModal의 이름을 Modal로 변경
llqqssttyy Mar 6, 2024
3d1b09d
chore: 레스토랑 필터 select 박스 배치
llqqssttyy Mar 6, 2024
e6d5e13
feat: localStorage와 도메인 연동
llqqssttyy Mar 7, 2024
209e3d6
feat: localStorage와 도메인 연동
llqqssttyy Mar 7, 2024
4d30ee4
feat: 음식점 렌더링 컴포넌트
llqqssttyy Mar 7, 2024
5061412
feat: 레스토랑 목록을 렌더링하는 컴포넌트
llqqssttyy Mar 7, 2024
9dd126b
feat: 식당 데이터를 웹 뷰에 렌더링
llqqssttyy Mar 7, 2024
dd1c2e4
test: RestaurantManger filteredAndSortedByOptions 테스트 케이스 추가
llqqssttyy Mar 7, 2024
ec59210
chore: eslint 적용에 따른 코드 수정
llqqssttyy Mar 8, 2024
bb00ff5
refactor: WebController 함수 분리
llqqssttyy Mar 8, 2024
1203cdf
docs: 기능 요구사항 문서 업데이트
llqqssttyy Mar 8, 2024
b5f3da4
fix: 폼이 제출되었을 때 input이 초기화되지 않는 버그 수정
llqqssttyy Mar 8, 2024
a1405e3
feat: 링크 유효성 검사
llqqssttyy Mar 8, 2024
d87f672
chore: console.log 제거
llqqssttyy Mar 8, 2024
db3b61e
docs: 실행 결과 gif
llqqssttyy Mar 8, 2024
5b2dd6e
refacotr: 오타 수정
00kang Mar 11, 2024
679fb2f
chore: webpack 재설치 업데이트
00kang Mar 11, 2024
5dc4cd0
refactor: filtering과 sorting 관련 수정
00kang Mar 11, 2024
e893a0c
refactor: CSS 파일 수정
00kang Mar 11, 2024
de2b044
refactor: 불필요한 async 삭제, 오타 수정
00kang Mar 11, 2024
c882101
refactor: innerHTML 대신 textContent 사용
00kang Mar 11, 2024
922b5b7
refactor: attributeChangedCallback 수정
00kang Mar 12, 2024
0d7a2eb
docs: step2 미션에 맞게 REQUIREMENTS.md 업데이트
00kang Mar 13, 2024
5691ed8
feat: 탭바, 즐겨찾기 아이콘 추가
00kang Mar 13, 2024
381c42e
feat: 음식점 목록에서 즐겨찾기 등록
00kang Mar 17, 2024
f7339f7
feat: 전체 음식점과 즐겨찾는 음식점 탭 구현
00kang Mar 18, 2024
3e34b4a
feat: 레스토랑 아이템 클릭시 디테일 모달 띄우
00kang Mar 18, 2024
d69d556
feat: 디테일 모달 버튼 기능 구현
00kang Mar 18, 2024
451a7d3
feat: 디테일 모달 내 링크 클릭시 이동
00kang Mar 18, 2024
180d1d0
test(e2e) : 탭 클릭으로 화면 전환 테스트
00kang Mar 18, 2024
bd85468
style: 레스토랑 디테일 모달 스타일링
00kang Mar 18, 2024
747321d
Merge branch '00kang' into step2
00kang Mar 18, 2024
03b036c
fix: 레스토랑 디테일 모달 내의 컨텐츠 로드 에러 해결
00kang Mar 18, 2024
7b560d4
Merge branch 'step2' of https://github.com/00kang/javascript-lunch in…
00kang Mar 18, 2024
06fc2d7
fix: } 추가
00kang Mar 18, 2024
7b94e0a
feat: 불필요한 코드 삭제
00kang Mar 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
"browser": true,
"es6": true,
"node": true,
"jest": true
"jest": true,
"cypress/globals": true

},
"plugins": ["cypress"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"extends": ["airbnb-base", "prettier"],
"extends": ["airbnb-base", "prettier", "eslint:recommended", "plugin:cypress/recommended"],
"ignorePatterns": ["dist"]
}
10 changes: 10 additions & 0 deletions cypress.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: 'http://localhost:8080/',
},
});
15 changes: 15 additions & 0 deletions cypress/e2e/cypress-test.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
describe('화면 전환 확인', () => {
beforeEach(() => {
cy.visit('/'); // 웹 페이지로 이동
});

it('메뉴 탭 클릭으로 화면 전환 확인', () => {
// '모든 음식점' 탭을 클릭하고 활성화 여부를 확인합니다.
cy.contains('모든 음식점').click();
cy.get('.tab__item.active').should('contain', '모든 음식점');

// '자주 가는 음식점' 탭을 클릭하고 활성화 여부를 확인합니다.
cy.contains('자주 가는 음식점').click();
cy.get('.tab__item.active').should('contain', '자주 가는 음식점');
});
});
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
37 changes: 37 additions & 0 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
20 changes: 20 additions & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
30 changes: 27 additions & 3 deletions docs/REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,45 @@
## 음식점

- [x] 음식점을 추가할 수 있다.

- 카테고리, 이름, 거리는 필수 정보다.
- 설명, 참고 링크는 옵션이다. 정보가 없어도 음식점 추가가 가능하다.
- 카테고리는 "한식", "중식", "일식", "아시안", "양식", "기타" 중 하나를 선택한다.
- 거리는 캠퍼스로부터 도보로 걸리는 시간(분). 5, 10, 15, 20, 30 중 하나를 선택한다.

- [ ] 음식점의 상세 정보를 확인할 수 있다.

- 음식점을 삭제할 수 있다.

- [ ] 자주 가는 음식점을 추가하고 목록으로 확인할 수 있다.

- 음식점 목록에서 추가할 수 있다.
- 음식점 상세 정보에서 추가할 수 있다.


## UI

- [x] 음식점 목록을 확인할 수 있다.
- 카테고리별로 필터링해서 확인할 수 있다.
- 이름순/거리순으로 정렬해서 확인할 수 있다.
- 추가된 음식점은 localStorage에 저장해 새로고침해도 추가한 음식점 정보들이 유지된다.

- [x] 음식점 추가 form

- 카테고리, 거리는 셀렉트 박스, 이름/설명/참고 링크는 텍스트 인풋을 사용한다.
- 유효하지 않은 링크가 입력되면 '유효하지 않은 링크입니다.'를 노출한다.

## Bug🐞
- [ ] 유효하지 않은 링크를 입력한 뒤 유효한 링크를 입력했을 때 정상제출 되지 않는 버그 해결
- [ ] 음식점의 상세 정보를 확인할 수 있다.
- 카테고리, 이름, 거리, 설명, 참고 링크를 확인할 수 있다.

## TEST

- [ ] 개발한 앱에서 핵심이 되는 기능이라고 생각하는 기능 플로우를 선정하고 그에 대한 E2E 테스트를 추가한다.

## 개선할 점

- [ ] connectedCallback에서 추가한 이벤트, disconnectedCallback에서 제거하기
- [ ] 입력 폼에 대한 각종 validator 추가하기
- [ ] 전체적인 코드 구조 수정하기

- [ ] 유효하지 않은 링크를 입력한 뒤 유효한 링크를 입력했을 때 정상제출 되지 않는 버그 해결
- [ ] 음식점 아이템 추가 후 다시 폼 진입 시 scroll이 최상단으로 가지 않는 문제 해결
63 changes: 57 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 수정하신 이유가 있으실까요?

<html lang="ko">
<head>
<meta charset="UTF-8" />
Expand Down Expand Up @@ -95,7 +95,6 @@ <h1 class="gnb__title text-title">점심 뭐 먹지</h1>
position: fixed;
bottom: 0;
width: 100%;
height: 90%;

padding: 32px 16px;
box-sizing: border-box;
Expand All @@ -106,7 +105,7 @@ <h1 class="gnb__title text-title">점심 뭐 먹지</h1>
</style>
<div class="modal">
<div class="modal-backdrop"></div>
<div class="modal-container">
<div class="modal-container" id="modal-container">
<slot name="title"></slot>
<slot name="body"></slot>
</div>
Expand All @@ -118,15 +117,62 @@ <h1 class="gnb__title text-title">점심 뭐 먹지</h1>
<img src="" alt="" class="category-icon" />
</div>
<div class="restaurant__info">
<h3 class="restaurant__name text-subtitle"></h3>
<span class="restaurant__distance text-body"></span>
<div class="restaurant__header">
<div>
<h3 class="restaurant__name text-subtitle"></h3>
<span class="restaurant__distance text-body"></span>
</div>
<button type="button" class="restaurant__favorite-button" aria-label="즐겨찾기">
<img src="" alt="즐겨찾기" />
</button>
</div>
<p class="restaurant__description text-body"></p>
</div>
</template>

<template id="template-restaurant-detail">
<div class="detail-container">
<!-- 레스토랑 정보 -->
<div class="restaurant-detail__header">
<div class="restaurant-detail__category">
<img src="" alt="" class="category-icon" />
</div>
<button type="button" class="restaurant-detail__favorite-button" aria-label="즐겨찾기">
<img src="./favorite-icon-lined.png" alt="즐겨찾기" />
</button>
</div>

<div class="restaurant-detail__body">
<h1 class="restaurant-detail__name text-title"></h1>
<span class="restaurant-detail__distance text-subtitle"></span>
<p class="restaurant-detail__description text-body"></p>
<a class="restaurant-detail__link text-body"></a>
</div>

<!-- 삭제 / 닫기 버튼 -->
<div class="button-container">
<button type="button" class="button button--secondary text-caption">삭제하기</button>
<button type="button" class="button button--primary text-caption" id="close">닫기</button>
</div>
</div>
</template>

<!-- GNB -->
<app-gnb></app-gnb>

<!-- 메뉴 탭 바 -->
<section>
<ul class="tab text-title">
<li class="tab__item active">
<a href="#tab1">모든 음식점</a>
</li>
<li class="tab__item">
<a href="#tab2">자주 가는 음식점</a>
</li>
</ul>
</section>

<!-- 정렬 -->
<section class="restaurant-filter-container">
<select
is="app-select"
Expand All @@ -152,9 +198,14 @@ <h3 class="restaurant__name text-subtitle"></h3>
</section>

<!-- 음식점 추가 모달 -->
<app-modal>
<app-modal id="addModal" height="90%">
<h2 slot="title" class="modal-title text-title">새로운 음식점</h2>
<form is="app-restaurant-form" slot="body" id="restaurant-form"></form>
</app-modal>

<!-- 음식점 상세 정보 모달 -->
<app-modal id="detailModal" height="75%">
<app-restaurant-detail slot="body" id="restaurant-detail"></app-restaurant-detail>
</app-modal>
</body>
</html>
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@babel/preset-typescript": "^7.18.6",
"babel-jest": "^29.7.0",
"css-loader": "^6.7.3",
"cypress": "^13.6.6",
"cypress": "^13.7.0",
"eslint": "^8.34.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-base": "^15.0.0",
Expand Down
20 changes: 20 additions & 0 deletions src/domain/RestaurantManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface Restaurant {
distance: 5 | 10 | 15 | 20 | 30;
description?: string;
link?: string;
favorite?: boolean;
}

class RestaurantManager {
Expand Down Expand Up @@ -46,6 +47,25 @@ class RestaurantManager {
#sortByDistance(restaurants: Restaurant[]): Restaurant[] {
return [...restaurants].sort((a, b) => a.distance - b.distance);
}

toggleFavorite(restaurantName: string): void {
const restaurantIndex = this.#restaurants.findIndex((restaurant) => restaurant.name === restaurantName);
if (restaurantIndex !== -1) {
this.#restaurants[restaurantIndex].favorite = !this.#restaurants[restaurantIndex].favorite;
this.updateLocalStorage();
}
}

updateLocalStorage(): void {
localStorage.setItem('restaurants', JSON.stringify(this.#restaurants));
}

loadRestaurantsFromLocalStorage() {
const storedRestaurants = localStorage.getItem('restaurants');
if (storedRestaurants) {
this.#restaurants = JSON.parse(storedRestaurants);
}
}
}

export default RestaurantManager;
9 changes: 9 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Select from './view/components/Select';
import RestaurantForm from './view/components/RestaurantForm';
import RestaurantList from './view/components/RestaurantList';
import RestaurantItem from './view/components/RestaurantItem';
import RestaurantDetail from './view/components/RestaurantDetail';

// styles
import './view/styles/global.css';
Expand All @@ -14,6 +15,9 @@ import './view/styles/Modal.css';
import './view/styles/Select.css';
import './view/styles/RestaurantForm.css';
import './view/styles/RestaurantList.css';
import './view/styles/Tab.css';
import './view/styles/RestaurantDetail.css';


// imgs
import './view/imgs/add-button.png';
Expand All @@ -23,13 +27,18 @@ import './view/imgs/category-chinese.png';
import './view/imgs/category-japanese.png';
import './view/imgs/category-western.png';
import './view/imgs/category-etc.png';
import './view/imgs/favorite-icon-filled.png';
import './view/imgs/favorite-icon-lined.png';


window.customElements.define('app-gnb', GNB);
window.customElements.define('app-modal', Modal);
window.customElements.define('app-select', Select, { extends: 'select' });
window.customElements.define('app-restaurant-form', RestaurantForm, { extends: 'form' });
window.customElements.define('app-restaurant-item', RestaurantItem, { extends: 'li' });
window.customElements.define('app-restaurant-list', RestaurantList, { extends: 'ul' });
window.customElements.define('app-restaurant-detail', RestaurantDetail);


const controller = new WebController();
controller.start();
Loading