refactor(frontend): 공통 모듈 common/ 분리 + OpenLayers 제거 + path alias 설정

- OpenLayers(ol) 패키지 제거 (미사용, import 0건)
- common/ 디렉토리 생성: components, hooks, services, store, types, utils
- 17개 공통 파일을 common/으로 이동 (git mv, blame 이력 보존)
- MainTab 타입을 App.tsx에서 common/types/navigation.ts로 분리
- tsconfig path alias (@common/*, @tabs/*) + vite resolve.alias 설정
- 42개 import 경로를 @common/ alias 또는 상대경로로 수정

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-28 14:00:50 +09:00
부모 411b32caad
커밋 a61864646f
44개의 변경된 파일62개의 추가작업 그리고 254개의 파일을 삭제

파일 보기

@ -19,7 +19,6 @@
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"lucide-react": "^0.564.0", "lucide-react": "^0.564.0",
"ol": "^10.8.0",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",
@ -1148,12 +1147,6 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@petamoriken/float16": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz",
"integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==",
"license": "MIT"
},
"node_modules/@react-leaflet/core": { "node_modules/@react-leaflet/core": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
@ -1650,12 +1643,6 @@
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
}, },
"node_modules/@types/rbush": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-4.0.0.tgz",
"integrity": "sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==",
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.2.14", "version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
@ -1966,16 +1953,6 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/@zarrita/storage": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.1.4.tgz",
"integrity": "sha512-qURfJAQcQGRfDQ4J9HaCjGaj3jlJKc66bnRk6G/IeLUsM7WKyG7Bzsuf1EZurSXyc0I4LVcu6HaeQQ4d3kZ16g==",
"license": "MIT",
"dependencies": {
"reference-spec-reader": "^0.2.0",
"unzipit": "1.4.3"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@ -2483,12 +2460,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/earcut": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
"license": "ISC"
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.286", "version": "1.5.286",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz",
@ -2897,12 +2868,6 @@
} }
} }
}, },
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -3051,25 +3016,6 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/geotiff": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-3.0.3.tgz",
"integrity": "sha512-yRoDQDYxWYiB421p0cbxJvdy79OlQW+rxDI9GDbIUeWCAh6YAZ0vlTKF448EAiEuuUpBsNaegd2flavF0p+kvw==",
"license": "MIT",
"dependencies": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.5.0",
"xml-utils": "^1.10.2",
"zstddec": "^0.2.0"
},
"engines": {
"node": ">=10.19"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@ -3410,12 +3356,6 @@
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==",
"license": "Apache-2.0"
},
"node_modules/levn": { "node_modules/levn": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@ -3633,15 +3573,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/numcodecs": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz",
"integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==",
"license": "MIT",
"dependencies": {
"fflate": "^0.8.0"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -3662,24 +3593,6 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/ol": {
"version": "10.8.0",
"resolved": "https://registry.npmjs.org/ol/-/ol-10.8.0.tgz",
"integrity": "sha512-kLk7jIlJvKyhVMAjORTXKjzlM6YIByZ1H/d0DBx3oq8nSPCG6/gbLr5RxukzPgwbhnAqh+xHNCmrvmFKhVMvoQ==",
"license": "BSD-2-Clause",
"dependencies": {
"@types/rbush": "4.0.0",
"earcut": "^3.0.0",
"geotiff": "^3.0.2",
"pbf": "4.0.1",
"rbush": "^4.0.0",
"zarrita": "^0.6.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -3730,12 +3643,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -3749,12 +3656,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/parse-headers": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.6.tgz",
"integrity": "sha512-Tz11t3uKztEW5FEVZnj1ox8GKblWn+PvHY9TmJV5Mll2uHEwRdR/5Li1OlXoECjLYkApdhWy44ocONwXLiKO5A==",
"license": "MIT"
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -3782,18 +3683,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pbf": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz",
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
"license": "BSD-3-Clause",
"dependencies": {
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -4007,12 +3896,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"license": "MIT"
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -4050,33 +3933,6 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/quick-lru": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz",
"integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quickselect": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz",
"integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
"license": "ISC"
},
"node_modules/rbush": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz",
"integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==",
"license": "MIT",
"dependencies": {
"quickselect": "^3.0.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.4", "version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@ -4158,12 +4014,6 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/reference-spec-reader": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz",
"integrity": "sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==",
"license": "MIT"
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.11", "version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@ -4195,15 +4045,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"license": "MIT",
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/reusify": { "node_modules/reusify": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@ -4598,18 +4439,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/unzipit": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz",
"integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==",
"license": "MIT",
"dependencies": {
"uzip-module": "^1.0.2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@ -4658,12 +4487,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/uzip-module": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz",
"integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==",
"license": "MIT"
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
@ -4739,12 +4562,6 @@
} }
} }
}, },
"node_modules/web-worker": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
"integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==",
"license": "Apache-2.0"
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -4792,12 +4609,6 @@
} }
} }
}, },
"node_modules/xml-utils": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz",
"integrity": "sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==",
"license": "CC0-1.0"
},
"node_modules/xmlhttprequest-ssl": { "node_modules/xmlhttprequest-ssl": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
@ -4826,16 +4637,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/zarrita": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.6.1.tgz",
"integrity": "sha512-YOMTW8FT55Rz+vadTIZeOFZ/F2h4svKizyldvPtMYSxPgSNcRkOzkxCsWpIWlWzB1I/LmISmi0bEekOhLlI+Zw==",
"license": "MIT",
"dependencies": {
"@zarrita/storage": "^0.1.4",
"numcodecs": "^0.3.2"
}
},
"node_modules/zod": { "node_modules/zod": {
"version": "4.3.6", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
@ -4859,12 +4660,6 @@
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
} }
}, },
"node_modules/zstddec": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.2.0.tgz",
"integrity": "sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==",
"license": "MIT AND BSD-3-Clause"
},
"node_modules/zustand": { "node_modules/zustand": {
"version": "5.0.11", "version": "5.0.11",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",

파일 보기

@ -21,7 +21,6 @@
"emoji-mart": "^5.6.0", "emoji-mart": "^5.6.0",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"lucide-react": "^0.564.0", "lucide-react": "^0.564.0",
"ol": "^10.8.0",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-leaflet": "^5.0.0", "react-leaflet": "^5.0.0",

파일 보기

@ -1,10 +1,11 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { GoogleOAuthProvider } from '@react-oauth/google' import { GoogleOAuthProvider } from '@react-oauth/google'
import { MainLayout } from './components/layout/MainLayout' import type { MainTab } from '@common/types/navigation'
import { LoginPage } from './components/auth/LoginPage' import { MainLayout } from '@common/components/layout/MainLayout'
import { registerMainTabSwitcher } from './hooks/useSubMenu' import { LoginPage } from '@common/components/auth/LoginPage'
import { useAuthStore } from './store/authStore' import { registerMainTabSwitcher } from '@common/hooks/useSubMenu'
import { useMenuStore } from './store/menuStore' import { useAuthStore } from '@common/store/authStore'
import { useMenuStore } from '@common/store/menuStore'
import { OilSpillView } from './components/views/OilSpillView' import { OilSpillView } from './components/views/OilSpillView'
import { ReportsView } from './components/views/ReportsView' import { ReportsView } from './components/views/ReportsView'
import { HNSView } from './components/views/HNSView' import { HNSView } from './components/views/HNSView'
@ -17,8 +18,6 @@ import { AdminView } from './components/views/AdminView'
import { PreScatView } from './components/views/PreScatView' import { PreScatView } from './components/views/PreScatView'
import { RescueView } from './components/views/RescueView' import { RescueView } from './components/views/RescueView'
export type MainTab = 'prediction' | 'hns' | 'rescue' | 'reports' | 'aerial' | 'assets' | 'scat' | 'incidents' | 'board' | 'weather' | 'admin'
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || '' const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || ''
function App() { function App() {

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import type { Layer } from '../../data/layerDatabase' import type { Layer } from '../../../data/layerDatabase'
const PRESET_COLORS = [ const PRESET_COLORS = [
'#ef4444','#f97316','#eab308','#22c55e','#06b6d4', '#ef4444','#f97316','#eab308','#22c55e','#06b6d4',

파일 보기

@ -1,5 +1,5 @@
import type { ReactNode } from 'react' import type { ReactNode } from 'react'
import type { MainTab } from '../../App' import type { MainTab } from '../../types/navigation'
import { TopBar } from './TopBar' import { TopBar } from './TopBar'
import { SubMenuBar } from './SubMenuBar' import { SubMenuBar } from './SubMenuBar'

파일 보기

@ -1,4 +1,4 @@
import type { MainTab } from '../../App' import type { MainTab } from '../../types/navigation'
import { useSubMenu } from '../../hooks/useSubMenu' import { useSubMenu } from '../../hooks/useSubMenu'
interface SubMenuBarProps { interface SubMenuBarProps {

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef, useEffect, useMemo } from 'react' import { useState, useRef, useEffect, useMemo } from 'react'
import type { MainTab } from '../../App' import type { MainTab } from '../../types/navigation'
import { useAuthStore } from '../../store/authStore' import { useAuthStore } from '../../store/authStore'
import { useMenuStore } from '../../store/menuStore' import { useMenuStore } from '../../store/menuStore'

파일 보기

@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { fetchAllLayers, fetchLayerTree, fetchWMSLayers } from '../services/api' import { fetchAllLayers, fetchLayerTree, fetchWMSLayers } from '../services/api'
import type { Layer } from '../data/layerDatabase' import type { Layer } from '../../data/layerDatabase'
// 모든 레이어 조회 훅 // 모든 레이어 조회 훅
export function useLayers() { export function useLayers() {

파일 보기

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import type { MainTab } from '../App' import type { MainTab } from '../types/navigation'
interface SubMenuItem { interface SubMenuItem {
id: string id: string

파일 보기

@ -0,0 +1 @@
export type MainTab = 'prediction' | 'hns' | 'rescue' | 'reports' | 'aerial' | 'assets' | 'scat' | 'incidents' | 'board' | 'weather' | 'admin';

파일 보기

@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
const panels = [ const panels = [
{ id: 0, icon: '🌐', label: '개요' }, { id: 0, icon: '🌐', label: '개요' },

파일 보기

@ -1,5 +1,5 @@
import { useRef, useEffect } from 'react' import { useRef, useEffect } from 'react'
import type { BacktrackPhase, BacktrackVessel, BacktrackConditions } from '../../types/backtrack' import type { BacktrackPhase, BacktrackVessel, BacktrackConditions } from '@common/types/backtrack'
interface BacktrackModalProps { interface BacktrackModalProps {
isOpen: boolean isOpen: boolean

파일 보기

@ -1,5 +1,5 @@
import React, { useState, useRef, useMemo } from 'react' import React, { useState, useRef, useMemo } from 'react'
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
import { HNS_SEARCH_DB, type HNSSearchSubstance } from '../../data/hnsSubstanceSearchData' import { HNS_SEARCH_DB, type HNSSearchSubstance } from '../../data/hnsSubstanceSearchData'
/* ═══ HNS 물질 데이터베이스 ═══ */ /* ═══ HNS 물질 데이터베이스 ═══ */

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef } from 'react' import { useState, useRef } from 'react'
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
const theoryTabs = [ const theoryTabs = [
{ icon: '🔬', name: '시스템 개요' }, { icon: '🔬', name: '시스템 개요' },

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef } from 'react' import { useState, useRef } from 'react'
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
const theoryTabs: { id: number; icon: string; name: string; nameColor?: string }[] = [ const theoryTabs: { id: number; icon: string; name: string; nameColor?: string }[] = [
{ id: 0, icon: '🌊', name: '시스템 개요' }, { id: 0, icon: '🌊', name: '시스템 개요' },

파일 보기

@ -1,4 +1,4 @@
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
export function RescueTheoryView() { export function RescueTheoryView() {
const contentHtml = ` const contentHtml = `

파일 보기

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { sanitizeInput } from '../../utils/sanitize' import { sanitizeInput } from '@common/utils/sanitize'
interface BoardPost { interface BoardPost {
id?: number id?: number

파일 보기

@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { ComboBox } from '../ui/ComboBox' import { ComboBox } from '@common/components/ui/ComboBox'
interface HNSLeftPanelProps { interface HNSLeftPanelProps {
activeSubTab: 'analysis' | 'list' activeSubTab: 'analysis' | 'list'

파일 보기

@ -1,15 +1,15 @@
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { LayerTree } from '../layer/LayerTree' import { LayerTree } from '@common/components/layer/LayerTree'
import { useLayerTree } from '../../hooks/useLayers' import { useLayerTree } from '@common/hooks/useLayers'
import { layerData } from '../../data/layerData' import { layerData } from '../../data/layerData'
import type { LayerNode } from '../../data/layerData' import type { LayerNode } from '../../data/layerData'
import type { Layer } from '../../data/layerDatabase' import type { Layer } from '../../data/layerDatabase'
import { decimalToDMS } from '../../utils/coordinates' import { decimalToDMS } from '@common/utils/coordinates'
import { ComboBox } from '../ui/ComboBox' import { ComboBox } from '@common/components/ui/ComboBox'
import { ALL_MODELS } from '../views/OilSpillView' import { ALL_MODELS } from '../views/OilSpillView'
import type { PredictionModel } from '../views/OilSpillView' import type { PredictionModel } from '../views/OilSpillView'
import type { BoomLine, BoomLineCoord, AlgorithmSettings, ContainmentResult } from '../../types/boomLine' import type { BoomLine, BoomLineCoord, AlgorithmSettings, ContainmentResult } from '@common/types/boomLine'
import { generateAIBoomLines, runContainmentAnalysis, computePolylineLength, computeBearing } from '../../utils/geo' import { generateAIBoomLines, runContainmentAnalysis, computePolylineLength, computeBearing } from '@common/utils/geo'
import type { Analysis } from '../analysis/AnalysisListTable' import type { Analysis } from '../analysis/AnalysisListTable'
interface LeftPanelProps { interface LeftPanelProps {

파일 보기

@ -1,4 +1,4 @@
import type { ReplayShip, CollisionEvent } from '../../types/backtrack' import type { ReplayShip, CollisionEvent } from '@common/types/backtrack'
interface BacktrackReplayBarProps { interface BacktrackReplayBarProps {
isPlaying: boolean isPlaying: boolean

파일 보기

@ -1,7 +1,7 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { Polyline, CircleMarker, Circle, Marker, Popup } from 'react-leaflet' import { Polyline, CircleMarker, Circle, Marker, Popup } from 'react-leaflet'
import L from 'leaflet' import L from 'leaflet'
import type { ReplayShip, CollisionEvent, ReplayPathPoint } from '../../types/backtrack' import type { ReplayShip, CollisionEvent, ReplayPathPoint } from '@common/types/backtrack'
interface BacktrackReplayOverlayProps { interface BacktrackReplayOverlayProps {
replayShips: ReplayShip[] replayShips: ReplayShip[]

파일 보기

@ -3,10 +3,10 @@ import { MapContainer, TileLayer, Marker, Popup, useMap, useMapEvents, CircleMar
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import L from 'leaflet' import L from 'leaflet'
import { layerDatabase } from '../../data/layerDatabase' import { layerDatabase } from '../../data/layerDatabase'
import { decimalToDMS } from '../../utils/coordinates' import { decimalToDMS } from '@common/utils/coordinates'
import type { PredictionModel } from '../views/OilSpillView' import type { PredictionModel } from '../views/OilSpillView'
import type { BoomLine, BoomLineCoord } from '../../types/boomLine' import type { BoomLine, BoomLineCoord } from '@common/types/boomLine'
import type { ReplayShip, CollisionEvent } from '../../types/backtrack' import type { ReplayShip, CollisionEvent } from '@common/types/backtrack'
import { BacktrackReplayOverlay } from './BacktrackReplayOverlay' import { BacktrackReplayOverlay } from './BacktrackReplayOverlay'
// Fix Leaflet default icon issue // Fix Leaflet default icon issue

파일 보기

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef } from 'react' import { useState, useEffect, useCallback, useRef } from 'react'
import { useSubMenu } from '../../hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import data from '@emoji-mart/data' import data from '@emoji-mart/data'
import EmojiPicker from '@emoji-mart/react' import EmojiPicker from '@emoji-mart/react'
import { import {
@ -43,8 +43,8 @@ import {
type RegistrationSettings, type RegistrationSettings,
type OAuthSettings, type OAuthSettings,
type MenuConfigItem, type MenuConfigItem,
} from '../../services/authApi' } from '@common/services/authApi'
import { useMenuStore } from '../../store/menuStore' import { useMenuStore } from '@common/store/menuStore'
const DEFAULT_ROLE_COLORS: Record<string, string> = { const DEFAULT_ROLE_COLORS: Record<string, string> = {
ADMIN: 'var(--red)', ADMIN: 'var(--red)',

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import { useSubMenu } from '../../hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { AerialTheoryView } from '../analysis/AerialTheoryView' import { AerialTheoryView } from '../analysis/AerialTheoryView'
type AerialTab = 'media' | 'analysis' | 'realtime' | 'sensor' type AerialTab = 'media' | 'analysis' | 'realtime' | 'sensor'

파일 보기

@ -1,5 +1,5 @@
import { useState } from 'react' import { useState } from 'react'
import { useSubMenu } from '../../hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { BoardWriteForm } from '../board/BoardWriteForm' import { BoardWriteForm } from '../board/BoardWriteForm'
import { BoardDetailView } from '../board/BoardDetailView' import { BoardDetailView } from '../board/BoardDetailView'

파일 보기

@ -7,7 +7,7 @@ import { HNSTheoryView } from '../analysis/HNSTheoryView'
import { HNSSubstanceView } from '../analysis/HNSSubstanceView' import { HNSSubstanceView } from '../analysis/HNSSubstanceView'
import { HNSScenarioView } from '../analysis/HNSScenarioView' import { HNSScenarioView } from '../analysis/HNSScenarioView'
import { HNSRecalcModal } from '../analysis/HNSRecalcModal' import { HNSRecalcModal } from '../analysis/HNSRecalcModal'
import { useSubMenu, navigateToTab, setReportGenCategory } from '../../hooks/useSubMenu' import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu'
/* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */ /* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */
function HNSManualViewer() { function HNSManualViewer() {

파일 보기

@ -8,10 +8,10 @@ import { BoomDeploymentTheoryView } from '../analysis/BoomDeploymentTheoryView'
import { BacktrackModal } from '../analysis/BacktrackModal' import { BacktrackModal } from '../analysis/BacktrackModal'
import { RecalcModal } from '../analysis/RecalcModal' import { RecalcModal } from '../analysis/RecalcModal'
import { BacktrackReplayBar } from '../map/BacktrackReplayBar' import { BacktrackReplayBar } from '../map/BacktrackReplayBar'
import { useSubMenu, navigateToTab, setReportGenCategory } from '../../hooks/useSubMenu' import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu'
import type { BoomLine, AlgorithmSettings, ContainmentResult, BoomLineCoord } from '../../types/boomLine' import type { BoomLine, AlgorithmSettings, ContainmentResult, BoomLineCoord } from '@common/types/boomLine'
import type { BacktrackPhase, BacktrackVessel } from '../../types/backtrack' import type { BacktrackPhase, BacktrackVessel } from '@common/types/backtrack'
import { TOTAL_REPLAY_FRAMES } from '../../types/backtrack' import { TOTAL_REPLAY_FRAMES } from '@common/types/backtrack'
import { MOCK_CONDITIONS, MOCK_VESSELS, MOCK_REPLAY_SHIPS, MOCK_COLLISION } from '../../data/backtrackMockData' import { MOCK_CONDITIONS, MOCK_VESSELS, MOCK_REPLAY_SHIPS, MOCK_COLLISION } from '../../data/backtrackMockData'
export type PredictionModel = 'KOSPS' | 'POSEIDON' | 'OpenDrift' export type PredictionModel = 'KOSPS' | 'POSEIDON' | 'OpenDrift'

파일 보기

@ -9,8 +9,8 @@ import {
type ReportType, type ReportType,
type Jurisdiction, type Jurisdiction,
} from '../reports/OilSpillReportTemplate' } from '../reports/OilSpillReportTemplate'
import { sanitizeHtml } from '../../utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
import { useSubMenu, consumeReportGenCategory } from '../../hooks/useSubMenu' import { useSubMenu, consumeReportGenCategory } from '@common/hooks/useSubMenu'
// ─── Report Export Helpers ────────────────────────────── // ─── Report Export Helpers ──────────────────────────────
function generateReportHTML( function generateReportHTML(

파일 보기

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useSubMenu } from '../../hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { RescueTheoryView } from '../analysis/RescueTheoryView' import { RescueTheoryView } from '../analysis/RescueTheoryView'
import { RescueScenarioView } from '../analysis/RescueScenarioView' import { RescueScenarioView } from '../analysis/RescueScenarioView'

파일 보기

@ -1,4 +1,4 @@
import type { BacktrackConditions, BacktrackVessel, ReplayShip, CollisionEvent } from '../types/backtrack' import type { BacktrackConditions, BacktrackVessel, ReplayShip, CollisionEvent } from '@common/types/backtrack'
export const MOCK_CONDITIONS: BacktrackConditions = { export const MOCK_CONDITIONS: BacktrackConditions = {
estimatedSpillTime: '02-10 06:30', estimatedSpillTime: '02-10 06:30',

파일 보기

@ -1,5 +1,5 @@
// 레이어 데이터베이스 - API에서 가져옴 // 레이어 데이터베이스 - API에서 가져옴
import { fetchAllLayers } from '../services/api' import { fetchAllLayers } from '@common/services/api'
export interface Layer { export interface Layer {
id: string id: string

파일 보기

@ -22,7 +22,14 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true,
/* Path aliases */
"baseUrl": ".",
"paths": {
"@common/*": ["src/common/*"],
"@tabs/*": ["src/tabs/*"]
}
}, },
"include": ["src"] "include": ["src"]
} }

파일 보기

@ -1,7 +1,14 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import path from 'path'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
resolve: {
alias: {
'@common': path.resolve(__dirname, 'src/common'),
'@tabs': path.resolve(__dirname, 'src/tabs'),
},
},
}) })