diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..e66cbb303
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+git-commit-template.txt
+node_modules
+package.json
+package-lock.json
+.eslintrc.js
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..0448900b1
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,61 @@
+## 기능 구현 목록
+
+### 화면 출력
+
+- ✔ 버튼을 누르면 버튼에 맞는 화면을 출력한다.
+
+### localStorage
+
+- ✔ localStorage를 사용해 새로고침하더라도 이전에 작업한 정보를 불러온다.
+
+### data
+
+- ✔ data속성을 활용해 역, 노선, 구간의 데이터 값을 관리한다.
+
+### 역 관리
+
+- ✔ 역을 추가할 수 있고, 화면에 추가한다.
+
+ - 예외처리)
+ - ✔ 중복 불가
+ - ✔ 2글자 미만 불가
+ - ✔ 한글과 숫자만 가능(기능 추가)
+
+- ✔ 역을 삭제할 수 있고, 화면에서 삭제한다.
+ - 예외처리)
+ - ✔ 노선에 등록된 역 삭제 불가
+
+### 노선 관리
+
+- ✔ 존재하는 역을 통해 상행과 하행 종점을 선택할 수 있고, 이름을 정해서 노선을 추가할 수 있으며, 화면에 추가한다.
+
+ - 예외처리)
+ - ✔ 노선 이름 중복 불가
+ - ✔ 한글과 숫자만 가능(기능 추가)
+ - ✔ 2글자 미만 불가(기능 추가)
+ - ✔ 한 노선에서 중복된 역 입력 불가(기능 추가)
+
+- ✔ 노선을 삭제할 수 있고, 화면에서 삭제한다.
+
+### 구간 관리
+
+- ✔ 존재하는 노선을 선택할 수 있고, 선택 시 화면에 출력한다.
+
+- ✔ 노선에 존재하는 역을 추가할 수 있으며, 순서를 정할 수 있다.
+ - 예외처리)
+ - ✔ 같은 노선에 중복된 역 추가 불가
+ - ✔ 노선 내에서 갈래길이 존재할 수 없음
+ - ✔ 가장 큰 순서보다 큰 수 입력 불가
+ - ✔ 0부터 입력가능
+- ✔ 이때 순서에 맞게 화면이 랜더링된다.
+- ✔ 또한, 순서 0이 입력되면, 상행 종점이 된다.
+
+- ✔ 노선에서 역을 삭제할 수 있으며, 화면에서 삭제한다.
+
+ - 예외처리)
+ - ✔ 한 노선에서 역이 두 개 이하일 때 삭제 불가
+ - ✔ 이때, 종점을 삭제하면 종점 전의 역이 종점이 되고, 화면을 랜더링한다.
+
+### 노선도 출력
+
+- ✔ 노선 별 상행 종점부터 하행 종점까지 연결된 순서대로 역 목록을 화면에 출력한다.
diff --git a/index.html b/index.html
index fc99deac2..bf34b9a8b 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,73 @@
-
🚇 지하철 노선도 관리
+
🚇 지하철 노선도 관리
+
1. 역 관리
+
2. 노선 관리
+
3. 구간 관리
+
4. 지하철 노선도 출력
+
+
+ 노선 이름
+
+
+
+ 상행 종점
+
+
+
+ 하행 종점
+
+
+
+ 노선 추가
+ 🚉 지하철 노선 목록
+
+
+
+ 노선 이름
+ 상행 종점역
+ 하행 종점역
+ 설정
+
+
+
+
+
+ 구간을 수정할 노선을 선택해주세요.
+
+
+
+
diff --git a/src/Controller/line-control.js b/src/Controller/line-control.js
new file mode 100644
index 000000000..e7685d7d3
--- /dev/null
+++ b/src/Controller/line-control.js
@@ -0,0 +1,81 @@
+import {
+ $lineNameInput,
+ $upStreamSelector,
+ $downStreamSelector,
+} from '../View/element.js';
+import {
+ addLineScreen,
+ addMapPrint,
+ addSectionButton,
+ addSectionScreen,
+ addSelectorOption,
+} from '../View/add-screen.js';
+import {
+ removeLineScreen,
+ removeSectionButton,
+ removeSectionScreen,
+ removeMapPrint,
+} from '../View/remove-screen.js';
+import {hideSectionEditContainer} from '../View/hide-screen.js';
+import {
+ setLocalStorage,
+ getLocalStorage,
+ removeLocalStorage,
+} from './local-storage.js';
+import {stationInstance, lineInstance} from '../index.js';
+import {
+ setLineButtonDeleteEvent,
+ setSectionButtonLoadEvent,
+ setSectionButtonDeleteEvent,
+} from './set-button-event.js';
+import {isLineInputValid} from './valid.js';
+import {KEY, TEXT} from '../constant.js';
+
+export const onAddLine = () => {
+ const lineValue = getLineValue();
+ if (isLineInputValid(lineValue, lineInstance.lines)) {
+ setLocalStorage(KEY.LINE, lineValue);
+ lineInstance.addLine(lineValue);
+ addLineScreen(lineValue);
+ addSectionButton(lineValue.lineName);
+ addSectionScreen(lineValue);
+ addMapPrint([lineValue]);
+ setSectionButtonDeleteEvent(lineValue);
+ setLineButtonDeleteEvent(lineValue.lineName);
+ setSectionButtonLoadEvent(lineValue.lineName);
+ }
+ $lineNameInput.value = '';
+};
+
+export const onRemoveLine = (e) => {
+ const removeConfirm = confirm(TEXT.CONFIRM_DELETE);
+ if (removeConfirm) {
+ removeLocalStorage(KEY.LINE, e.target.dataset.lineName);
+ lineInstance.removeLine(e.target.dataset.lineName);
+ removeLineScreen(e.target);
+ removeSectionButton(e.target.dataset.lineName);
+ removeSectionScreen(e.target.dataset.lineName);
+ removeMapPrint(e.target.dataset.lineName);
+ hideSectionEditContainer();
+ }
+};
+
+export const loadLine = () => {
+ const lines = getLocalStorage(KEY.LINE);
+ lineInstance.loadLine(lines);
+ stationInstance.stations.forEach((station) => {
+ addSelectorOption($upStreamSelector, station);
+ addSelectorOption($downStreamSelector, station);
+ });
+ lineInstance.lines.forEach((line) => {
+ addLineScreen(line);
+ setLineButtonDeleteEvent(line.lineName);
+ });
+};
+
+const getLineValue = () => {
+ return {
+ lineName: $lineNameInput.value,
+ station: [$upStreamSelector.value, $downStreamSelector.value],
+ };
+};
diff --git a/src/Controller/local-storage.js b/src/Controller/local-storage.js
new file mode 100644
index 000000000..3b0f52136
--- /dev/null
+++ b/src/Controller/local-storage.js
@@ -0,0 +1,59 @@
+import {KEY} from '../constant.js';
+
+export const addSectionOnLocalStorage = (key, value) => {
+ const localStorageValue = getLocalStorage(key);
+ const sectionIndex = localStorageValue.findIndex(
+ (line) => line.lineName === value.lineName,
+ );
+ localStorageValue[sectionIndex].station.splice(
+ value.number,
+ 0,
+ value.sectionName,
+ );
+
+ return localStorage.setItem(key, JSON.stringify([...localStorageValue]));
+};
+
+export const setLocalStorage = (key, value) => {
+ const localStorageValue = getLocalStorage(key);
+ if (localStorageValue === null) {
+ return localStorage.setItem(key, JSON.stringify([value]));
+ }
+
+ return localStorage.setItem(
+ key,
+ JSON.stringify([...localStorageValue, value]),
+ );
+};
+
+export const getLocalStorage = (key) => {
+ const localStorageValue = JSON.parse(localStorage.getItem(key));
+
+ return localStorageValue;
+};
+
+export const removeSectionOnLocalStorage = (key, value) => {
+ const localStorageValue = getLocalStorage(key);
+ const sectionIndex = localStorageValue.findIndex(
+ (line) => line.lineName === value.lineName,
+ );
+ localStorageValue[sectionIndex].station = localStorageValue[
+ sectionIndex
+ ].station.filter((station) => station !== value.station);
+
+ return localStorage.setItem(key, JSON.stringify([...localStorageValue]));
+};
+
+export const removeLocalStorage = (key, value) => {
+ const localStorageValue = getLocalStorage(key);
+ const filteredStorage = localStorageValue.filter((storage) => {
+ if (key === KEY.STATION) {
+ return storage !== value;
+ }
+ if (key === KEY.LINE) {
+ return storage.lineName !== value;
+ }
+ });
+
+ return localStorage.setItem(key, JSON.stringify(filteredStorage));
+};
diff --git a/src/Controller/map-print-control.js b/src/Controller/map-print-control.js
new file mode 100644
index 000000000..545600dc5
--- /dev/null
+++ b/src/Controller/map-print-control.js
@@ -0,0 +1,7 @@
+import {addMapPrint} from '../View/add-screen.js';
+import {lineInstance} from '../index.js';
+
+export const loadMapPrint = () => {
+ lineInstance.loadLine();
+ addMapPrint(lineInstance.lines);
+};
diff --git a/src/Controller/section-control.js b/src/Controller/section-control.js
new file mode 100644
index 000000000..034e01118
--- /dev/null
+++ b/src/Controller/section-control.js
@@ -0,0 +1,117 @@
+import {
+ $sectionAddButton,
+ $sectionOrderInput,
+ $sectionSelector,
+ $sectionEditContainer,
+ $lineContainer,
+} from '../View/element.js';
+import {
+ addLineTitle,
+ addSelectorOption,
+ addSectionScreen,
+ addSectionButton,
+ addLineScreen,
+ addMapPrint,
+} from '../View/add-screen.js';
+import {removeAllMapPrint, removeTableScreen} from '../View/remove-screen.js';
+import {showSectionScreen} from '../View/show-screen.js';
+import {hideSectionLine} from '../View/hide-screen.js';
+import {isSectionValid, isMoreThanTwoStation} from './valid.js';
+import {
+ addSectionOnLocalStorage,
+ removeSectionOnLocalStorage,
+} from './local-storage.js';
+import {stationInstance, lineInstance} from '../index.js';
+import {
+ setLineButtonDeleteEvent,
+ setSectionButtonDeleteEvent,
+ setSectionButtonLoadEvent,
+} from './set-button-event.js';
+import {KEY, TEXT} from '../constant.js';
+
+export const onLoadSection = (e) => {
+ hideSectionLine();
+ addLineTitle(e.target.dataset.lineName);
+ showSectionScreen(e.target.dataset.lineName);
+ $sectionAddButton.dataset.lineName = e.target.dataset.lineName;
+};
+
+export const onAddSection = (e) => {
+ const sectionValue = getSectionValue(e.target.dataset.lineName);
+ const selectedSection = getSelectedSection(sectionValue.lineName);
+ if (isSectionValid(sectionValue, selectedSection.station)) {
+ addSectionOnLocalStorage(KEY.LINE, sectionValue);
+ lineInstance.updateAddLine(selectedSection, sectionValue);
+ updateSectionTable();
+ showSectionScreen(e.target.dataset.lineName);
+ }
+ $sectionOrderInput.value = '';
+};
+
+export const onRemoveSection = (e) => {
+ const removeConfirm = confirm(TEXT.CONFIRM_DELETE);
+ const parsedData = JSON.parse(e.target.dataset.sectionLine);
+ const removedData = getRemovedData(parsedData);
+ const selectedSection = getSelectedSection(removedData.lineName);
+ if (removeConfirm && isMoreThanTwoStation(selectedSection.station)) {
+ removeSectionOnLocalStorage(KEY.LINE, removedData);
+ lineInstance.updateRemoveLine(selectedSection, parsedData.station);
+ updateSectionTable();
+ showSectionScreen(parsedData.lineName);
+ }
+};
+
+export const loadSectionTable = () => {
+ lineInstance.loadLine();
+ stationInstance.stations.forEach((station) =>
+ addSelectorOption($sectionSelector, station),
+ );
+ lineInstance.lines.forEach((line) => {
+ addSectionScreen(line);
+ setSectionButtonLoadEvent(line.lineName);
+ setSectionButtonDeleteEvent(line);
+ });
+};
+
+export const loadSectionButton = () => {
+ lineInstance.loadLine();
+ lineInstance.lines.forEach((line) => {
+ addSectionButton(line.lineName);
+ });
+};
+
+export const updateSectionTable = () => {
+ removeTableScreen($sectionEditContainer);
+ removeTableScreen($lineContainer);
+ removeAllMapPrint();
+ lineInstance.lines.forEach((line) => {
+ addLineScreen(line);
+ addSectionScreen(line);
+ setLineButtonDeleteEvent(line.lineName);
+ setSectionButtonDeleteEvent(line);
+ });
+ addMapPrint(lineInstance.lines);
+};
+
+const getRemovedData = (data) => {
+ return {
+ lineName: data.lineName,
+ station: data.station,
+ };
+};
+
+const getSectionValue = (lineName) => {
+ return {
+ lineName,
+ sectionName: $sectionSelector.value,
+ number: $sectionOrderInput.value,
+ };
+};
+
+const getSelectedSection = (lineName) => {
+ const sectionIndex = lineInstance.lines.findIndex(
+ (line) => line.lineName === lineName,
+ );
+
+ return lineInstance.lines[sectionIndex];
+};
diff --git a/src/Controller/set-button-event.js b/src/Controller/set-button-event.js
new file mode 100644
index 000000000..f960ca7c5
--- /dev/null
+++ b/src/Controller/set-button-event.js
@@ -0,0 +1,38 @@
+import {
+ $stationTbody,
+ $lineTbody,
+ $sectionEditButtonContainer,
+ $sectionTbody,
+} from '../View/element.js';
+import {onRemoveStation} from './station-control.js';
+import {onRemoveLine} from './line-control.js';
+import {onLoadSection, onRemoveSection} from './section-control.js';
+
+export const setStationButtonDeleteEvent = (stationName) => {
+ const $button = $stationTbody.querySelector(
+ `[data-station-name='${stationName}']`,
+ );
+ $button.addEventListener('click', onRemoveStation);
+};
+
+export const setLineButtonDeleteEvent = (lineName) => {
+ const $button = $lineTbody.querySelector(`[data-line-name='${lineName}']`);
+ $button.addEventListener('click', onRemoveLine);
+};
+
+export const setSectionButtonLoadEvent = (lineName) => {
+ const $button = $sectionEditButtonContainer.querySelector(
+ `[data-line-name='${lineName}']`,
+ );
+ $button.addEventListener('click', onLoadSection);
+};
+
+export const setSectionButtonDeleteEvent = (line) => {
+ const $lineAllTr = $sectionTbody.querySelectorAll(
+ `[data-line-name='${line.lineName}']`,
+ );
+
+ return $lineAllTr.forEach(($tr) => {
+ $tr.querySelector('button').addEventListener('click', onRemoveSection);
+ });
+};
diff --git a/src/Controller/station-control.js b/src/Controller/station-control.js
new file mode 100644
index 000000000..4b1f700bf
--- /dev/null
+++ b/src/Controller/station-control.js
@@ -0,0 +1,57 @@
+import {
+ $stationNameInput,
+ $upStreamSelector,
+ $downStreamSelector,
+ $sectionSelector,
+} from '../View/element.js';
+import {addStationScreen, addSelectorOption} from '../View/add-screen.js';
+import {
+ removeStationScreen,
+ removeSelectorOption,
+} from '../View/remove-screen.js';
+import {
+ setLocalStorage,
+ getLocalStorage,
+ removeLocalStorage,
+} from './local-storage.js';
+import {isNotLineHaved, isStationInputVaild} from './valid.js';
+import {stationInstance, lineInstance} from '../index.js';
+import {setStationButtonDeleteEvent} from './set-button-event.js';
+import {KEY, TEXT} from '../constant.js';
+
+export const loadStation = () => {
+ const stations = getLocalStorage(KEY.STATION);
+ stationInstance.loadStation(stations);
+ stationInstance.stations.forEach((stationName) => {
+ addStationScreen(stationName);
+ setStationButtonDeleteEvent(stationName);
+ });
+};
+
+export const onAddStation = () => {
+ if (isStationInputVaild($stationNameInput.value, stationInstance.stations)) {
+ setLocalStorage(KEY.STATION, $stationNameInput.value);
+ stationInstance.addStation($stationNameInput.value);
+ addStationScreen($stationNameInput.value);
+ addSelectorOption($upStreamSelector, $stationNameInput.value);
+ addSelectorOption($downStreamSelector, $stationNameInput.value);
+ addSelectorOption($sectionSelector, $stationNameInput.value);
+ setStationButtonDeleteEvent($stationNameInput.value);
+ }
+ $stationNameInput.value = '';
+};
+
+export const onRemoveStation = (e) => {
+ const removeConfirm = confirm(TEXT.CONFIRM_DELETE);
+ if (
+ removeConfirm &&
+ isNotLineHaved(e.target.dataset.stationName, lineInstance.lines)
+ ) {
+ removeLocalStorage(KEY.STATION, e.target.dataset.stationName);
+ stationInstance.removeStation(e.target.dataset.stationName);
+ removeStationScreen(e.target);
+ removeSelectorOption($upStreamSelector, e.target.dataset.stationName);
+ removeSelectorOption($downStreamSelector, e.target.dataset.stationName);
+ removeSelectorOption($sectionSelector, e.target.dataset.stationName);
+ }
+};
diff --git a/src/Controller/valid.js b/src/Controller/valid.js
new file mode 100644
index 000000000..ba5e85660
--- /dev/null
+++ b/src/Controller/valid.js
@@ -0,0 +1,115 @@
+import {ERROR_MESSAGE, MIN_LETTER, MIN_LINE_LENGTH} from '../constant.js';
+
+export const isStationInputVaild = (station, exStation) => {
+ if (!isMatched(station)) {
+ return false;
+ }
+ if (!isAllDifferentStation(station, exStation)) {
+ return false;
+ }
+
+ return true;
+};
+
+export const isLineInputValid = (userLine, exLine) => {
+ if (!isMatched(userLine.lineName)) {
+ return false;
+ }
+ if (!isAllDifferentLine(userLine.lineName, exLine)) {
+ return false;
+ }
+ if (!isLastStopDifferent(userLine.station)) {
+ return false;
+ }
+
+ return true;
+};
+
+export const isSectionValid = (userSection, exSection) => {
+ if (!isNumber(userSection.number)) {
+ return false;
+ }
+ if (!isAllDifferentSection(userSection, exSection)) {
+ return false;
+ }
+ if (!isMoreThanMaxNumber(userSection, exSection)) {
+ return false;
+ }
+
+ return true;
+};
+
+export const isNotLineHaved = (station, lines) => {
+ if (lines.find((line) => line.station.includes(station))) {
+ return alert(ERROR_MESSAGE.LINE_HAVE_SAME_STATION);
+ }
+
+ return true;
+};
+
+export const isMoreThanTwoStation = (line) => {
+ if (line.length <= MIN_LINE_LENGTH) {
+ return alert(ERROR_MESSAGE.LESS_THAN_TWO_STATION);
+ }
+
+ return true;
+};
+
+const isMatched = (value) => {
+ if (value.match(/[^가-힣0-9]/)) {
+ return alert(ERROR_MESSAGE.WRONG_INPUT);
+ }
+ if (value.length < MIN_LETTER) {
+ return alert(ERROR_MESSAGE.LESS_THAN_ONE_LETTER);
+ }
+
+ return true;
+};
+
+const isAllDifferentStation = (station, allStations) => {
+ if (allStations && allStations.includes(station)) {
+ return alert(ERROR_MESSAGE.SAME_STATION);
+ }
+
+ return true;
+};
+
+const isAllDifferentLine = (userLine, allLines) => {
+ if (allLines.find((line) => line.lineName === userLine)) {
+ return alert(ERROR_MESSAGE.SAME_LINE);
+ }
+
+ return true;
+};
+
+const isAllDifferentSection = (userSection, allSection) => {
+ if (allSection.find((station) => station === userSection.sectionName)) {
+ return alert(ERROR_MESSAGE.ALREADY_INCLUDE_STATION);
+ }
+
+ return true;
+};
+
+const isLastStopDifferent = (station) => {
+ if (station[0] === station[station.length - 1]) {
+ return alert(ERROR_MESSAGE.SAME_LAST_STOP);
+ }
+
+ return true;
+};
+
+const isNumber = (number) => {
+ if (number.match(/\D/) || number === '') {
+ return alert(ERROR_MESSAGE.NOT_NUMBER);
+ }
+
+ return true;
+};
+
+const isMoreThanMaxNumber = (userSection, section) => {
+ if (section.length < userSection.number) {
+ return alert(`❌ ${section.length}${ERROR_MESSAGE.LENGTH_LIMIT}`);
+ }
+
+ return true;
+};
diff --git a/src/Model/line.js b/src/Model/line.js
new file mode 100644
index 000000000..8eec3bacb
--- /dev/null
+++ b/src/Model/line.js
@@ -0,0 +1,39 @@
+export default class Line {
+ constructor() {
+ this.lines = [];
+ }
+
+ loadLine(lines) {
+ if (lines) {
+ return (this.lines = lines);
+ }
+ }
+
+ addLine(line) {
+ return this.lines.push(line);
+ }
+
+ removeLine(lineName) {
+ const removedLineIndex = this.lines.findIndex(
+ (v) => v.lineName === lineName,
+ );
+
+ return this.lines.splice(removedLineIndex, 1);
+ }
+
+ updateAddLine(selectedLine, lineValue) {
+ return selectedLine.station.splice(
+ lineValue.number,
+ 0,
+ lineValue.sectionName,
+ );
+ }
+
+ updateRemoveLine(selectedLine, removedSectionName) {
+ const removedSectionIndex = selectedLine.station.indexOf(
+ removedSectionName,
+ );
+
+ return selectedLine.station.splice(removedSectionIndex, 1);
+ }
+}
diff --git a/src/Model/station.js b/src/Model/station.js
new file mode 100644
index 000000000..add85cc11
--- /dev/null
+++ b/src/Model/station.js
@@ -0,0 +1,21 @@
+export default class Station {
+ constructor() {
+ this.stations = [];
+ }
+
+ loadStation(stations) {
+ if (stations) {
+ return (this.stations = stations);
+ }
+ }
+
+ addStation(stationName) {
+ return this.stations.push(stationName);
+ }
+
+ removeStation(stationName) {
+ const removedStationIndex = this.stations.indexOf(stationName);
+
+ return this.stations.splice(removedStationIndex, 1);
+ }
+}
diff --git a/src/View/add-screen.js b/src/View/add-screen.js
new file mode 100644
index 000000000..9af1ad441
--- /dev/null
+++ b/src/View/add-screen.js
@@ -0,0 +1,103 @@
+import {
+ $stationTbody,
+ $lineTbody,
+ $sectionEditButtonContainer,
+ $sectionTbody,
+ $sectionEditContainer,
+ $mapContainer,
+} from './element.js';
+import {TEXT, ADD_BUTTON_CLASS} from '../constant.js';
+
+const {
+ STATION_DELETE,
+ LINE_DELETE,
+ SECTION_DELETE,
+ SECTION_LINE_MENU,
+} = ADD_BUTTON_CLASS;
+
+const {DELETE, LINE_MANAGEMENT, DELETE_FROM_LINE} = TEXT;
+
+export const addStationScreen = (stationName) => {
+ const $stationTr = document.createElement('tr');
+ $stationTr.innerHTML = `
+ ${stationName}
+ ${DELETE}
+ `;
+ $stationTr.querySelector('button').dataset.stationName = stationName;
+ $stationTbody.appendChild($stationTr);
+};
+
+export const addSelectorOption = ($selector, stationName) => {
+ const $optionLastStopStation = document.createElement('option');
+ $optionLastStopStation.textContent = stationName;
+ $selector.appendChild($optionLastStopStation);
+};
+
+export const addLineScreen = (line) => {
+ const $lineTr = document.createElement('tr');
+ $lineTr.innerHTML = `
+ ${line.lineName}
+ ${line.station[0]}
+ ${line.station[line.station.length - 1]}
+ ${DELETE}
+ `;
+ $lineTr.querySelector('button').dataset.lineName = line.lineName;
+ $lineTbody.appendChild($lineTr);
+};
+
+export const addLineTitle = (lineName) => {
+ const $lineTitle = $sectionEditContainer.querySelector('h2');
+ $lineTitle.textContent = `${lineName} ${LINE_MANAGEMENT}`;
+};
+
+export const addSectionScreen = (line) => {
+ for (let i = 0; i < line.station.length; i++) {
+ const $sectionTr = document.createElement('tr');
+ $sectionTr.innerHTML = `
+ ${i}
+ ${line.station[i]}
+ ${DELETE_FROM_LINE}
+ `;
+ const sectionData = {lineName: line.lineName, station: line.station[i]};
+ setSectionData($sectionTr, sectionData);
+ $sectionTbody.appendChild($sectionTr);
+ }
+};
+
+const setSectionData = ($sectionTr, sectionData) => {
+ $sectionTr.querySelector('button').dataset.sectionLine = JSON.stringify(
+ sectionData,
+ );
+ $sectionTr.dataset.lineName = sectionData.lineName;
+ $sectionTr.style.display = 'none';
+};
+
+export const addSectionButton = (lineName) => {
+ const $sectionButton = document.createElement('button');
+ $sectionButton.className = SECTION_LINE_MENU;
+ $sectionButton.textContent = lineName;
+ $sectionButton.dataset.lineName = lineName;
+ $sectionEditButtonContainer.appendChild($sectionButton);
+};
+
+export const addMapPrint = (lines) => {
+ for (let i = 0; i < lines.length; i++) {
+ const $lineMap = document.createElement('div');
+ const $lineTitle = document.createElement('h3');
+ const $lineUl = document.createElement('ul');
+ $lineMap.dataset.lineName = lines[i].lineName;
+ $lineTitle.textContent = lines[i].lineName;
+ addMapStation($lineUl, lines[i].station);
+ $lineMap.appendChild($lineTitle);
+ $lineMap.appendChild($lineUl);
+ $mapContainer.appendChild($lineMap);
+ }
+};
+
+const addMapStation = ($ul, stations) => {
+ stations.forEach((stationName) => {
+ const $stationLi = document.createElement('li');
+ $stationLi.textContent = stationName;
+ $ul.appendChild($stationLi);
+ });
+};
diff --git a/src/View/element.js b/src/View/element.js
new file mode 100644
index 000000000..776e463b4
--- /dev/null
+++ b/src/View/element.js
@@ -0,0 +1,41 @@
+export const $screenAllButton = document.body.querySelectorAll('#app > button');
+
+export const $stationContainer = document.body.querySelector('#station');
+export const $stationTbody = $stationContainer.querySelector('table > tbody');
+export const $stationNameInput = $stationContainer.querySelector(
+ '#station-name-input',
+);
+export const $stationAddButton = $stationContainer.querySelector(
+ '#station-add-button',
+);
+
+export const $lineContainer = document.body.querySelector('#line');
+export const $lineTbody = $lineContainer.querySelector('table > tbody');
+export const $upStreamSelector = $lineContainer.querySelector(
+ '#line-start-station-selector',
+);
+export const $downStreamSelector = $lineContainer.querySelector(
+ '#line-end-station-selector',
+);
+export const $lineAddButton = $lineContainer.querySelector('#line-add-button');
+export const $lineNameInput = $lineContainer.querySelector('#line-name-input');
+
+export const $sectionContainer = document.body.querySelector('#subway-section');
+export const $sectionTbody = $sectionContainer.querySelector('table > tbody');
+export const $sectionEditButtonContainer = $sectionContainer.querySelector(
+ '#section-select-button',
+);
+export const $sectionEditContainer = $sectionContainer.querySelector(
+ '#subway-section-edit',
+);
+export const $sectionAddButton = $sectionContainer.querySelector(
+ '#section-add-button',
+);
+export const $sectionOrderInput = $sectionContainer.querySelector(
+ '#section-order-input',
+);
+export const $sectionSelector = $sectionContainer.querySelector(
+ '#section-station-selector',
+);
+
+export const $mapContainer = document.body.querySelector('#map-section');
diff --git a/src/View/hide-screen.js b/src/View/hide-screen.js
new file mode 100644
index 000000000..0ed44ff1e
--- /dev/null
+++ b/src/View/hide-screen.js
@@ -0,0 +1,17 @@
+import {$sectionContainer, $sectionEditContainer} from './element.js';
+
+export const hideScreen = () => {
+ const $allSection = document.body.querySelectorAll('section');
+ $allSection.forEach((section) => (section.style.display = 'none'));
+};
+
+export const hideSectionLine = () => {
+ const $sectionLines = $sectionContainer.querySelectorAll('tr');
+ for (let i = 1; i < $sectionLines.length; i++) {
+ $sectionLines[i].style.display = 'none';
+ }
+};
+
+export const hideSectionEditContainer = () => {
+ $sectionEditContainer.style.display = 'none';
+};
diff --git a/src/View/remove-screen.js b/src/View/remove-screen.js
new file mode 100644
index 000000000..2ce68533d
--- /dev/null
+++ b/src/View/remove-screen.js
@@ -0,0 +1,68 @@
+import {
+ $stationTbody,
+ $lineTbody,
+ $sectionEditButtonContainer,
+ $mapContainer,
+ $sectionTbody,
+} from './element.js';
+
+export const removeStationScreen = (button) => {
+ const $stationTr = button.parentElement.parentElement;
+ $stationTbody.removeChild($stationTr);
+};
+
+export const removeSelectorOption = ($stationSelector, stationName) => {
+ const $stationSelectorOptions = $stationSelector.querySelectorAll('option');
+ const removedStationOption = Array.from($stationSelectorOptions).find(
+ (option) => option.value === stationName,
+ );
+ $stationSelector.removeChild(removedStationOption);
+};
+
+export const removeLineScreen = (button) => {
+ const $lineTr = button.parentElement.parentElement;
+ $lineTbody.removeChild($lineTr);
+};
+
+export const removeTableScreen = ($container) => {
+ const $tbody = $container.querySelector('table > tbody');
+ const $tr = $container.querySelectorAll('tr');
+ for (let i = 1; i < $tr.length; i++) {
+ $tbody.removeChild($tr[i]);
+ }
+};
+
+export const removeSectionScreen = (lineName) => {
+ const $allSectionTr = $sectionTbody.querySelectorAll('tr');
+ const $allDeletedSectionTr = Array.from($allSectionTr).filter(
+ (tr) => tr.dataset.lineName === lineName,
+ );
+
+ for (let i = 0; i < $allDeletedSectionTr.length; i++) {
+ $sectionTbody.removeChild($allDeletedSectionTr[i]);
+ }
+};
+
+export const removeSectionButton = (lineName) => {
+ const $sectionAllLineButton = $sectionEditButtonContainer.querySelectorAll(
+ 'button',
+ );
+ const $removedSectionButton = Array.from($sectionAllLineButton).find(
+ (button) => button.textContent === lineName,
+ );
+ $sectionEditButtonContainer.removeChild($removedSectionButton);
+};
+
+export const removeMapPrint = (lineName) => {
+ const $mapAllLines = $mapContainer.querySelectorAll('div');
+ const $removedMapLine = Array.from($mapAllLines).find(
+ (map) => map.dataset.lineName === lineName,
+ );
+ $mapContainer.removeChild($removedMapLine);
+};
+
+export const removeAllMapPrint = () => {
+ while ($mapContainer.firstChild) {
+ $mapContainer.removeChild($mapContainer.firstChild);
+ }
+};
diff --git a/src/View/show-screen.js b/src/View/show-screen.js
new file mode 100644
index 000000000..ee07d152d
--- /dev/null
+++ b/src/View/show-screen.js
@@ -0,0 +1,34 @@
+import {
+ $stationContainer,
+ $lineContainer,
+ $sectionContainer,
+ $mapContainer,
+ $sectionEditContainer,
+} from './element.js';
+import {BUTTON_MANAGEMENT_ID} from '../constant.js';
+
+export const showScreen = (e) => {
+ const {STATION, LINE, SECTION, MAP} = BUTTON_MANAGEMENT_ID;
+ if (e.target.id === STATION) {
+ return ($stationContainer.style.display = 'block');
+ }
+ if (e.target.id === LINE) {
+ return ($lineContainer.style.display = 'block');
+ }
+ if (e.target.id === SECTION) {
+ return ($sectionContainer.style.display = 'block');
+ }
+ if (e.target.id === MAP) {
+ return ($mapContainer.style.display = 'block');
+ }
+};
+
+export const showSectionScreen = (lineName) => {
+ const $allSectionTr = $sectionEditContainer.querySelectorAll('tr');
+ $sectionEditContainer.style.display = 'block';
+ $allSectionTr.forEach((tr) => {
+ if (tr.dataset.lineName === lineName) {
+ tr.style.display = 'table-row';
+ }
+ });
+};
diff --git a/src/constant.js b/src/constant.js
new file mode 100644
index 000000000..d43f31b80
--- /dev/null
+++ b/src/constant.js
@@ -0,0 +1,41 @@
+export const BUTTON_MANAGEMENT_ID = {
+ STATION: 'station-manager-button',
+ LINE: 'line-manager-button',
+ SECTION: 'section-manager-button',
+ MAP: 'map-print-manager-button',
+};
+
+export const ADD_BUTTON_CLASS = {
+ STATION_DELETE: 'station-delete-button',
+ LINE_DELETE: 'line-delete-button',
+ SECTION_DELETE: 'section-delete-button',
+ SECTION_LINE_MENU: 'section-line-menu-button',
+};
+
+export const TEXT = {
+ LINE_MANAGEMENT: '관리',
+ DELETE_FROM_LINE: '노선에서 삭제',
+ DELETE: '삭제',
+ CONFIRM_DELETE: '❗ 정말로 삭제하시겠습니까?',
+};
+
+export const KEY = {
+ STATION: 'station',
+ LINE: 'line',
+};
+
+export const ERROR_MESSAGE = {
+ LINE_HAVE_SAME_STATION: '❌ 노선에 존재하는 역은 삭제할 수 없습니다.',
+ LESS_THAN_TWO_STATION: '❌ 노선에 역이 2개 이하이면 삭제할 수 없습니다.',
+ WRONG_INPUT: '❌ 띄어쓰기 없이 한글과 숫자만 입력해주세요.',
+ LESS_THAN_ONE_LETTER: '❌ 2글자 이상으로 입력해주세요.',
+ SAME_STATION: '❌ 같은 이름의 역이 존재합니다.',
+ SAME_LINE: '❌ 같은 이름의 노선이 존재합니다.',
+ ALREADY_INCLUDE_STATION: '❌ 이미 역이 추가되어 있습니다.',
+ SAME_LAST_STOP: '❌ 상행과 하행이 같은 역이 될 수 없습니다.',
+ NOT_NUMBER: '❌ 0이상의 숫자를 입력해주세요.',
+ LENGTH_LIMIT: '이하의 수를 입력해주세요.',
+};
+
+export const MIN_LETTER = 2;
+export const MIN_LINE_LENGTH = 2;
diff --git a/src/index.js b/src/index.js
index e69de29bb..033302c42 100644
--- a/src/index.js
+++ b/src/index.js
@@ -0,0 +1,41 @@
+import Station from './Model/station.js';
+import Line from './Model/line.js';
+import {
+ $screenAllButton,
+ $stationAddButton,
+ $lineAddButton,
+ $sectionAddButton,
+} from './View/element.js';
+import {hideScreen} from './View/hide-screen.js';
+import {showScreen} from './View/show-screen.js';
+import {onAddStation, loadStation} from './Controller/station-control.js';
+import {onAddLine, loadLine} from './Controller/line-control.js';
+import {
+ onAddSection,
+ loadSectionButton,
+ loadSectionTable,
+} from './Controller/section-control.js';
+import {loadMapPrint} from './Controller/map-print-control.js';
+
+export const stationInstance = new Station();
+export const lineInstance = new Line();
+
+const onChangeScreen = (e) => {
+ hideScreen();
+ showScreen(e);
+};
+
+(function gameStart() {
+ $screenAllButton.forEach((button) =>
+ button.addEventListener('click', onChangeScreen),
+ );
+ $stationAddButton.addEventListener('click', onAddStation);
+ $lineAddButton.addEventListener('click', onAddLine);
+ $sectionAddButton.addEventListener('click', onAddSection);
+
+ loadStation();
+ loadLine();
+ loadSectionButton();
+ loadSectionTable();
+ loadMapPrint();
+})();