diff --git a/README.md b/README.md
index e97a1d649..3f72aab87 100644
--- a/README.md
+++ b/README.md
@@ -3,20 +3,23 @@
## π κΈ°λ₯ μꡬμ¬ν
### μ§νμ² μ κ΄λ ¨ κΈ°λ₯
+
- μ§νμ² μμ λ±λ‘νκ³ μμ ν μ μλ€. (λ¨, λ
Έμ μ λ±λ‘λ μμ μμ ν μ μλ€)
- μ€λ³΅λ μ§νμ² μ μ΄λ¦μ΄ λ±λ‘λ μ μλ€.
- μ§νμ² μμ 2κΈμ μ΄μμ΄μ΄μΌ νλ€.
- μ§νμ² μμ λͺ©λ‘μ μ‘°νν μ μλ€.
### μ§νμ² λ
Έμ κ΄λ ¨ κΈ°λ₯
+
- μ§νμ² λ
Έμ μ λ±λ‘νκ³ μμ ν μ μλ€.
- μ€λ³΅λ μ§νμ² λ
Έμ μ΄λ¦μ΄ λ±λ‘λ μ μλ€.
- λ
Έμ λ±λ‘ μ μν μ’
μ μκ³Ό νν μ’
μ μμ μ
λ ₯λ°λλ€.
- μ§νμ² λ
Έμ μ λͺ©λ‘μ μ‘°νν μ μλ€.
### μ§νμ² κ΅¬κ° μΆκ° κΈ°λ₯
+
- μ§νμ² λ
Έμ μ ꡬκ°μ μΆκ°νλ κΈ°λ₯μ λ
Έμ μ μμ μΆκ°νλ κΈ°λ₯μ΄λΌκ³ λ ν μ μλ€.
- - μκ³Ό μμ¬μ΄λ₯Ό ꡬκ°μ΄λΌ νκ³ μ΄ κ΅¬κ°λ€μ λͺ¨μμ΄ λ
Έμ μ΄λ€.
+ - μκ³Ό μμ¬μ΄λ₯Ό ꡬκ°μ΄λΌ νκ³ μ΄ κ΅¬κ°λ€μ λͺ¨μμ΄ λ
Έμ μ΄λ€.
- νλμ μμ μ¬λ¬κ°μ λ
Έμ μ μΆκ°λ μ μλ€.
- μκ³Ό μ μ¬μ΄μ μλ‘μ΄ μμ΄ μΆκ° λ μ μλ€.
- λ
Έμ μμ κ°λκΈΈμ μκΈΈ μ μλ€.
@@ -24,6 +27,7 @@
### μ§νμ² κ΅¬κ° μμ κΈ°λ₯
+
- λ
Έμ μ λ±λ‘λ μμ μ κ±°ν μ μλ€.
- μ’
μ μ μ κ±°ν κ²½μ° λ€μ μμ΄ μ’
μ μ΄ λλ€.
- λ
Έμ μ ν¬ν¨λ μμ΄ λκ° μ΄νμΌ λλ μμ μ κ±°ν μ μλ€.
@@ -31,6 +35,7 @@
### μ§νμ² λ
Έμ μ λ±λ‘λ μ μ‘°ν κΈ°λ₯
+
- λ
Έμ μ μν μ’
μ λΆν° νν μ’
μ κΉμ§ μ°κ²°λ μμλλ‘ μ λͺ©λ‘μ μ‘°νν μ μλ€.
@@ -38,32 +43,38 @@
## π» νλ‘κ·Έλ¨ μ€ν κ²°κ³Ό
### μκ΄λ¦¬
+
### λ
Έμ κ΄λ¦¬
+
### ꡬκ°κ΄λ¦¬
+
### λ
Έμ λ μΆλ ₯
-
+
## β
νλ‘κ·Έλλ° μꡬμ¬ν
### λ©λ΄ λ²νΌ
+
- μ κ΄λ¦¬ button νκ·Έλ `#station-manager-button` idκ°μ κ°μ§λ€.
- λ
Έμ κ΄λ¦¬ button νκ·Έλ `#line-manager-button` idκ°μ κ°μ§λ€.
- κ΅¬κ° κ΄λ¦¬ button νκ·Έλ `#section-manager-button` idκ°μ κ°μ§λ€.
- μ§νμ² λ
Έμ λ μΆλ ₯ κ΄λ¦¬ button νκ·Έλ `#map-print-manager-button` idκ°μ κ°μ§λ€.
### μ§νμ² μ κ΄λ ¨ κΈ°λ₯
+
- μ§νμ² μμ μ
λ ₯νλ input νκ·Έλ `#station-name-input` idκ°μ κ°μ§λ€.
- μ§νμ² μμ μΆκ°νλ button νκ·Έλ `#station-add-button` idκ°μ κ°μ§λ€.
- μ§νμ² μμ μμ νλ button νκ·Έλ `.station-delete-button` classκ°μ κ°μ§λ€.
### μ§νμ² λ
Έμ κ΄λ ¨ κΈ°λ₯
+
- μ§νμ² λ
Έμ μ μ΄λ¦μ μ
λ ₯νλ input νκ·Έλ `#line-name-input` idκ°μ κ°μ§λ€.
- μ§νμ² λ
Έμ μ μν μ’
μ μ μ ννλ select νκ·Έλ `#line-start-station-selector` idκ°μ κ°μ§λ€.
- μ§νμ² λ
Έμ μ νν μ’
μ μ μ ννλ select νκ·Έλ `#line-end-station-selector` idκ°μ κ°μ§λ€.
@@ -71,6 +82,7 @@
- μ§νμ² λ
Έμ μ μμ νλ button νκ·Έλ `.line-delete-button` classκ°μ κ°μ§λ€.
### μ§νμ² κ΅¬κ° μΆκ° κΈ°λ₯
+
- μ§νμ² λ
Έμ μ μ ννλ button νκ·Έλ `.section-line-menu-button` classκ°μ κ°μ§λ€.
- μ§νμ² κ΅¬κ°μ μ€μ ν μ select νκ·Έλ `#section-station-selector` idκ°μ κ°μ§λ€.
- μ§νμ² κ΅¬κ°μ μμλ₯Ό μ
λ ₯νλ input νκ·Έλ `#section-order-input` idκ°μ κ°μ§λ€.
@@ -78,6 +90,7 @@
- μ§νμ² κ΅¬κ°μ μ κ±°νλ button νκ·Έλ `.section-delete-button` classκ°μ κ°μ§λ€.
### μ§νμ² λ
Έμ λ μΆλ ₯ κΈ°λ₯
+
- μ§νμ² λ
Έμ λ μΆλ ₯ λ²νΌμ λλ₯΄λ©΄ `
` νκ·Έλ₯Ό λ§λ€κ³ ν΄λΉ νκ·Έ λ΄λΆμ λ
Έμ λλ₯Ό μΆλ ₯νλ€.
### κΈ°μ‘΄ μꡬμ¬ν
@@ -101,7 +114,8 @@
- [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Template_literals)
### μΆκ°λ μꡬμ¬ν
-- [data](https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%86%8D%EC%84%B1_%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0)μμ±μ νμ©νμ¬ html νκ·Έμ μ, λ
Έμ , ꡬκ°μ μ μΌν λ°μ΄ν° κ°λ€μ κ΄λ¦¬νλ€.
+
+- [data](https://developer.mozilla.org/ko/docs/Learn/HTML/Howto/%EB%8D%B0%EC%9D%B4%ED%84%B0_%EC%86%8D%EC%84%B1_%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0)μμ±μ νμ©νμ¬ html νκ·Έμ μ, λ
Έμ , ꡬκ°μ μ μΌν λ°μ΄ν° κ°λ€μ κ΄λ¦¬νλ€.
- [localStorage](https://developer.mozilla.org/ko/docs/Web/API/Window/localStorage)λ₯Ό μ΄μ©νμ¬, μλ‘κ³ μΉ¨νλλΌλ κ°μ₯ μ΅κ·Όμ μμ
ν μ 보λ€μ λΆλ¬μ¬ μ μλλ‘ νλ€.
@@ -112,3 +126,56 @@
- **κΈ°λ₯μ ꡬννκΈ° μ μ javascript-subway-precourse/docs/README.md νμΌμ ꡬνν κΈ°λ₯ λͺ©λ‘**μ μ λ¦¬ν΄ μΆκ°νλ€.
- **gitμ commit λ¨μλ μ λ¨κ³μμ README.md νμΌμ μ 리ν κΈ°λ₯ λͺ©λ‘ λ¨μλ‘ μΆκ°**νλ€.
- [ν리μ½μ€ κ³Όμ μ μΆ](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) λ¬Έμ μ μ°¨λ₯Ό λ°λΌ λ―Έμ
μ μ μΆνλ€.
+
+## π₯ κΈ°λ₯ λͺ©λ‘
+
+- κ³΅ν΅ κΈ°λ₯
+
+ - [x] κ° λ©λ΄λ₯Ό ν΄λ¦μ ν΄λΉ κΈ°λ₯μ μνν μ μκ²ν΄μΌ νλ€. (μ κ΄λ¦¬, λ
Έμ κ΄λ¦¬, κ΅¬κ° κ΄λ¦¬, μ§νμ² λ
Έμ λ μΆλ ₯)
+ - [x] dataλ₯Ό 곡μ ν μ μλλ‘ κ° λ©λ΄μμ dataλ₯Ό λκΈ°ν μν¨λ€.
+ - [x] localStorageλ₯Ό μ΄μ©νμ¬ μλ‘κ³ μΉ¨ν΄λ μ΅κ·Ό λ°μ΄ν°λ₯Ό νμ©ν΄ λͺ©λ‘λ€μ νμνλ€.
+ - [x] form μ μΆ μ inputμ μ΄κΈ°ν νλ€.
+ - [x] μ¬λ°λ₯΄ μμ μ
λ ₯μΌ κ²½μ° κ²½κ³ μ°½μ νμνμ¬ μ¬μ
λ ₯ ν μ μκ²νλ€.
+
+- μ κ΄λ ¨ κΈ°λ₯
+
+ - [x] μ¬μ©μκ° μ μ΄λ¦μ μΆκ°ν μ μκ²ν΄μΌ νλ€.
+ - [x] μ μ΄λ¦μ΄ μ€λ³΅λλμ§ νμΈν΄μΌνλ€.
+ - [x] μ μ΄λ¦μ΄ 2κΈμ μ΄μμΈμ§ νμΈν΄μΌ νλ€. (λ―Έλ§μΌ κ²½μ° κ²½κ³ μ°½ νμ)
+ - [x] μ¬μ©μκ° μ
λ ₯ν μλ€μ μ°¨λ‘λλ‘ μΆλ ₯ν΄μΌ νλ€.
+ - [x] μμ λ²νΌμ ν΄λ¦μ μμ ν μ μκ² ν΄μΌνλ€.
+ - [x] μμ μ μ±κ³΅νμ κ²½μ° λͺ©λ‘μ μ
λ°μ΄νΈν΄μ λ€μ νμν΄μΌ νλ€.
+ - [x] μμ μ μ¬μ©μμ νμΈμ λ°λλ€
+ - [x] μμ μ λ
Έμ μ μΆκ°λμ΄ μμ κ²½μ° μμ ν μ μκ² ν΄μΌ νλ€.
+
+- λ
Έμ κ΄λ ¨ κΈ°λ₯
+
+ - [x] λ
Έμ μ΄λ¦μ λ±λ‘ ν μ μκ² νλ€.
+ - [x] μν μ’
μ νν μ’
μ μ λ±λ‘ν μ λͺ©λ‘μμ μ νν μ μκ² ν΄μΌνλ€.
+ - [x] λ
Έμ μ΄λ¦μ΄ μ€λ³΅λλμ§ νμΈν΄μΌνλ€.
+ - [x] μν μ’
μ κ³Ό νν μ’
μ μ΄ κ°μμ§ νμΈν΄μΌ νλ€.
+ - [x] λ
Έμ λͺ©λ‘μ μΆλ ₯νλ€.
+ - [x] μμ λ²νΌ ν΄λ¦ μ μμ ν μ μκ² ν΄μΌνλ€.
+ - [x] μμ μ μ±κ³΅νμ κ²½μ° λͺ©λ‘μ μ
λ°μ΄νΈ ν΄μ λ€μ νμν΄μΌ νλ€.
+ - [x] μμ μ μ¬μ©μμ νμΈμ λ°λλ€.
+
+- κ΅¬κ° κ΄λ ¨ κΈ°λ₯
+
+ - [x] μ¬μ©μκ° λ±λ‘ν λ
Έμ μ νμν΄μΌνλ€.
+ - [x] λ
Έμ μ ν΄λ¦ μ κ·Έ λ
Έμ μ μμ ν μ μκ² ν΄μΌνλ€.
+ - [x] μ¬μ©μκ° μ
λ ₯ν μλ€μ μμμ λ°λΌ μΆκ° ν μ μλ€.
+ - [x] μλ‘μ΄ κ΅¬κ° λ±λ‘μ λ
Έμ μ μνμ’
μ κ³Ό ννμ’
μ μ μ
λ°μ΄νΈ νλ€.
+ - [x] κ΅¬κ° λ±λ‘μ λ
Έμ μ μ΄λ―Έ ν¬ν¨λ μμΈμ§ νμΈνλ€.
+ - [x] 'λ
Έμ μμ μ κ±°' λ²νΌ ν΄λ¦μ λ
Έμ μμ μ κ±° ν μ μκ² ν΄μΌνλ€.
+ - [x] μμ μ λ
Έμ μ ν¬ν¨λ μμ΄ λκ° μ΄νμΈμ§ νμΈν΄μΌνλ€.
+ - [x] μμ μ μ±κ³΅νμ κ²½μ° λͺ©λ‘μ μ
λ°μ΄νΈ ν΄μ λ€μ νμν΄μΌ νλ€.
+
+- λ
Έμ λ μΆλ ₯ κΈ°λ₯
+
+ - [x] λ
Έμ μ μνμ’
μ λΆν° ννμ’
μ κΉμ§ μμλλ‘ λͺ©λ‘μ νμν΄μΌ νλ€.
+
+- μμΈμ¬ν
+ - μ
λ ₯μ λͺ¨λ 곡백μ μ μΈνλ€.
+ - λ
Έμ μ κ΅¬κ° μΆκ° μ νμ¬μ κΈΈμ΄λ³΄λ€ λ λμ μ«μλ₯Ό μ
λ ₯ν κ²½μ° λ§μ§λ§μ μΆκ°λλ€.
+
+
diff --git a/index.html b/index.html
index fc99deac2..253c80c62 100644
--- a/index.html
+++ b/index.html
@@ -3,10 +3,108 @@
μ§νμ² λ
Έμ λ κ΄λ¦¬
+
-
π μ§νμ² λ
Έμ λ κ΄λ¦¬
+
π μ§νμ² λ
Έμ λ κ΄λ¦¬
+
+
+
+
+ π μ§νμ² μ λͺ©λ‘
+
+
+
+ | μ μ΄λ¦ |
+ μ€μ |
+
+
+
+
+
+
+
+
+
+ π μ§νμ² λ
Έμ λͺ©λ‘
+
+
+
+ | λ
Έμ μ΄λ¦ |
+ μν μ’
μ μ |
+ νν μ’
μ μ |
+ μ€μ |
+
+
+
+
+
+
+
+
+
ꡬκ°μ μμ ν λ
Έμ μ μ νν΄ μ£ΌμΈμ.
+
+
+
+
+ π μ§νμ² λ
Έμ λͺ©λ‘
+
+
+
+ | μμ |
+ μ΄λ¦ |
+ μ€μ |
+
+
+
+
+
+
+
+
+
diff --git a/src/factory/Component.js b/src/factory/Component.js
new file mode 100644
index 000000000..b7e84eb9e
--- /dev/null
+++ b/src/factory/Component.js
@@ -0,0 +1,45 @@
+export default class Component {
+ constructor(props = {}) {
+ this.data = {};
+ this.props = props;
+ this.managerId = props.managerId;
+ this.syncData = props.syncData;
+ this.container = document.querySelector(`#${props.containerId}`);
+ }
+
+ setData(nextData) {
+ this.data = {
+ ...this.data,
+ ...nextData,
+ };
+ this.render?.();
+ }
+
+ clearInput() {
+ this.userInput.value = '';
+ }
+
+ addToTable(value, keyName) {
+ const newData = { ...this.data };
+ newData[keyName].push(value);
+ this.syncData(newData);
+ }
+
+ deleteFromTable(index, keyName) {
+ const newData = { ...this.data };
+ newData[keyName].splice(index, 1);
+ this.syncData(newData);
+ }
+
+ updateTable(template) {
+ this.table.innerHTML = template;
+ }
+
+ show() {
+ this.container.style.display = 'block';
+ }
+
+ hide() {
+ this.container.style.display = 'none';
+ }
+}
diff --git a/src/factory/Line.js b/src/factory/Line.js
new file mode 100644
index 000000000..e151d227b
--- /dev/null
+++ b/src/factory/Line.js
@@ -0,0 +1,35 @@
+const MIN_SECTION_LENGTH = 2;
+
+export default class Line {
+ constructor(props) {
+ this.props = props;
+ this.name = props.name;
+ this.startStation = props.startStation;
+ this.endStation = props.endStation;
+ this.section = this.setSection();
+ }
+
+ setSection = () => this.props.section || [this.startStation, this.endStation];
+
+ addStationToSection({ stationName, index }) {
+ this.section.splice(index, 0, stationName);
+ this.updateStartStation();
+ this.updateEndStation();
+ }
+
+ deleteStationFromSection({ index }) {
+ this.section.splice(index, 1);
+ this.updateStartStation();
+ this.updateEndStation();
+ }
+
+ checkSectionLength = () => this.section.length === MIN_SECTION_LENGTH;
+
+ updateStartStation() {
+ [this.startStation] = this.section;
+ }
+
+ updateEndStation() {
+ this.endStation = this.section[this.section.length - 1];
+ }
+}
diff --git a/src/index.js b/src/index.js
index e69de29bb..15a4760fc 100644
--- a/src/index.js
+++ b/src/index.js
@@ -0,0 +1,5 @@
+import SubwayManager from './managers/app.js';
+
+const app = new SubwayManager();
+
+app.init();
diff --git a/src/managers/LineManager.js b/src/managers/LineManager.js
new file mode 100644
index 000000000..44e49518d
--- /dev/null
+++ b/src/managers/LineManager.js
@@ -0,0 +1,98 @@
+import Component from '../factory/Component.js';
+import Line from '../factory/Line.js';
+import { LINE_SELECTOR } from '../share/selector.js';
+import {
+ isOverlap,
+ isSameStation,
+ deleteWhiteSpace,
+ isEmpty,
+} from '../share/utils.js';
+import { lineTableTemplate, optionTemplate } from '../share/template.js';
+import { DATA_KEY, LINE_WORDS } from '../share/words.js';
+
+export default class LineManager extends Component {
+ constructor(props) {
+ super(props);
+
+ this.form = this.container.querySelector(`#${LINE_SELECTOR.FORM_ID}`);
+ this.userInput = this.container.querySelector(
+ `#${LINE_SELECTOR.NAME_INPUT_ID}`,
+ );
+ this.startStationSelector = this.container.querySelector(
+ `#${LINE_SELECTOR.START_STATION_SELECTOR_ID}`,
+ );
+ this.endStationSelector = this.container.querySelector(
+ `#${LINE_SELECTOR.END_STATION_SELECTOR}`,
+ );
+ this.table = this.container.querySelector(`#${LINE_SELECTOR.TABLE_BODY}`);
+
+ this.form.addEventListener('submit', this.onSubmit);
+ this.table.addEventListener('click', this.onTableClick);
+ }
+
+ onSubmit = (event) => {
+ event.preventDefault();
+ const constructorObj = this.getValues();
+ const newLine = new Line(constructorObj);
+ if (!this.isValid(newLine)) return;
+ this.addToTable(newLine, DATA_KEY.LINE_LIST);
+ this.clearInput();
+ };
+
+ onTableClick = (event) => {
+ const { className } = event.target;
+ const { index } = event.target.dataset;
+ if (className !== LINE_SELECTOR.DELETE_BUTTON_CLASS) return;
+ if (!confirm(LINE_WORDS.CONFIRM_MESSAGE)) return;
+ this.deleteFromTable(index, DATA_KEY.LINE_LIST);
+ };
+
+ isValid({ name, startStation, endStation }) {
+ if (isEmpty(name)) {
+ alert(LINE_WORDS.ALERT_MESSAGE_NO_WHITESPACE);
+ return false;
+ }
+ if (isOverlap(name, this.getAllLineNames())) {
+ alert(LINE_WORDS.ALERT_MESSAGE_SAME_NAME);
+ return false;
+ }
+ if (isSameStation(startStation, endStation)) {
+ alert(LINE_WORDS.ALERT_MESSAGE_SAME_STATION);
+ return false;
+ }
+ return true;
+ }
+
+ getAllLineNames = () => this.data.lineList.map((line) => line.name);
+
+ getValues = () => {
+ const name = deleteWhiteSpace(this.userInput.value);
+ const { value: startStation } = this.startStationSelector;
+ const { value: endStation } = this.endStationSelector;
+ return { name, startStation, endStation };
+ };
+
+ updateStationList() {
+ this.startStationSelector.innerHTML = this.data.stationList
+ .map((station) => optionTemplate(station))
+ .join('');
+ this.endStationSelector.innerHTML = this.data.stationList
+ .map((station) => optionTemplate(station))
+ .join('');
+ }
+
+ template() {
+ return this.data.lineList
+ .map((line, index) => lineTableTemplate({
+ ...line,
+ index,
+ buttonClass: LINE_SELECTOR.DELETE_BUTTON_CLASS,
+ }))
+ .join('');
+ }
+
+ render() {
+ this.updateStationList();
+ this.updateTable(this.template());
+ }
+}
diff --git a/src/managers/MapPrintManager.js b/src/managers/MapPrintManager.js
new file mode 100644
index 000000000..77b509eea
--- /dev/null
+++ b/src/managers/MapPrintManager.js
@@ -0,0 +1,22 @@
+import Component from '../factory/Component.js';
+import { MAP_SELECTOR } from '../share/selector.js';
+import { mapTemplate } from '../share/template.js';
+
+export default class MapPrintManager extends Component {
+ constructor(props) {
+ super(props);
+
+ this.map = document.createElement('div');
+ this.map.setAttribute('class', MAP_SELECTOR.MAP_CLASS);
+
+ this.container.appendChild(this.map);
+ }
+
+ updateTemplate() {
+ this.map.innerHTML = mapTemplate(this.data.lineList);
+ }
+
+ render() {
+ this.updateTemplate();
+ }
+}
diff --git a/src/managers/SectionDetailManager.js b/src/managers/SectionDetailManager.js
new file mode 100644
index 000000000..ff9a1c5bc
--- /dev/null
+++ b/src/managers/SectionDetailManager.js
@@ -0,0 +1,100 @@
+import Component from '../factory/Component.js';
+import { SECTION_SELECTOR } from '../share/selector.js';
+import {
+ optionTemplate,
+ sectionDetailTableTemplate,
+} from '../share/template.js';
+import { isOverlap, isEmpty } from '../share/utils.js';
+import { SECTION_DETAIL_WORDS } from '../share/words.js';
+
+export default class SectionDetailManager extends Component {
+ constructor(props) {
+ super(props);
+
+ this.form = this.container.querySelector(`#${SECTION_SELECTOR.FORM_ID}`);
+ this.formHeader = this.container.querySelector(
+ `#${SECTION_SELECTOR.FORM_HEADER_ID}`,
+ );
+ this.stationSelector = this.container.querySelector(
+ `#${SECTION_SELECTOR.STATION_SELECTOR_ID}`,
+ );
+ this.userInput = this.container.querySelector(
+ `#${SECTION_SELECTOR.ORDER_INPUT_ID}`,
+ );
+ this.table = this.container.querySelector(
+ `#${SECTION_SELECTOR.TABLE_BODY}`,
+ );
+
+ this.form.addEventListener('submit', this.onSubmit);
+ this.table.addEventListener('click', this.onTableClick);
+ }
+
+ onSubmit = (event) => {
+ event.preventDefault();
+ const targetLine = this.data.currentLineData;
+ if (!this.isValid(targetLine)) return;
+ targetLine.addStationToSection(this.getValues());
+ this.clearInput();
+ this.syncData(this.data);
+ };
+
+ onTableClick = (event) => {
+ const { className } = event.target;
+ const { index } = event.target.dataset;
+ const targetLine = this.data.currentLineData;
+ if (className !== SECTION_SELECTOR.DELETE_BUTTON_CLASS) return;
+ if (!confirm(SECTION_DETAIL_WORDS.CONFIRM_MESSAGE)) return;
+ if (targetLine.checkSectionLength()) {
+ alert(SECTION_DETAIL_WORDS.ALERT_MESSAGE_SECTION_MINLENGTH);
+ return;
+ }
+ targetLine.deleteStationFromSection({ index });
+ this.syncData(this.data);
+ };
+
+ isValid(targetLine) {
+ const { stationName, index } = this.getValues();
+ if (isEmpty(index)) {
+ alert(SECTION_DETAIL_WORDS.ALERT_MESSAGE_NO_INDEX);
+ return false;
+ }
+ if (isOverlap(stationName, targetLine.section)) {
+ alert(SECTION_DETAIL_WORDS.ALERT_MESSAGE_SECTION_INCLUDES_STATION);
+ return false;
+ }
+ return true;
+ }
+
+ getValues() {
+ const { value: stationName } = this.stationSelector;
+ const { value: index } = this.userInput;
+ return { stationName, index };
+ }
+
+ updateOptions() {
+ this.stationSelector.innerHTML = this.data.stationList
+ .map((station) => optionTemplate(station))
+ .join('');
+ }
+
+ updateFormHeader() {
+ const { name } = this.data.currentLineData;
+ this.formHeader.innerHTML = `${name} κ΄λ¦¬`;
+ }
+
+ template() {
+ return this.data.currentLineData.section
+ .map((station, index) => sectionDetailTableTemplate({
+ name: station,
+ index,
+ buttonClass: SECTION_SELECTOR.DELETE_BUTTON_CLASS,
+ }))
+ .join('');
+ }
+
+ render() {
+ this.updateOptions();
+ this.updateFormHeader();
+ this.updateTable(this.template());
+ }
+}
diff --git a/src/managers/SectionManager.js b/src/managers/SectionManager.js
new file mode 100644
index 000000000..efe9d44ac
--- /dev/null
+++ b/src/managers/SectionManager.js
@@ -0,0 +1,43 @@
+import Component from '../factory/Component.js';
+import SectionDetailManager from './SectionDetailManager.js';
+import { SECTION_SELECTOR } from '../share/selector.js';
+
+export default class SectionManager extends Component {
+ constructor(props) {
+ super(props);
+
+ this.sectionLineMenu = this.container.querySelector(
+ `#${SECTION_SELECTOR.LINE_MENU_ID}`,
+ );
+ this.sectionDetailManager = new SectionDetailManager({
+ containerId: SECTION_SELECTOR.DETAIL_CONTAINER_ID,
+ syncData: this.syncData,
+ });
+
+ this.sectionLineMenu.addEventListener('click', this.changeSectionDetailManger);
+ }
+
+ changeSectionDetailManger = (event) => {
+ if (event.target.nodeName !== 'BUTTON') return;
+ const { name: selectedLineName } = event.target.dataset;
+ const newData = {
+ ...this.data,
+ currentLineData:
+ this.getSelectedLine(selectedLineName),
+ };
+ this.syncData(newData);
+ this.sectionDetailManager.show();
+ };
+
+ getSelectedLine = (name) => this.data.lineList.find((line) => line.name === name);
+
+ updateMenuList() {
+ this.sectionLineMenu.innerHTML = this.data.lineList
+ .map((line) => ``)
+ .join('');
+ }
+
+ render() {
+ this.updateMenuList();
+ }
+}
diff --git a/src/managers/StationManager.js b/src/managers/StationManager.js
new file mode 100644
index 000000000..7fff3e87a
--- /dev/null
+++ b/src/managers/StationManager.js
@@ -0,0 +1,79 @@
+import Component from '../factory/Component.js';
+import { STATION_SELECTOR } from '../share/selector.js';
+import {
+ isOverlap,
+ isValidLength,
+ deleteWhiteSpace,
+} from '../share/utils.js';
+import { stationTableTemplate } from '../share/template.js';
+import { DATA_KEY, STATION_WORDS } from '../share/words.js';
+
+const MIN_STATION_NAME_LENGTH = 2;
+
+export default class StationManager extends Component {
+ constructor(props) {
+ super(props);
+
+ this.userInput = this.container.querySelector(
+ `#${STATION_SELECTOR.NAME_INPUT_ID}`,
+ );
+ this.form = this.container.querySelector(`#${STATION_SELECTOR.FORM_ID}`);
+ this.table = this.container.querySelector(
+ `#${STATION_SELECTOR.TABLE_BODY}`,
+ );
+
+ this.form.addEventListener('submit', this.onSubmit);
+ this.table.addEventListener('click', this.onTableClick);
+ }
+
+ onSubmit = (event) => {
+ event.preventDefault();
+ const stationName = deleteWhiteSpace(this.userInput.value);
+ if (!this.isValid(stationName)) return;
+ this.addToTable(stationName, DATA_KEY.STATION_LIST);
+ this.clearInput();
+ };
+
+ onTableClick = (event) => {
+ const { className } = event.target;
+ const { index } = event.target.dataset;
+ const { name: stationName } = event.target.parentNode.parentNode.dataset;
+ if (className !== STATION_SELECTOR.DELETE_BUTTON_CLASS) return;
+ if (!confirm(STATION_WORDS.CONFIRM_MESSAGE)) return;
+ if (isOverlap(stationName, this.getAllStationNamesInLines())) {
+ alert(STATION_WORDS.ALERT_MESSAGE_SECTION_INCLUDES_STATION);
+ return;
+ }
+ this.deleteFromTable(index, DATA_KEY.STATION_LIST);
+ };
+
+ isValid(value) {
+ if (isOverlap(value, this.data.stationList)) {
+ alert(STATION_WORDS.ALERT_MESSAGE_ALREADY_INCLUDE);
+ return false;
+ }
+ if (!isValidLength(value, MIN_STATION_NAME_LENGTH)) {
+ alert(STATION_WORDS.ALERT_MESSAGE_STATION_MINLENGTH);
+ return false;
+ }
+ return true;
+ }
+
+ getAllStationNamesInLines() {
+ return [...new Set(this.data.lineList.map((line) => line.section).flat())];
+ }
+
+ template() {
+ return this.data.stationList
+ .map((station, index) => stationTableTemplate({
+ stationName: station,
+ index,
+ buttonClass: STATION_SELECTOR.DELETE_BUTTON_CLASS,
+ }))
+ .join('');
+ }
+
+ render() {
+ this.updateTable(this.template());
+ }
+}
diff --git a/src/managers/app.js b/src/managers/app.js
new file mode 100644
index 000000000..6ceb2785b
--- /dev/null
+++ b/src/managers/app.js
@@ -0,0 +1,79 @@
+import Component from '../factory/Component.js';
+import StationManager from './StationManager.js';
+import LineManager from './LineManager.js';
+import SectionManager from './SectionManager.js';
+import MapPrintManager from './MapPrintManager.js';
+import {
+ MENU,
+ STATION_SELECTOR,
+ LINE_SELECTOR,
+ SECTION_SELECTOR,
+ MAP_SELECTOR,
+} from '../share/selector.js';
+import storage, { STORAGE_KEY } from '../share/storage.js';
+
+export default class SubwayManager extends Component {
+ constructor() {
+ super();
+ this.data = storage.getItem(STORAGE_KEY);
+ this.menu = document.querySelector(`#${MENU.MENU_CONTAINER_ID}`);
+
+ this.stationManager = new StationManager({
+ managerId: MENU.STATION_MANGER_BUTTON_ID,
+ containerId: STATION_SELECTOR.MANAGER_CONTAINER_ID,
+ syncData: this.syncData,
+ });
+ this.lineManager = new LineManager({
+ managerId: MENU.LINE_MANAGER_BUTTON_ID,
+ containerId: LINE_SELECTOR.MANAGER_CONTAINER_ID,
+ syncData: this.syncData,
+ });
+ this.sectionManager = new SectionManager({
+ managerId: MENU.SECTION_MANAGER_BUTTON_ID,
+ containerId: SECTION_SELECTOR.MANAGER_CONTAINER_ID,
+ syncData: this.syncData,
+ });
+ this.mapPrintManager = new MapPrintManager({
+ managerId: MENU.MAP_PRINT_MANAGER_BUTTON_ID,
+ containerId: MAP_SELECTOR.PRINT_MANAGER_CONTAINER_ID,
+ syncData: this.syncData,
+ });
+ }
+
+ init() {
+ this.syncData(this.data);
+ this.menu.addEventListener('click', this.changeMenu);
+ }
+
+ changeMenu = (event) => {
+ const { nodeName } = event.target;
+ const { id } = event.target;
+ if (nodeName !== 'BUTTON') return;
+ this.getManager(id);
+ };
+
+ getManager(id) {
+ const managerList = [
+ this.stationManager,
+ this.lineManager,
+ this.sectionManager,
+ this.mapPrintManager,
+ ];
+ managerList.forEach((manager) => {
+ manager.managerId === id ? manager.show() : manager.hide();
+ manager.managerId === MENU.SECTION_MANAGER_BUTTON_ID
+ ? this.sectionManager.sectionDetailManager.hide()
+ : '';
+ });
+ }
+
+ syncData = (data) => {
+ storage.setItem(STORAGE_KEY, data);
+ this.setData(data);
+ this.stationManager.setData(data);
+ this.lineManager.setData(data);
+ this.sectionManager.setData(data);
+ this.sectionManager.sectionDetailManager.setData(data);
+ this.mapPrintManager.setData(data);
+ };
+}
diff --git a/src/share/selector.js b/src/share/selector.js
new file mode 100644
index 000000000..4e7e541c6
--- /dev/null
+++ b/src/share/selector.js
@@ -0,0 +1,52 @@
+export const MENU = Object.freeze({
+ MENU_CONTAINER_ID: 'menu',
+ STATION_MANGER_BUTTON_ID: 'station-manager-button',
+ LINE_MANAGER_BUTTON_ID: 'line-manager-button',
+ SECTION_MANAGER_BUTTON_ID: 'section-manager-button',
+ MAP_PRINT_MANAGER_BUTTON_ID: 'map-print-manager-button',
+});
+
+export const STATION_SELECTOR = Object.freeze({
+ MANAGER_CONTAINER_ID: 'station-manager',
+ FORM_ID: 'station-form',
+ RESULT_CONTAINER_ID: 'station-result',
+ TABLE: 'station-table',
+ TABLE_BODY: 'station-table-body',
+ NAME_INPUT_ID: 'station-name-input',
+ ADD_BUTTON_ID: 'station-add-button',
+ DELETE_BUTTON_CLASS: 'station-delete-button',
+});
+
+export const LINE_SELECTOR = Object.freeze({
+ MANAGER_CONTAINER_ID: 'line-manager',
+ FORM_ID: 'line-form',
+ NAME_INPUT_ID: 'line-name-input',
+ START_STATION_SELECTOR_ID: 'line-start-station-selector',
+ END_STATION_SELECTOR: 'line-end-station-selector',
+ ADD_BUTTON_ID: 'line-add-button',
+ DELETE_BUTTON_CLASS: 'line-delete-button',
+ RESULT_CONTAINER_ID: 'line-result',
+ TABLE: 'line-table',
+ TABLE_BODY: 'line-table-body',
+});
+
+export const SECTION_SELECTOR = Object.freeze({
+ MANAGER_CONTAINER_ID: 'section-manager',
+ LINE_MENU_ID: 'section-line-menu',
+ LINE_MENU_BUTTON_CLASS: 'section-line-menu-button',
+ DETAIL_CONTAINER_ID: 'section-detail',
+ FORM_ID: 'section-form',
+ FORM_HEADER_ID: 'section-form-header',
+ STATION_SELECTOR_ID: 'section-station-selector',
+ ORDER_INPUT_ID: 'section-order-input',
+ ADD_BUTTON_ID: 'section-add-button',
+ DELETE_BUTTON_CLASS: 'section-delete-button',
+ RESULT_CONTAINER_ID: 'section-result',
+ TABLE: 'section-table',
+ TABLE_BODY: 'section-table-body',
+});
+
+export const MAP_SELECTOR = Object.freeze({
+ PRINT_MANAGER_CONTAINER_ID: 'map-print-manager',
+ MAP_CLASS: 'map',
+});
diff --git a/src/share/storage.js b/src/share/storage.js
new file mode 100644
index 000000000..d6b874d45
--- /dev/null
+++ b/src/share/storage.js
@@ -0,0 +1,30 @@
+import Line from '../factory/Line.js';
+
+export const STORAGE_KEY = 'data';
+
+const defaultState = {
+ stationList: [],
+ lineList: [],
+ currentLineData: {
+ name: '',
+ section: [],
+ },
+};
+
+const jsonToClassConverter = (dataList, Creator) => dataList.map((data) => new Creator(data));
+
+const setItem = (key, data) => localStorage.setItem(key, JSON.stringify(data));
+
+const getItem = (key) => {
+ const tempData = JSON.parse(localStorage.getItem(key));
+ const data = tempData || defaultState;
+ data.lineList = jsonToClassConverter(data.lineList, Line);
+ return data;
+};
+
+const storage = {
+ setItem,
+ getItem,
+};
+
+export default storage;
diff --git a/src/share/template.js b/src/share/template.js
new file mode 100644
index 000000000..00208cad6
--- /dev/null
+++ b/src/share/template.js
@@ -0,0 +1,37 @@
+export const stationTableTemplate = ({ stationName, index, buttonClass }) => `
+
+ | ${stationName} |
+ |
+
+`;
+
+export const lineTableTemplate = ({
+ name, startStation, endStation, index, buttonClass,
+}) => `
+
+ | ${name} |
+ ${startStation} |
+ ${endStation} |
+ |
+
+ `;
+
+export const sectionDetailTableTemplate = ({ index, name, buttonClass }) => `
+
+ | ${index} |
+ ${name} |
+ |
+
+ `;
+
+export const mapTemplate = (lineList) => lineList
+ .map((line) => `
+
+ π ${line.name}
+
+ ${line.section.map((stationName) => `- ${stationName}
`).join('')}
+
+
`)
+ .join('');
+
+export const optionTemplate = (station) => ``;
diff --git a/src/share/utils.js b/src/share/utils.js
new file mode 100644
index 000000000..bcd8b00b9
--- /dev/null
+++ b/src/share/utils.js
@@ -0,0 +1,9 @@
+export const isOverlap = (value, list) => list.includes(value);
+
+export const isValidLength = (value, minLength) => value.length >= minLength;
+
+export const isSameStation = (prevStation, nextStation) => prevStation === nextStation;
+
+export const isEmpty = (value) => !value.length;
+
+export const deleteWhiteSpace = (words) => words.replaceAll(' ', '');
diff --git a/src/share/words.js b/src/share/words.js
new file mode 100644
index 000000000..c76f22668
--- /dev/null
+++ b/src/share/words.js
@@ -0,0 +1,28 @@
+export const DATA_KEY = {
+ STATION_LIST: 'stationList',
+ LINE_LIST: 'lineList',
+};
+
+export const STATION_WORDS = {
+ CONFIRM_MESSAGE: 'μ λ§λ‘ μμ νμκ² μ΅λκΉ?',
+ ALERT_MESSAGE_SECTION_INCLUDES_STATION:
+ 'λ
Έμ μ ν¬ν¨λμ΄μμ΄ μμ κ° λΆκ°λ₯ν©λλ€.',
+ ALERT_MESSAGE_STATION_MINLENGTH:
+ 'μμ 곡백μ μ μΈνκ³ 2μ μ΄μμ΄μ¬μΌ ν©λλ€.',
+ ALERT_MESSAGE_ALREADY_INCLUDE: 'μ΄λ―Έ λ±λ‘λ μμ
λλ€.',
+
+};
+
+export const LINE_WORDS = {
+ CONFIRM_MESSAGE: 'μ λ§ λ
Έμ μ μμ νμκ² μ΅λκΉ?',
+ ALERT_MESSAGE_SAME_STATION: 'μν μ’
μ κ³Ό νν μ’
μ μ λ¬λΌμΌ ν©λλ€.',
+ ALERT_MESSAGE_SAME_NAME: 'μ΄λ―Έ λ±λ‘λ λ
Έμ μ΄λ¦μ
λλ€.',
+ ALERT_MESSAGE_NO_WHITESPACE: 'λ
Έμ μ΄λ¦μ 곡백μΌλ‘ μ§μ ν μ μμ΅λλ€.',
+};
+
+export const SECTION_DETAIL_WORDS = {
+ CONFIRM_MESSAGE: 'μ λ§λ‘ μμ νμκ² μ΅λκΉ?',
+ ALERT_MESSAGE_SECTION_INCLUDES_STATION: 'μ΄λ―Έ λ
Έμ μ ν¬ν¨λμ΄μμ΅λλ€',
+ ALERT_MESSAGE_SECTION_MINLENGTH: 'λ
Έμ μ μ΅μ 2κ°μμ ν¬ν¨ν΄μΌ ν©λλ€.',
+ ALERT_MESSAGE_NO_INDEX: 'μμλ₯Ό μ
λ ₯ν΄μ£ΌμΈμ',
+};
diff --git a/style.css b/style.css
new file mode 100644
index 000000000..fa27f7831
--- /dev/null
+++ b/style.css
@@ -0,0 +1,28 @@
+label {
+ margin-top: 2rem;
+ display: block;
+}
+table,
+th,
+tr,
+td {
+ border: 1px solid black;
+}
+
+input{
+ width: 10rem;
+}
+
+li{
+ list-style :none
+}
+
+.dot{
+ list-style: disc;
+}
+
+.section-line-menu-button{
+ min-width: 3rem;
+ min-height: 1.5rem;
+ margin-right: 0.3rem;
+}
\ No newline at end of file