diff --git a/README.md b/README.md
index e97a1d649..46040b0eb 100644
--- a/README.md
+++ b/README.md
@@ -1,69 +1,153 @@
# π μ§νμ² λ
Έμ λ λ―Έμ
+## π¦ λλ ν 리 ꡬ쑰
+```
+βββ LICENSE
+βββ README.md
+βββ images
+β βββ line_manager.gif
+β βββ map_print_manager.gif
+β βββ section1.png
+β βββ section2.png
+β βββ section_manager.gif
+β βββ station_manager.gif
+βββ index.html
+βββ src
+ βββ config.js
+ βββ core
+ β βββ Component.js
+ βββ index.js
+ βββ pages
+ β βββ LineManager.js
+ β βββ PrintSubwayLine.js
+ β βββ SectionManager.js
+ β βββ StationManager.js
+ βββ utils
+ βββ errors.js
+ βββ events.js
+ βββ reRenderPage.js
+```
+- config: μ 체 νμ΄μ§μ νμν νκ·Έλ₯Ό λ°λ‘ κ΄λ¦¬νκΈ° μν΄ μ€μ νμΌλ‘ λΆλ¦¬
+- core: μνλ‘ HTMLμ λ λλ§νκΈ° μν΄ μ¬μ©λλ λ©μλλ€μ μ»΄ν¬λνΈ ν΄λμ€λ₯Ό μ¬μ©νμ¬ μ μ
+- pages: κ°κ°μ νλ©΄λ€μ ν ν΄λμμ κ΄λ¦¬νκΈ° μν΄ pages ν΄λμ λΆλ¦¬
+- utils: μλ¬λ©μμ§, μ΄λ²€νΈ, νμ΄μ§λ₯Ό λ€μ κ·Έλ €μ£Όλ ν¨μλ₯Ό utils ν΄λμ λΆλ¦¬
+
+
+
## π κΈ°λ₯ μꡬμ¬ν
-### μ§νμ² μ κ΄λ ¨ κΈ°λ₯
-- μ§νμ² μμ λ±λ‘νκ³ μμ ν μ μλ€. (λ¨, λ
Έμ μ λ±λ‘λ μμ μμ ν μ μλ€)
-- μ€λ³΅λ μ§νμ² μ μ΄λ¦μ΄ λ±λ‘λ μ μλ€.
-- μ§νμ² μμ 2κΈμ μ΄μμ΄μ΄μΌ νλ€.
-- μ§νμ² μμ λͺ©λ‘μ μ‘°νν μ μλ€.
+### 1. μ κ΄λ¦¬ νμ΄μ§
-### μ§νμ² λ
Έμ κ΄λ ¨ κΈ°λ₯
-- μ§νμ² λ
Έμ μ λ±λ‘νκ³ μμ ν μ μλ€.
-- μ€λ³΅λ μ§νμ² λ
Έμ μ΄λ¦μ΄ λ±λ‘λ μ μλ€.
-- λ
Έμ λ±λ‘ μ μν μ’
μ μκ³Ό νν μ’
μ μμ μ
λ ₯λ°λλ€.
-- μ§νμ² λ
Έμ μ λͺ©λ‘μ μ‘°νν μ μλ€.
+#### 1) μμ μ΄λ¦ μ ν¨μ± κ²μ¬
+
+- μ€λ³΅λ μμ μ΄λ¦μ λ±λ‘ν μ μλ€.
+- μμ μ΄λ¦μ 2κΈμ μ΄μμΌλ‘ νλ€.
+- μμ μ΄λ¦μ κ³΅λ°±μ΄ λ€μ΄κ° μ μλ€.
+- μμ μ΄λ¦μ νκΈλ‘λ§ μμ± κ°λ₯νλ€.
+- μμ μ΄λ¦μ΄ μ¬λ°λ₯΄μ§ μμ μ, `alert` λ©μμ§λ₯Ό μΆλ ₯νλ€.
+
+#### 2) μ§νμ² μ λͺ©λ‘ κ΄λ¦¬
+
+- μ κ΄λ¦¬ λ²νΌ ν΄λ¦ μ, μ κ΄λ¦¬ νμ΄μ§λ₯Ό μΆλ ₯νλ€.
+- μ μΆκ° λ²νΌ ν΄λ¦ μ, μμ μΆκ°νλ€.
+- μ μμ λ²νΌ ν΄λ¦ μ, μμ μμ νλ€.
+- μμ μΆκ°μ μμ μ, μμ λͺ©λ‘μ μΆλ ₯νλ€.
+- μ κ΄λ¦¬κ° λλ μμ λͺ©λ‘μ λ‘컬μ€ν 리μ§μ μ μ₯νλ€.
+
+### 2. λ
Έμ κ΄λ¦¬ νμ΄μ§
+
+#### 1) λ
Έμ μ μ΄λ¦ μ ν¨μ± κ²μ¬
+
+- μ€λ³΅λ λ
Έμ μ μ΄λ¦μ λ±λ‘ν μ μλ€.
+- λ
Έμ μ μ΄λ¦μ κ³΅λ°±μ΄ λ€μ΄κ° μ μλ€.
+- λ
Έμ μ μ΄λ¦μ΄ μ¬λ°λ₯΄μ§ μμ μ, `alert` λ©μμ§λ₯Ό μΆλ ₯νλ€.
+
+#### 2) μ§νμ² λ
Έμ λͺ©λ‘ κ΄λ¦¬
+
+- λ
Έμ κ΄λ¦¬ λ²νΌ ν΄λ¦ μ, λ
Έμ κ΄λ¦¬ νμ΄μ§λ₯Ό μΆλ ₯νλ€.
+- λ
Έμ μΆκ° λ²νΌ ν΄λ¦ μ, λ
Έμ μ μΆκ°νλ€.
+- μμ λ²νΌ ν΄λ¦ μ, λ
Έμ μ μμ νλ€.
+- λ
Έμ μΆκ°μ μμ μ, λ
Έμ λͺ©λ‘μ μΆλ ₯νκ³ λ‘컬μ€ν 리μ§μ λ
Έμ λͺ©λ‘μ μ μ₯νλ€.
+
+### 3. κ΅¬κ° κ΄λ¦¬ νμ΄μ§
+
+#### 1) λ
Έμ μ ν
+
+- λ‘컬μ€ν 리μ§μμ λ
Έμ μ λ°μμ λ
Έμ λ©λ΄ λ²νΌμ μμ±νλ€.
+- κ° λ
Έμ μ λ²νΌμ ν΄λ¦ μ, ν΄λΉ λ
Έμ μ μ 보λ₯Ό μΆλ ₯νλ€.
+
+#### 2) μ§νμ² κ΅¬κ° λͺ©λ‘ κ΄λ¦¬
-### μ§νμ² κ΅¬κ° μΆκ° κΈ°λ₯
- μ§νμ² λ
Έμ μ ꡬκ°μ μΆκ°νλ κΈ°λ₯μ λ
Έμ μ μμ μΆκ°νλ κΈ°λ₯μ΄λΌκ³ λ ν μ μλ€.
- - μκ³Ό μμ¬μ΄λ₯Ό ꡬκ°μ΄λΌ νκ³ μ΄ κ΅¬κ°λ€μ λͺ¨μμ΄ λ
Έμ μ΄λ€.
+- μκ³Ό μμ¬μ΄λ₯Ό ꡬκ°μ΄λΌ νκ³ μ΄ κ΅¬κ°λ€μ λͺ¨μμ΄ λ
Έμ μ΄λ€.
- νλμ μμ μ¬λ¬κ°μ λ
Έμ μ μΆκ°λ μ μλ€.
- μκ³Ό μ μ¬μ΄μ μλ‘μ΄ μμ΄ μΆκ° λ μ μλ€.
- λ
Έμ μμ κ°λκΈΈμ μκΈΈ μ μλ€.
+- μΆκ°λ μμ μ΄λ¦κ³Ό μμλ₯Ό μ
λ ₯ λ°λλ€.
+- λ±λ‘ λ²νΌ ν΄λ¦ μ, λ
Έμ μ μμ μΆκ°νλ€.
+- λ
Έμ μμ μ κ±° λ²νΌ ν΄λ¦ μ, λ
Έμ μ μμ μ κ±°νλ€.
+- λ
Έμ μ λ±λ‘κ³Ό μ κ±° μ, κ΅¬κ° λͺ©λ‘μ μΆλ ₯νκ³ λ‘컬μ€ν 리μ§μ λ
Έμ λͺ©λ‘μ μ μ₯νλ€.
+- μ’
μ μ κ±° μ λ€μ μμ΄ μ’
μ μ΄ λλ€.
+- λ
Έμ μ ν¬ν¨λ μμ΄ 2κ° μ΄νμΌ λ, μμ μ κ±°ν μ μλ€.
+- μΆκ°ν μμ μμλ₯Ό μ
λ ₯νμ§ μμΌλ©΄ `alert` λ©μμ§λ₯Ό μΆλ ₯νλ€.
-
+### 4. μ§νμ² λ
Έμ λ μΆλ ₯
-### μ§νμ² κ΅¬κ° μμ κΈ°λ₯
-- λ
Έμ μ λ±λ‘λ μμ μ κ±°ν μ μλ€.
-- μ’
μ μ μ κ±°ν κ²½μ° λ€μ μμ΄ μ’
μ μ΄ λλ€.
-- λ
Έμ μ ν¬ν¨λ μμ΄ λκ° μ΄νμΌ λλ μμ μ κ±°ν μ μλ€.
+- λ‘컬μ€ν 리μ§μ μ μ₯λ λ
Έμ λλ₯Ό νλ©΄μ μΆλ ₯νλ€.
+
+
-
+## π₯ λ―Έν‘νλ μ & κ³ μνλ μ
-### μ§νμ² λ
Έμ μ λ±λ‘λ μ μ‘°ν κΈ°λ₯
-- λ
Έμ μ μν μ’
μ λΆν° νν μ’
μ κΉμ§ μ°κ²°λ μμλλ‘ μ λͺ©λ‘μ μ‘°νν μ μλ€.
+- [data]: νμ¬ κ΅¬μ‘°μμ μ΄λ»κ² [data]μμ±μ μ¬μ©ν΄μΌ ν μ§ νμ
μ νμ§ λͺ»νμ΅λλ€.
+- μλ‘μ΄ κ΅¬μ‘°λ‘ μ½λ©μ νλ€ λ³΄λ λ§νλ λΆλΆμ΄ λ§μκ³ , κ·Έλ‘ μΈν΄ μκ°μ μ«κ²¨ λ λ§μ μμΈμ¬νμ λν κ³ λ―Όμ΄ λΆμ‘±νμ΅λλ€.
+- μνκ° λ³ν λ λ§λ€ μ 체 νλ©΄μ λ λλ§νλ κ΅¬μ‘°λ‘ μ½λ©μ νμ¬, λ³ννμ§ μλ λΆλΆλ λ€μ κ·Έλ €μΌ νμ΅λλ€.
+- κ° νμ΄μ§λ§λ€ νλμ ν΄λμ€ μμ λ§μ λ©μλκ° μ‘΄μ¬νμ¬ μ½κΈ° λΆνΈνλ€κ³ μκ°μ΄ λ€μμ΅λλ€.
+- eslintμ prettierμ μ
ν
μ μλν°μμλ§ μ€μ νκ³ , νμΌλ‘ λ³Ό μ μλλ‘ νμ§ λͺ»ν μ μ΄ μμ½μ΅λλ€.
+- λ ν¨μ¨μ μΈ μ½λλ₯Ό μμ±νμ§ λͺ»νκ³ , λ¨μν 15λΌμΈμ λμ§ μκΈ° μν΄ μ΅μ§λ‘ λΆλ¦¬ν λ―ν λΆλΆμ΄ μμ΄ μμ½μ΅λλ€.
+- μ΄λ ν μ΄μ λ‘ μλ¬λ©μμ§κ° νλ©΄μ λ¬ κ±΄μ§ μν©λ§λ€ λ λͺ
ννκ² μ€λͺ
μ νμ§ λͺ»νμ΅λλ€. λ€λ₯Έ λΆμ prμ λ³΄κ³ try catchλ¬Έμ μ¬μ©νλ©΄ λ λͺ
ννκ² μ€λͺ
μ ν μ μλ€λ κ²μ λ°°μΈ μ μμμ΅λλ€.
+- mapμΌλ‘ νλ©΄μ 그릴 λ, νλ©΄μ κ³μ μλμΉ μμ ν
μ€νΈκ° λ°μνμ¬ μμΈμ μ°Ύλλ° κ½€ μ€λ κ±Έλ Έμ΅λλ€. κ²°κ³Όμ μΌλ‘ λ°°μ΄μΈλ° `join` μ μ¬μ©νμ§ μμ λ°μν λ¬Έμ μμ΅λλ€.
+- ν
νλ¦Ώ 리ν°λ΄μ μ¬μ©ν΄ μΌμΌν νλ©΄μ κ·Έλ¦¬λ€ λ³΄λ νκ·Έλ€μ μμ±νλλ° ν·κ°λ¦¬λ λΆλΆμ΄ λ§μ κ³ μνμ΅λλ€. νμ₯λꡬλ₯Ό μ΄μ©νμ¬ λ νΈνκ² ν μ μλ€λ κ²μ λμ€μ μκ² λμμ΅λλ€.
+- μ΄λ€ κ°μ μμλ‘ κ΄λ¦¬ν΄μΌ νλ μ§ νλ¨νλ κ²μ΄ μ΄λ €μ μ΅λλ€. ν¨μμμμλ§ μ¬μ©νκ³ μλ―Έκ° λͺ
νν΄ λ³΄μ΄λ κ°λ€λ μμ μ²λ¦¬ ν΄μΌ νλ μ§ μλ¬Έμ΄ λ€μμ΅λλ€.
+- μνμ’
μ κ³Ό ννμ’
μ μ κ°μ μ μ₯ν λ νλμ λ°°μ΄μ μ μ₯νμ§ μκ³ λ°λ‘ μ μ₯νμ΅λλ€. κ·Έλ‘ μΈν΄ ꡬκ°μμ λͺ©λ‘μ μΆκ°νκ³ μμ ν λ, λ°μ΄ν° λ³κ²½νλ λΆλΆμ΄ λ무 μ΄λ €μ μ΅λλ€. κ²°κ΅ λ€μ λμκ° λ κ°λ€μ νλμ λ°°μ΄λ‘ κ΄λ¦¬νλλ‘ μμ νμκ³ , λ¬Έμ λ₯Ό ν΄κ²°ν μ μμμ΅λλ€.
## π» νλ‘κ·Έλ¨ μ€ν κ²°κ³Ό
### μκ΄λ¦¬
+
### λ
Έμ κ΄λ¦¬
+
### ꡬκ°κ΄λ¦¬
+
### λ
Έμ λ μΆλ ₯
-
+
## β
νλ‘κ·Έλλ° μꡬμ¬ν
### λ©λ΄ λ²νΌ
+
- μ κ΄λ¦¬ 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 +155,7 @@
- μ§νμ² λ
Έμ μ μμ νλ button νκ·Έλ `.line-delete-button` classκ°μ κ°μ§λ€.
### μ§νμ² κ΅¬κ° μΆκ° κΈ°λ₯
+
- μ§νμ² λ
Έμ μ μ ννλ button νκ·Έλ `.section-line-menu-button` classκ°μ κ°μ§λ€.
- μ§νμ² κ΅¬κ°μ μ€μ ν μ select νκ·Έλ `#section-station-selector` idκ°μ κ°μ§λ€.
- μ§νμ² κ΅¬κ°μ μμλ₯Ό μ
λ ₯νλ input νκ·Έλ `#section-order-input` idκ°μ κ°μ§λ€.
@@ -78,6 +163,7 @@
- μ§νμ² κ΅¬κ°μ μ κ±°νλ button νκ·Έλ `.section-delete-button` classκ°μ κ°μ§λ€.
### μ§νμ² λ
Έμ λ μΆλ ₯ κΈ°λ₯
+
- μ§νμ² λ
Έμ λ μΆλ ₯ λ²νΌμ λλ₯΄λ©΄ `
` νκ·Έλ₯Ό λ§λ€κ³ ν΄λΉ νκ·Έ λ΄λΆμ λ
Έμ λλ₯Ό μΆλ ₯νλ€.
### κΈ°μ‘΄ μꡬμ¬ν
@@ -101,7 +187,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)λ₯Ό μ΄μ©νμ¬, μλ‘κ³ μΉ¨νλλΌλ κ°μ₯ μ΅κ·Όμ μμ
ν μ 보λ€μ λΆλ¬μ¬ μ μλλ‘ νλ€.
diff --git a/index.html b/index.html
index fc99deac2..f2318dd3c 100644
--- a/index.html
+++ b/index.html
@@ -3,10 +3,29 @@
μ§νμ² λ
Έμ λ κ΄λ¦¬
+
+
+
π μ§νμ² λ
Έμ λ κ΄λ¦¬
+
+
+
+
+
-
π μ§νμ² λ
Έμ λ κ΄λ¦¬
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 000000000..aeb4b6135
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,23 @@
+import LineManager from "./pages/lineManager.js";
+import PrintSubwayLine from "./pages/PrintSubwayLine.js";
+import SectionManager from "./pages/SectionManager.js";
+import StationManager from "./pages/StationManager.js";
+
+export const elementMap = [
+ {
+ page: StationManager,
+ button: document.getElementById("station-manager-button"),
+ },
+ {
+ page: LineManager,
+ button: document.getElementById("line-manager-button"),
+ },
+ {
+ page: SectionManager,
+ button: document.getElementById("section-manager-button"),
+ },
+ {
+ page: PrintSubwayLine,
+ button: document.getElementById("map-print-manager-button"),
+ },
+];
diff --git a/src/core/Component.js b/src/core/Component.js
new file mode 100644
index 000000000..4fb87f321
--- /dev/null
+++ b/src/core/Component.js
@@ -0,0 +1,32 @@
+import { dispatchReRender } from "../utils/events.js";
+
+export default class Component {
+ constructor() {
+ this.state = {};
+ this.store = {};
+ }
+
+ setState(state) {
+ this.state = state;
+ dispatchReRender();
+ }
+
+ setStore(store) {
+ this.store = store;
+ localStorage.setItem("store", JSON.stringify(store));
+ dispatchReRender();
+ }
+
+ create() {
+ const store = localStorage.getItem("store");
+ if (store) {
+ this.store = JSON.parse(store);
+ }
+ }
+
+ afterCreate() {}
+
+ mount() {}
+
+ render() {}
+}
diff --git a/src/index.js b/src/index.js
index e69de29bb..dc01061c1 100644
--- a/src/index.js
+++ b/src/index.js
@@ -0,0 +1,20 @@
+import { dispatchReRender, RE_RENDER_EVENT } from "./utils/events.js";
+import { elementMap } from "./config.js";
+import reRenderPage from "./utils/reRenderPage.js";
+
+function init() {
+ const app = document.querySelector("#app");
+ let page;
+
+ elementMap.forEach((element) => {
+ const managerPage = new element.page();
+ element.button.addEventListener("click", () => {
+ page = managerPage;
+ dispatchReRender();
+ });
+ });
+
+ window.addEventListener(RE_RENDER_EVENT, () => reRenderPage(app, page));
+}
+
+init();
diff --git a/src/pages/LineManager.js b/src/pages/LineManager.js
new file mode 100644
index 000000000..4425b6fa7
--- /dev/null
+++ b/src/pages/LineManager.js
@@ -0,0 +1,180 @@
+import Component from "../core/Component.js";
+import { ERROR } from "../utils/errors.js";
+
+const elementMap = {
+ lineNameInput: "line-name-input",
+ lineStartStationSelector: "line-start-station-seletor",
+ lineEndStationSelector: "line-end-station-seletor",
+ lineAddButton: "line-add-button",
+ lineDeleteButton: "line-delete-button",
+};
+
+export default class LineManager extends Component {
+ constructor() {
+ super();
+ this.state = {
+ lineName: "",
+ lineStartStation: "",
+ lineEndStation: "",
+ };
+
+ this.handleLineAddButton = () => {
+ const line = {
+ name: this.state.lineName,
+ stations: [this.state.lineStartStation, this.state.lineEndStation],
+ };
+ const lines = this.store.lines ? [...this.store.lines, line] : [line];
+
+ if (isDuplicateLine(this.store.lines, this.state.lineName)) {
+ alert(ERROR.RE_TYPING_LINE);
+ } else {
+ this.setStore({ ...this.store, lines });
+ }
+ };
+
+ this.handleLineDeleteButton = (index) => {
+ this.store.lines.splice(index, 1);
+ this.setStore({ ...this.store });
+ };
+ }
+
+ afterCreate() {
+ const { lineStartStation, lineEndStation } = this.state;
+ const { stations } = this.store;
+ if (lineStartStation.length === 0)
+ this.state.lineStartStation = stations[0];
+ if (lineEndStation.length === 0) this.state.lineEndStation = stations[0];
+ }
+
+ mount() {
+ this.mountNameInput();
+ this.mountStartSelector();
+ this.mountEndSelector();
+ this.mountAddButton();
+ this.mountDeleteButton();
+ }
+
+ mountNameInput() {
+ const lineNameInput = document.getElementById(elementMap.lineNameInput);
+ lineNameInput.addEventListener("blur", (event) => {
+ if (haveSpaceInLineName(event.target.value)) {
+ alert(ERROR.RE_TYPING_LINENAME);
+ return;
+ }
+ this.setState({ ...this.state, lineName: event.target.value });
+ });
+ }
+
+ mountStartSelector() {
+ const lineStartStationSelector = document.getElementById(
+ elementMap.lineStartStationSelector
+ );
+ lineStartStationSelector.addEventListener("change", (event) => {
+ this.setState({ ...this.state, lineStartStation: event.target.value });
+ });
+ }
+
+ mountEndSelector() {
+ const lineEndStationSelector = document.getElementById(
+ elementMap.lineEndStationSelector
+ );
+ lineEndStationSelector.addEventListener("change", (event) => {
+ this.setState({ ...this.state, lineEndStation: event.target.value });
+ });
+ }
+
+ mountAddButton() {
+ const lineAddButton = document.getElementById(elementMap.lineAddButton);
+ lineAddButton.addEventListener("click", this.handleLineAddButton);
+ }
+
+ mountDeleteButton() {
+ const lineDeleteButtons = document.getElementsByClassName(
+ elementMap.lineDeleteButton
+ );
+ [...lineDeleteButtons].forEach((lineDeleteButton, index) => {
+ lineDeleteButton.addEventListener("click", () => {
+ this.handleLineDeleteButton(index);
+ });
+ });
+ }
+
+ render() {
+ const { lines = [], stations = [] } = this.store;
+ const { lineName, lineStartStation, lineEndStation } = this.state;
+ return `
+
+
λ
Έμ μ΄λ¦
+
+
+
+
+ μν μ’
μ
+
+
+
+ νν μ’
μ
+
+
+
+
+
+
+
+
π μ§νμ² λ
Έμ λͺ©λ‘
+
+
+
+ | λ
Έμ μ΄λ¦ |
+ μν μ’
μ μ |
+ νν μ’
μ μ |
+ μ€μ |
+
+
+
+ ${lines
+ .map(
+ (line) =>
+ `
+ | ${line.name} |
+ ${line.stations[0]} |
+ ${line.stations[1]} |
+
+
+ |
+
`
+ )
+ .join("")}
+
+
+
+ `;
+ }
+}
+
+function isDuplicateLine(lines = [], lineName) {
+ return lines.find((line) => line.name === lineName);
+}
+
+function haveSpaceInLineName(lineName) {
+ return [...lineName].includes(" ");
+}
diff --git a/src/pages/PrintSubwayLine.js b/src/pages/PrintSubwayLine.js
new file mode 100644
index 000000000..8113cd70f
--- /dev/null
+++ b/src/pages/PrintSubwayLine.js
@@ -0,0 +1,33 @@
+import Component from "../core/Component.js";
+
+export default class PrintSubwayLine extends Component {
+ constructor() {
+ super();
+ }
+
+ render() {
+ const { lines } = this.store;
+ return `
+
+ ${lines
+ .map(
+ (line) => `
+
+
${line.name}
+
+ ${line.stations
+ .map(
+ (station) => `
+ - ${station}
+ `
+ )
+ .join("")}
+
+
+ `
+ )
+ .join("")}
+
+ `;
+ }
+}
diff --git a/src/pages/SectionManager.js b/src/pages/SectionManager.js
new file mode 100644
index 000000000..fca148a9f
--- /dev/null
+++ b/src/pages/SectionManager.js
@@ -0,0 +1,164 @@
+import Component from "../core/Component.js";
+import { ERROR } from "../utils/errors.js";
+
+const elementMap = {
+ sectionLineMenuButton: "section-line-menu-button",
+ sectionStationSelector: "section-station-selector",
+ sectionOrderInput: "section-order-input",
+ sectionAddButton: "section-add-button",
+ sectionDeleteButton: "section-delete-button",
+};
+
+export default class SectionManager extends Component {
+ constructor() {
+ super();
+ this.state = {
+ selectedLine: null,
+ };
+
+ this.handleLineButtonClick = (index) => {
+ this.setState({ selectedLine: index });
+ };
+
+ this.handleAddButtonClick = (order, station) => {
+ if (order.length === 0) {
+ alert(ERROR.RE_TYPING_ORDER);
+ return;
+ }
+
+ const { selectedLine } = this.state;
+ this.store.lines[selectedLine].stations.splice(order, 0, station);
+ this.setStore(cloneDeep(this.store));
+ };
+
+ this.handleDeleteButtonClick = (index) => {
+ const isLessThanTwoStation = stations.length < 2;
+ if (isLessThanTwoStation) {
+ alert(ERROR.NOT_DELETE);
+ return;
+ }
+
+ const stations = this.store.lines[this.state.selectedLine].stations;
+ stations.splice(index, 1);
+ this.setStore(cloneDeep(this.store));
+ };
+ }
+
+ mount() {
+ const sectionStationSelector = document.getElementById(
+ elementMap.sectionStationSelector
+ );
+ const sectionOrderInput = document.getElementById(
+ elementMap.sectionOrderInput
+ );
+ this.mountLineMenuButton();
+ this.mountAddButton(sectionOrderInput, sectionStationSelector);
+ this.mountDeleteButton();
+ }
+
+ mountLineMenuButton() {
+ const sectionLineMenuButtons = document.getElementsByClassName(
+ elementMap.sectionLineMenuButton
+ );
+ [...sectionLineMenuButtons].forEach((button, index) => {
+ button.addEventListener("click", () => {
+ this.handleLineButtonClick(index);
+ });
+ });
+ }
+
+ mountAddButton(sectionOrderInput, sectionStationSelector) {
+ const sectionAddButton = document.getElementById(
+ elementMap.sectionAddButton
+ );
+ sectionAddButton?.addEventListener("click", () => {
+ this.handleAddButtonClick(
+ sectionOrderInput.value,
+ sectionStationSelector.value
+ );
+ });
+ }
+
+ mountDeleteButton() {
+ const sectionDeleteButtons = document.getElementsByClassName(
+ elementMap.sectionDeleteButton
+ );
+ sectionDeleteButtons &&
+ [...sectionDeleteButtons].forEach((button, index) => {
+ button.addEventListener("click", () => {
+ this.handleDeleteButtonClick(index);
+ });
+ });
+ }
+
+ render() {
+ const { stations = [], lines = [] } = this.store;
+ const { selectedLine } = this.state;
+ const Section = () => `
+
+
${lines[selectedLine]?.name} κ΄λ¦¬
+ κ΅¬κ° λ±λ‘
+
+
+
+
+
+
+
+ | μμ |
+ μ΄λ¦ |
+ μ€μ |
+
+
+
+ ${
+ lines[selectedLine]?.stations
+ .map(
+ (station, index) =>
+ `
+ | ${index} |
+ ${station} |
+
+
+ |
+
`
+ )
+ .join("") || ""
+ }
+
+
+ `;
+
+ return `
+
+
ꡬκ°μ μμ ν λ
Έμ μ μ νν΄μ£ΌμΈμ.
+ ${lines
+ .map(
+ (line) =>
+ ``
+ )
+ .join("")}
+
+ ${isNull(selectedLine) ? "" : Section()}
+ `;
+ }
+}
+
+function isNull(value) {
+ return String(value) === "null" || String(value) === "undefined";
+}
+
+function cloneDeep(value) {
+ return JSON.parse(JSON.stringify(value));
+}
diff --git a/src/pages/StationManager.js b/src/pages/StationManager.js
new file mode 100644
index 000000000..3c92f3adb
--- /dev/null
+++ b/src/pages/StationManager.js
@@ -0,0 +1,129 @@
+import Component from "../core/Component.js";
+import { ERROR } from "../utils/errors.js";
+
+const elementMap = {
+ stationNameInput: "station-name-input",
+ stationAddButton: "station-add-button",
+ stationDeleteButton: "station-delete-button",
+};
+
+export default class StationManager extends Component {
+ constructor() {
+ super();
+
+ this.handleAddButtonClick = (newStation) => {
+ if (
+ isMoreThanTwoWords(newStation) &&
+ isNotDuplicateSatationName(newStation, this.store.stations) &&
+ hasNotSpaceInStationName(newStation) &&
+ isOnlyWord(newStation)
+ ) {
+ const stations = this.store.stations
+ ? [...this.store.stations, newStation]
+ : [newStation];
+ this.setStore({ ...this.store, stations });
+ } else {
+ alert(ERROR.RE_TYPING_STATION);
+ }
+ };
+
+ this.handleDeleteButtonClick = (index) => {
+ this.store.stations.splice(index, 1);
+ this.setStore({ ...this.store });
+ };
+ }
+
+ mount() {
+ const stationNameInput = document.getElementById(
+ elementMap.stationNameInput
+ );
+ this.mountAddButton(stationNameInput);
+ this.mountDeleteButton();
+ }
+
+ mountAddButton(stationNameInput) {
+ const stationAddButton = document.getElementById(
+ elementMap.stationAddButton
+ );
+ stationAddButton.addEventListener("click", () => {
+ this.handleAddButtonClick(stationNameInput.value);
+ });
+ }
+
+ mountDeleteButton() {
+ const stationDeleteButtons = document.getElementsByClassName(
+ elementMap.stationDeleteButton
+ );
+ [...stationDeleteButtons].forEach((stationDeleteButton, index) => {
+ stationDeleteButton.addEventListener("click", () => {
+ this.handleDeleteButtonClick(index);
+ });
+ });
+ }
+
+ render() {
+ const { stations = [] } = this.store;
+ return `
+
+
μ μ΄λ¦
+
+
+
+
+
+
π μ§νμ² μ λͺ©λ‘
+
+
+
+ | μ μ΄λ¦ |
+ μ€μ |
+
+
+
+ ${stations
+ .map(
+ (station) =>
+ `
+ | ${station} |
+
+
+ |
+
+ `
+ )
+ .join("")}
+
+
+
+
+ `;
+ }
+}
+
+function isMoreThanTwoWords(stationName) {
+ return stationName.length > 1;
+}
+
+function isNotDuplicateSatationName(stationName, stations = []) {
+ return !stations.includes(stationName);
+}
+
+function hasNotSpaceInStationName(stationName) {
+ return ![...stationName].includes(" ");
+}
+
+function isOnlyWord(stationName) {
+ const check_num = /[0-9]/;
+ const check_eng = /[a-zA-Z]/;
+ const check_spc = /[~!@#$%^&*()_+|<>?:{}]/;
+ const check_kor = /[γ±-γ
|γ
-γ
£|κ°-ν£]/;
+
+ return (
+ check_kor.test(stationName) &&
+ !check_eng.test(stationName) &&
+ !check_num.test(stationName) &&
+ !check_spc.test(stationName)
+ );
+}
diff --git a/src/utils/errors.js b/src/utils/errors.js
new file mode 100644
index 000000000..e1000f94b
--- /dev/null
+++ b/src/utils/errors.js
@@ -0,0 +1,7 @@
+export const ERROR = {
+ RE_TYPING_STATION: "μ μ΄λ¦μ λ€μ μ
λ ₯ν΄ μ£ΌμΈμ",
+ RE_TYPING_LINE: "μ€λ³΅λ λ
Έμ μ΄ μ‘΄μ¬ν©λλ€",
+ RE_TYPING_LINENAME: "λ
Έμ μ΄λ¦μ κ³΅λ°±μ΄ μ‘΄μ¬ν©λλ€",
+ RE_TYPING_ORDER: "μμλ₯Ό μ
λ ₯νμΈμ",
+ NOT_DELETE: "2κ° μ΄νλ μ§μΈ μ μμ΅λλ€",
+};
diff --git a/src/utils/events.js b/src/utils/events.js
new file mode 100644
index 000000000..94eb7023b
--- /dev/null
+++ b/src/utils/events.js
@@ -0,0 +1,5 @@
+export { dispatchReRender, RE_RENDER_EVENT, reRenderEvent };
+
+const RE_RENDER_EVENT = "customRender";
+const reRenderEvent = new CustomEvent(RE_RENDER_EVENT);
+const dispatchReRender = () => window.dispatchEvent(reRenderEvent);
diff --git a/src/utils/reRenderPage.js b/src/utils/reRenderPage.js
new file mode 100644
index 000000000..b7fead00d
--- /dev/null
+++ b/src/utils/reRenderPage.js
@@ -0,0 +1,6 @@
+export default function reRenderPage(app, page) {
+ page.create();
+ page.afterCreate();
+ app.innerHTML = page.render();
+ page.mount();
+}