diff --git a/.gitignore b/.gitignore index 7175c78d4..ee2cfd052 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,7 @@ # dependencies node_modules/ - -# build -.next/ -dist/ +.idea/ # build .next @@ -22,9 +19,14 @@ apps/fo/.next/**/* #.env* .qodo +# next-agents-md +.next-docs/ + pnpm-lock.yaml settings.json .npmrc .cursor +.agent - +apps/**/tsconfig.tsbuildinfo +logs/ diff --git a/.husky/pre-push b/.husky/pre-push index 0ddfdb46e..2adeaead9 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -3,4 +3,8 @@ BRANCH=$(git rev-parse --abbrev-ref HEAD) if [ "$BRANCH" != "main" ]; then exit 0 fi +echo "Branch: $BRANCH" +echo "Changed packages (dry-run):" +pnpm turbo run build --filter="...[origin/$BRANCH]" --dry-run +echo "Starting build..." pnpm turbo run build --filter="...[origin/$BRANCH]" diff --git a/apps/_template/.env.example b/apps/_template/.env.example index b2d706024..f9794a6f8 100644 --- a/apps/_template/.env.example +++ b/apps/_template/.env.example @@ -3,3 +3,6 @@ NEXT_PUBLIC_PORT= NEXT_PUBLIC_PROXY_HOST= NEXT_PUBLIC_SITE_ID= NEXT_PUBLIC_SERVER_API_URL= + +# 401 시 브라우저 리다이렉트 대상 +NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL=/auth/login diff --git a/apps/_template/package.json b/apps/_template/package.json index fb27f8af7..17b53990f 100644 --- a/apps/_template/package.json +++ b/apps/_template/package.json @@ -25,7 +25,7 @@ }, "devDependencies": { "eslint": "^9.0.0", - "eslint-config-next": "15.4.0", + "eslint-config-next": "15.3.8", "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/cm/package.json b/apps/cm/package.json index 9d4e1da8f..bde698393 100644 --- a/apps/cm/package.json +++ b/apps/cm/package.json @@ -52,7 +52,7 @@ "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", "eslint": "^9.0.0", - "eslint-config-next": "15.4.0", + "eslint-config-next": "15.3.8", "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/cm/src/entities/auth/lib/auth-wrapper.tsx b/apps/cm/src/entities/auth/lib/auth-wrapper.tsx index 1aa4f8e61..5366696f7 100644 --- a/apps/cm/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/cm/src/entities/auth/lib/auth-wrapper.tsx @@ -5,16 +5,15 @@ import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; +import { isAtLoginRedirectUrl } from "@common/network"; import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { - // 로그인 페이지에서는 인증 체크를 건너뜀 - if (window.location.pathname === '/auth/login') { + if (isAtLoginRedirectUrl("/auth/login")) { return; } @@ -35,13 +34,8 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); + setUserInfo(null); + if (!isAtLoginRedirectUrl("/auth/login")) { router.push("/auth/login"); } } diff --git a/apps/cm/src/pages/auth/login/ui/login-page.tsx b/apps/cm/src/pages/auth/login/ui/login-page.tsx index fed7be4ad..297d28a8f 100644 --- a/apps/cm/src/pages/auth/login/ui/login-page.tsx +++ b/apps/cm/src/pages/auth/login/ui/login-page.tsx @@ -145,11 +145,11 @@ function LoginPage() {

- 유역하수도 통합플랫폼 + 유역하수도통합플랫폼
- 통합관리시스템 + 하수도 포탈

-

유역하수도 통합플랫폼에 오신 것을 환영합니다.

+

유역하수도통합플랫폼 하수도 포탈에 오신 것을 환영합니다.

logo diff --git a/apps/common/features/next.config.ts b/apps/common/features/next.config.ts index 34965cf32..8a50ea611 100644 --- a/apps/common/features/next.config.ts +++ b/apps/common/features/next.config.ts @@ -1,6 +1,21 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { + output: "standalone", + experimental: { + proxyTimeout: 600000, + }, + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + logging: { + fetches: { + fullUrl: true, + }, + }, async rewrites() { return { afterFiles: [ diff --git a/apps/common/features/package.json b/apps/common/features/package.json index 14a96b6f8..7118ae49d 100644 --- a/apps/common/features/package.json +++ b/apps/common/features/package.json @@ -46,7 +46,7 @@ "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", "eslint": "^9", - "eslint-config-next": "15.1.7", + "eslint-config-next": "15.3.8", "postcss": "^8", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx index e518f938a..d514569e0 100644 --- a/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx @@ -4,13 +4,17 @@ import { useEffect } from "react"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; +import { getAuthLoginRedirectUrl, isAtLoginRedirectUrl } from "@common/network"; import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(getAuthLoginRedirectUrl())) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -28,8 +32,6 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - await LoginApi.silentRefresh(); - await authCheck(); } })(); }, []); diff --git a/apps/cs/.gitignore b/apps/cs/.gitignore index b63718b61..f80a96304 100644 --- a/apps/cs/.gitignore +++ b/apps/cs/.gitignore @@ -38,4 +38,5 @@ yarn-error.log* next-env.d.ts .env* -!.env.example \ No newline at end of file +!.env.example +/start_cs.log diff --git a/apps/cs/package.json b/apps/cs/package.json index 03ec06fcf..27f43fcc1 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -52,7 +52,7 @@ "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", "eslint": "^9.0.0", - "eslint-config-next": "15.4.0", + "eslint-config-next": "15.3.8", "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx index c0be4c2dd..57ae75c34 100644 --- a/apps/cs/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx @@ -1,18 +1,24 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; -import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; +import { isAtLoginRedirectUrl } from "@common/network"; +import { + ENDPOINT, + AUTH_USER_ID, + AUTH_LOGIN_REDIRECT_URL, +} from "@/shared/config"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -30,14 +36,9 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); - router.push("/auth/login"); + setUserInfo(null); + if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + window.location.assign(AUTH_LOGIN_REDIRECT_URL); } } })(); diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx index 7ae7990a1..d94d79c9a 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -39,10 +39,10 @@ function LoginlogPage() { const [presetDateRange, setPresetDateRange] = useState(""); const [dateRange, setDateRange] = useState(defaultDateRange); const [userId, setUserId] = useState(""); - const [errOcrnYn, setErrOcrnYn] = useState("N"); + const [errOcrnYn, setErrOcrnYn] = useState("ALL"); const [searchParams, setSearchParams] = useState({ userId: "", - errOcrnYn: "N", + errOcrnYn: "", searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), searchDateTo: dayjs().format("YYYY-MM-DD"), sort: "lgnDt,desc", @@ -87,7 +87,7 @@ function LoginlogPage() { setSearchParams({ ...searchParams, userId, - errOcrnYn, + errOcrnYn: errOcrnYn === "ALL" ? "" : errOcrnYn, searchDateFrom: dateRange.from ? dayjs(dateRange.from).format("YYYY-MM-DD") : "", @@ -102,12 +102,12 @@ function LoginlogPage() { const handleReset = () => { setUserId(""); - setErrOcrnYn("N"); + setErrOcrnYn("ALL"); setDateRange(defaultDateRange); setSearchParams({ ...searchParams, userId: "", - errOcrnYn: "N", + errOcrnYn: "", searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), searchDateTo: dayjs().format("YYYY-MM-DD"), }); @@ -180,6 +180,7 @@ function LoginlogPage() { + 전체 정상 오류 diff --git a/apps/cs/src/shared/config/env.ts b/apps/cs/src/shared/config/env.ts index c6373724f..95d1a4477 100644 --- a/apps/cs/src/shared/config/env.ts +++ b/apps/cs/src/shared/config/env.ts @@ -2,6 +2,8 @@ export const PORT = process.env.NEXT_PUBLIC_PORT || "3000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; +export const AUTH_LOGIN_REDIRECT_URL = + process.env.NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL || "/auth/login"; export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS"; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = diff --git a/apps/dc/package.json b/apps/dc/package.json index 747cdf91e..5835b9ab6 100644 --- a/apps/dc/package.json +++ b/apps/dc/package.json @@ -41,7 +41,7 @@ "@types/node": "^22.17.0", "@types/webpack": "^5.28.5", "eslint": "^9.0.0", - "eslint-config-next": "15.4.0", + "eslint-config-next": "15.3.8", "postcss": "^8.4.49", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.11", diff --git a/apps/dc/src/entities/auth/lib/auth-wrapper.tsx b/apps/dc/src/entities/auth/lib/auth-wrapper.tsx index 1f196ed52..57ae75c34 100644 --- a/apps/dc/src/entities/auth/lib/auth-wrapper.tsx +++ b/apps/dc/src/entities/auth/lib/auth-wrapper.tsx @@ -1,20 +1,24 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; import { client } from "@/shared/api"; import type { IUserInfo } from "@/shared/store"; import { getCookie, useUserStore } from "@/shared/lib"; -import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; -import { LoginApi } from "../api/login-service"; - -const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; +import { isAtLoginRedirectUrl } from "@common/network"; +import { + ENDPOINT, + AUTH_USER_ID, + AUTH_LOGIN_REDIRECT_URL, +} from "@/shared/config"; const AuthWrapper = ({ children }: { children: React.ReactNode }) => { - const router = useRouter(); const { userInfo, setUserInfo } = useUserStore((state) => state); async function authCheck() { + if (isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + return; + } + const userId = getCookie(AUTH_USER_ID); if (!userId) { throw new Error("Missing required authentication tokens"); @@ -32,14 +36,9 @@ const AuthWrapper = ({ children }: { children: React.ReactNode }) => { await authCheck(); } catch (error) { console.error(error); - - try { - await LoginApi.silentRefresh(); - await authCheck(); - } catch (refreshError) { - console.error("Token refresh failed:", refreshError); - setUserInfo(null); - router.push("/auth/login"); + setUserInfo(null); + if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) { + window.location.assign(AUTH_LOGIN_REDIRECT_URL); } } })(); diff --git a/apps/dc/src/shared/config/env.ts b/apps/dc/src/shared/config/env.ts index 89d2c039c..9edea0e2d 100644 --- a/apps/dc/src/shared/config/env.ts +++ b/apps/dc/src/shared/config/env.ts @@ -3,10 +3,14 @@ export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; -export const NEXT_PUBLIC_DC_SERVICE_API_URL = process.env.NEXT_PUBLIC_DC_SERVICE_API_URL || `http://localhost:${PORT}`; +export const NEXT_PUBLIC_DC_SERVICE_API_URL = + process.env.NEXT_PUBLIC_DC_SERVICE_API_URL || `http://localhost:${PORT}`; +export const AUTH_LOGIN_REDIRECT_URL = + process.env.NEXT_PUBLIC_AUTH_LOGIN_REDIRECT_URL || "/auth/login"; // Flask API URL -export const FLASK_API_URL = process.env.NEXT_PUBLIC_FLASK_API_URL || "http://localhost:5000"; +export const FLASK_API_URL = + process.env.NEXT_PUBLIC_FLASK_API_URL || "http://localhost:5000"; //export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "DC"; export const SITE_ID = "DC"; diff --git a/apps/dm/.env.local b/apps/dm/.env.local index 88b6f8067..4dce58c9d 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -1,12 +1,13 @@ NEXT_PUBLIC_SITE_ID = DM +NEXT_PUBLIC_PORT = 3015 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token -NEXT_PUBLIC_PORT = 3005 -NEXT_PUBLIC_PROXY_HOST = http://localhost:3005 -NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_PROXY_HOST = http://localhost:3015 +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 # NEXT_PUBLIC_FO_SERVER_API_URL = http://192.168.1.222:8883/operation-service/api/v1 NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 @@ -19,7 +20,6 @@ NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&ver NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do diff --git a/apps/dm/.env.mindone b/apps/dm/.env.mindone index da6f80177..22fb6274c 100644 --- a/apps/dm/.env.mindone +++ b/apps/dm/.env.mindone @@ -1,23 +1,25 @@ NEXT_PUBLIC_SITE_ID = DM -NEXT_PUBLIC_PORT = 11035 -NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035 -NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 -NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload +NEXT_PUBLIC_PORT = 11315 NEXT_PUBLIC_CLAIM_NAME = Authorization NEXT_PUBLIC_AUTH_USER_ID = token-id NEXT_PUBLIC_ACCESS_TOKEN = access-token NEXT_PUBLIC_REFRESH_TOKEN = refresh-token +NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11315 +NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +#NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver +NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API # NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 #NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do diff --git a/apps/dm/.env.production b/apps/dm/.env.production index 5a401c89a..587001101 100644 --- a/apps/dm/.env.production +++ b/apps/dm/.env.production @@ -8,16 +8,16 @@ NEXT_PUBLIC_REFRESH_TOKEN = refresh-token #NEXT_PUBLIC_PROXY_HOST = https://devreuse.hasuon.or.kr #NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 -NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service/api/v1 NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 -NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload +NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 +NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service -NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} +NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do diff --git a/apps/dm/.gitignore b/apps/dm/.gitignore deleted file mode 100644 index c1b3c4e38..000000000 --- a/apps/dm/.gitignore +++ /dev/null @@ -1,39 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/dm/app/(base-layout)/layout.tsx b/apps/dm/app/(base-layout)/layout.tsx deleted file mode 100644 index 5caa088b0..000000000 --- a/apps/dm/app/(base-layout)/layout.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; -import Base from "../base"; -import "../globals.css"; -import { StoreProvider } from "../store"; - -export default function BaseLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx deleted file mode 100644 index 8c681876c..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmApplyList/page.tsx +++ /dev/null @@ -1,209 +0,0 @@ -"use client" -import { Table, TableBody, TableCell, TableRow } from '@common/components/ui/table'; - -import { useDmStore } from '@/app/store'; -import { ContentsDiv, Title, UiModal, UiTable } from '@common/business_components/ui'; -import ListDiv from '@common/business_components/ui/list_div'; -import { useEffect, useState } from 'react'; -import { Toaster } from 'react-hot-toast'; -import SdmLogin from '../sdmLogin/page'; -import type { DemandCompBizType } from '@/app/store/sdmMng'; -import TransValueFormat from '@/app/common/transValueFormat'; -import { useRouter } from 'next/navigation'; - -const TABLE_STYLES = { - header: "text-center border border-[var(--border)] bg-[rgb(234,243,251)] text-[rgb(100,119,132)] whitespace-pre-line", - cell: "text-right whitespace-pre-line", - fullWidth: "text-center border border-[var(--border)] bg-[rgb(234,243,251)] text-[rgb(100,119,132)] whitespace-pre-line", - table: "fixed w-full" -} as const; - -type SdmApplyProps = { - sdmNo : string; -} - -export default function SdmApplyList({}: SdmApplyProps) { - - const router = useRouter(); - - const {getSdmInfoStatusList, Sdm, Common} = useDmStore((state) => state); - const {successLogin, sdmInfoStatusList, upHeadSdmInfoStatus} = Sdm; - const {selectSidoData, selectSigunData} = Common; - const [isLogin, setIsLogin] = useState(false); - const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); - const [sdmApplyDetail, setSdmApplyDetail] = useState({} as DemandCompBizType); - const [transformedList, setTransformedList] = useState([]); - - useEffect(() => { - console.log('sessionStorage : ',sessionStorage.getItem('user')); - const user = sessionStorage.getItem('user'); - if (user) { - setIsLoginStorageInfo(true); - } - }, []); - - useEffect(() => { - if (successLogin || isLoginStorageInfo) { - setIsLogin(true); - const user = sessionStorage.getItem('user'); - if (user) { - try { - const userData = JSON.parse(user); - getSdmInfoStatusList(userData.dmdInfoId); - } catch (error) { - console.error('Error parsing user data:', error); - } - } - } - }, [successLogin, getSdmInfoStatusList, isLoginStorageInfo]); - - // 데이터 변환을 위한 별도의 useEffect - useEffect(() => { - if (sdmInfoStatusList.length > 0) { - - - const newTransformedList = sdmInfoStatusList.map((item: DemandCompBizType) => ({ - ...item, - ctpvCd: selectSidoData.find(sido => sido.val === item.ctpvCd?.trim())?.text || '', - sggCd: selectSigunData.find(sigun => sigun.val === item.sggCd?.trim())?.text || '', - })); - setTransformedList(newTransformedList); - } - }, [sdmInfoStatusList, selectSidoData, selectSigunData]); - - const onClickSdmStatus = (index: number | undefined) => { - if (index !== undefined) { - const selectedStatus = transformedList[index]; - if (selectedStatus) { - - - setSdmApplyDetail(selectedStatus); - } - } - } - - return ( - <> - - <ContentsDiv> - <ListDiv> - <div className="overflow-y-scroll h-[250px]"> - <div className="h-[110%] w-[99%]"> - <UiTable - headName='수요정보 신청현황' - tableData={transformedList} - headlist={upHeadSdmInfoStatus} - pageSize={0} - total={0} - cellClick={(index) => onClickSdmStatus(index)} - onPageChange={() => {}} - /> - </div> - </div> - </ListDiv> - <ListDiv> - <div className="card-header"> - <h3 className="text-2xl font-medium text-label">수요신청 상세현황</h3> - </div> - <div className={`card !m-[1.2rem] !p-0r`}> - <Table variant="secondary" style={{ tableLayout: 'fixed', width: '100%' }}> - <TableBody> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>요구시기</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.dmndTmng?.toString()}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>요구수량(m³/일)</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.dmndQty} format="number"/>}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>사용용도</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.useUsgCd}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>희망단가(원/m³)</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.wntUntprc} format="number"/>}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={6} className={TABLE_STYLES.fullWidth}>요구수량</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>총대장균군수(개/100ml)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.grssClfg!=-1?<TransValueFormat value={sdmApplyDetail?.grssClfg ?? ''} format="number"/>:''} {sdmApplyDetail?.grssClfg?sdmApplyDetail.grssClfg!=-1? '이하':'불검출':'-'} </TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>결합잔류염소(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.combResdch ?? ''} format="number"/>} {sdmApplyDetail?.combResdch?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>탁도(NTU)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.trbt ?? ''} format="number"/>} {sdmApplyDetail?.trbt?'이하':'-'}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>생물학적 산소요구량(BOD)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.blgcOxygDmndQnt ?? ''} format="number"/>} {sdmApplyDetail?.blgcOxygDmndQnt?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>냄새</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.smll} format="number"/>} {sdmApplyDetail?.smll?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>색도(도)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.crmty ?? ''} format="number"/>} {sdmApplyDetail?.crmty?'이하':'-'}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>총질소(T-N)(mg/L)</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tn ?? ''} format="number"/>} {sdmApplyDetail?.tn?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>총인(T-P)(mg/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.tph ?? ''} format="number"/>} {sdmApplyDetail?.tph?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>수소이온농도(pH)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.ph} format="number"/>}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>염화물(mgCL/L)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.clIonQnt ?? ''} format="number"/>} {sdmApplyDetail?.clIonQnt?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>전기전도도(㎲/cm)​</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{<TransValueFormat value={sdmApplyDetail?.cdtnv ?? ''} format="number"/>} {sdmApplyDetail?.cdtnv?'이하':'-'}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>비고</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.rmrk}</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={6} className={TABLE_STYLES.fullWidth}>공급처 후보지​</TableCell> - </TableRow> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지1</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{ sdmApplyDetail?.splyCndtList?.[0]?.ruseUtztnFcltNm ?? ""}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지2</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[1]?.ruseUtztnFcltNm ?? ""}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>후보지3</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.cell}>{sdmApplyDetail?.splyCndtList?.[2]?.ruseUtztnFcltNm ?? ""}</TableCell> - </TableRow> - </TableBody> - </Table> - </div> - </ListDiv> - <ListDiv> - <div className="card-header"> - <h3 className="text-2xl font-medium text-label">수요신청 진행상태</h3> - </div> - <div className={`card !m-[1.2rem] !p-0r`}> - <Table variant="secondary" style={{ tableLayout: 'fixed', width: '100%' }}> - <TableBody> - <TableRow> - <TableCell colSpan={1} className={TABLE_STYLES.header}>진행상태</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.status?.toString()}</TableCell> - <TableCell colSpan={1} className={TABLE_STYLES.header}>공급처매칭결과</TableCell> - <TableCell colSpan={2} className={TABLE_STYLES.cell}>{sdmApplyDetail?.ruseUtztnFcltNm??''}</TableCell> - </TableRow> - </TableBody> - </Table> - </div> - </ListDiv> - </ContentsDiv> - {!isLogin? - <UiModal - body={<SdmLogin/>} - bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" - open={!isLogin} - onOpenChange={(open) => { - if(!open){ - setIsLogin(false) - router.push('/'); - } - }} - noHeaderYn="Y" - noFooterYn="Y" - /> - :null} - <Toaster position="top-center" /> - </> - ); -} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx deleted file mode 100644 index 35836d07c..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/modalSimilar/page.tsx +++ /dev/null @@ -1,166 +0,0 @@ -"use client" - -import { useDmStore } from '@/app/store'; -import type { MoneyDetailType, SimilarCaseType } from '@/app/store/sdmMng'; -import { SelectBox, Title, UiTable } from '@common/business_components/ui'; -import { HeadMakeColSpan } from '@common/business_components/ui/table/HeadMakeColSpan'; -import { Table, TableBody, TableCell, TableHeader, TableRow } from '@common/components/ui/table'; -import { useEffect, useMemo, useState } from 'react'; - -type modalType = { - sply: number | 0; - usageCd: string | ''; - money: number | 0; -}; - -const searchUseTypeList = [ - { text: '공급량', val: 'S' }, - { text: '사용용도', val: 'U' }, -]; - -export default function ModalSimilarPage({ sply = 0, usageCd = '', money = 0 }: modalType) { - const { Sdm, getSimilarCaseList } = useDmStore((state) => state); - const [searchUseType, setSearchUseType] = useState<{text: string, value: string}>({text: '공급량', value: 'S'}); - const [moneyDetailList, setMoneyDetailList] = useState<MoneyDetailType[]>([]); - const { - upHeadSimilarCaseList, - similarCaseList, - upHeadMoneyDetailList, - } = Sdm; - - const rowSpans = useMemo(() => { - const spans: { [key: number]: number } = {}; - let currentValue = ''; - let currentSpan = 0; - let startIndex = 0; - - similarCaseList.forEach((item: SimilarCaseType, index: number) => { - if (item.ruseBizNm === currentValue - || item.bizBgngDate === currentValue - || item.bizEndDate === currentValue - || item.reqBizReuseFcltCst === currentValue) { - currentSpan++; - spans[startIndex] = currentSpan; - } else { - currentValue = item.ruseBizNm; - currentSpan = 1; - startIndex = index; - spans[index] = 1; - } - }); - - return spans; - }, [similarCaseList]); - - const handleCellClick = (index: number) => { - setMoneyDetailList([{ - reqBizGrssCst: similarCaseList[index]?.reqBizGrssCst ?? '', - reqBizReuseFcltCst: similarCaseList[index]?.reqBizReuseFcltCst ?? '', - reqBizPplCst: similarCaseList[index]?.reqBizPplCst ?? '', - reqBizArchCst: similarCaseList[index]?.reqBizArchCst ?? '', - reqBizMchnCst: similarCaseList[index]?.reqBizMchnCst ?? '', - reqBizMtrngCst: similarCaseList[index]?.reqBizMtrngCst ?? '', - reqBizGisCst: similarCaseList[index]?.reqBizGisCst ?? '', - reqBizCctvCst: similarCaseList[index]?.reqBizCctvCst ?? '', - reqBizCmcnCst: similarCaseList[index]?.reqBizCmcnCst ?? '', - reqBizWstCst: similarCaseList[index]?.reqBizWstCst ?? '', - reqBizDesignCst: similarCaseList[index]?.reqBizDesignCst ?? '', - reqBizSpvsCst: similarCaseList[index]?.reqBizSpvsCst ?? '', - reqBizSbsdCst: similarCaseList[index]?.reqBizSbsdCst ?? '', - reqBizEtc: similarCaseList[index]?.reqBizEtc ?? '', - prcmPlanNlep: similarCaseList[index]?.prcmPlanNlep ?? '', - prcmPlanLcep: similarCaseList[index]?.prcmPlanLcep ?? '', - prcmPlanBrdnCst: similarCaseList[index]?.prcmPlanBrdnCst ?? '', - prcmPlanEcqrRt: similarCaseList[index]?.prcmPlanEcqrRt ?? '', - prcmPlanGrssCst: similarCaseList[index]?.prcmPlanGrssCst ?? '', - clclnNlep: similarCaseList[index]?.clclnNlep ?? '', - clclnLcep: similarCaseList[index]?.clclnLcep ?? '', - clclnBrdnCst: similarCaseList[index]?.clclnBrdnCst ?? '', - clclnEcqrRt: similarCaseList[index]?.clclnEcqrRt ?? '', - clclnGrssCst: similarCaseList[index]?.clclnGrssCst ?? '' - }]); - } - - useEffect(() => { - const type = searchUseType.value; - getSimilarCaseList(type, type === 'S' ? sply : type === 'U' ? usageCd : money); - }, [getSimilarCaseList, money, searchUseType, sply, usageCd]); - - return ( - <div className="align-top w-[1360px] h-full"> - <Title title="유사사례 조회" /> - <div className="overflow-x-auto h-[480px] w-full mt-8" tabIndex={0} aria-label="유사사례 테이블 영역"> - <div className="w-[150%]"> - <div className="card-header justify-self-start"> - <h3 className="text-2xl font-medium text-label">유사사례</h3> - <SelectBox - tabIndex={0} - aria-label="검색 기준 선택" - selectArray={searchUseTypeList} - value={searchUseType.value} - onSelectValue={(val) => { - setSearchUseType({text: searchUseTypeList.find(item => item.val === val)?.text ?? '', value: val.toString()}); - }} - className="w-[140px] ml-20" - label="" - selectClass="w-[140px]" - /> - </div> - <div className={`card !m-[1.2rem] !p-0r`}> - <Table variant="secondary"> - <TableHeader className="font-bold"> - <HeadMakeColSpan upHeadList={upHeadSimilarCaseList} /> - </TableHeader> - <TableBody className='text-center'> - {similarCaseList.length > 0 ? similarCaseList?.map((el: SimilarCaseType, index) => ( - <TableRow key={`similar-case-row-${index}-${el.ruseBizNm}-${el.bizBgngDate}`}> - {upHeadSimilarCaseList.map((head) => ( - head.id && head.id.length > 1 && ( - <TableCell - tabIndex={head.id === 'someClickableId' ? 0 : undefined} - role="button" - aria-label={`유사사례 ${head.id} 클릭 시 상세 정보 표시`} - className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id as keyof SimilarCaseType] === 'number' ? 'text-right' : 'text-center'} cursor-pointer`} - key={`similar-case-cell-${head.id}-${index}-${el.ruseBizNm}`} - rowSpan={(head.id === 'businessName' || head.id ==='businessDuration' || head.id === 'money') ? rowSpans[index] : undefined} - style={(head.id === 'businessName' || head.id === 'businessDuration' || head.id === 'money') && index > 0 && similarCaseList[index - 1]?.ruseBizNm === el.ruseBizNm ? { display: 'none' } : undefined} - onClick={() => handleCellClick(index)} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - handleCellClick(index); - } - }} - > - {typeof el[head.id as keyof SimilarCaseType] !== 'number' - ? String(el[head.id as keyof SimilarCaseType] ?? '') - : String(el[head.id as keyof SimilarCaseType]).includes('.') - ? String(el[head.id as keyof SimilarCaseType]).split('.').map((part, index) => - index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part - ).join('.') - : String(el[head.id as keyof SimilarCaseType] ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")} - </TableCell> - ) - ))} - </TableRow> - )) : <></>} - </TableBody> - </Table> - </div> - </div> - </div> - <div className="overflow-x-auto h-[270px] w-full mt-8 overflow-y-hidden"> - <div className="w-[130%]"> - <UiTable - headName={"사업비 상세정보"} - tableData={moneyDetailList} - headlist={upHeadMoneyDetailList} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={() => {}} - /> - </div> - </div> - </div> - ) -} \ No newline at end of file diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx deleted file mode 100644 index df418b20f..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInfoInsert/page.tsx +++ /dev/null @@ -1,294 +0,0 @@ -"use client"; - -/** - * 수요정보등록 화면 - * - * 🔗 사용 API - * - 수요정보등록: POST /reuseFc/request/demandInfo - */ -import { useDmStore } from "@/app/store"; -import { sdmInfoFormSchema, type DemandCompBizType, type SdmInfoFormData } from "@/app/store/sdmMng"; -import { SdmInfoRegForm } from "@/components/ui/sdmInfoInsertForm/component"; -import { UiModal } from "@common/business_components/ui/dialog_ui"; -import { Button } from "@common/components/ui"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useRouter, useSearchParams } from "next/navigation"; -import { Suspense, useEffect, useState } from "react"; -import { FormProvider, useForm, type SubmitHandler } from "react-hook-form"; -import toast, { Toaster } from "react-hot-toast"; -import SdmLogin from "../sdmLogin/page"; - - - -function SdmInfoInsertContent() { - const searchParams = useSearchParams(); - const { insertSdmInfoData, Sdm, setSubsInfo } = useDmStore((state) => state); - const { successLogin } = Sdm; - const [ismoneyDisabled, setIsmoneyDisabled] = useState(false); - const formSchema = sdmInfoFormSchema; - - const [isLogin, setIsLogin] = useState(false); - const [facilityName, setFacilityName] = useState<string>(""); - const [defaultValues, setDefaultValues] = useState<SdmInfoFormData>({ - sdmNm: "", - demandYmd: new Date(), - demandQnty: "", - usePurpose: "", - money: "", - /**요구수질 */ - reqWaterQlty: "", - grssClfg: null, - tn: null, - combResdch: null, - tph: null, - trbt: null, - ph: null, - bod: null, - /**컬럼확인필요 */ - clIonQnt: null, - smll: "", - cdtnv: null, - crmty: null, - rmrk: "", - /** 후보지 제목 */ - candidateTitle: [ - { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, - { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, - { ruseUtztnFcltNm: "", ruseUtztnFcltCd: "" }, - ], - }); - - useEffect(() => { - const user = sessionStorage.getItem("user"); - - - setDefaultValues((prev) => ({ - ...prev, - sdmNm: searchParams.get("dmdCoNm") || "", - })); - if (searchParams.get("dmdInfoId")) { - // const data = { - // dmdCoNm: searchParams.get("dmdCoNm") || '', - // ctpvCd: searchParams.get("ctpvCd") || '', - // sggCd: searchParams.get("sggCd") || '', - // dmdInfoId: searchParams.get("dmdInfoId") || '', - // daddr: searchParams.get("daddr") || '', - // telno: searchParams.get("telno") || '', - // } - - // sessionStorage.setItem("user", JSON.stringify(data || {})); - } - //sessionStorage.setItem("user", JSON.parse(user||'') || ""); - setIsLogin(user || searchParams.get("dmdInfoId") ? true : false); - }, [searchParams]); - - useEffect(() => { - const local = localStorage.getItem("user"); - if (local) { - setIsLogin(true); - } - setSubsInfo([]); // subsInfo 초기화 후보지 처음 화면 들어올때 초기화(스토어내 객체만) - }, [setSubsInfo]); - - const form = useForm<SdmInfoFormData>({ - resolver: zodResolver(formSchema), - defaultValues, - mode: "onSubmit", - }); - - const subsInfo = useDmStore((state) => state.map.subsInfo); - - useEffect(() => { - //map.subsInfo 스토어 객체 값이 변경되면 해당 값이 폼에 자동으로 들어가도록 수정 대신 값이 있어야함 - if (subsInfo.length > 0) { - form.setValue("candidateTitle", subsInfo); - } - }, [subsInfo, form]); - - //후보지 팝업 값 셋팅 - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - if (event.data?.type === "SET_SUBS_INFO") { - setSubsInfo(event.data.payload); // zustand로 값 설정 - } - }; - - window.addEventListener("message", handleMessage); - return () => window.removeEventListener("message", handleMessage); - }, [setSubsInfo]); - - // 로그인 성공 시 모달 닫기 및 폼 업데이트 - useEffect(() => { - if (successLogin) { - setIsLogin(true); - const user = sessionStorage.getItem("user"); - if (user) { - try { - const userData = JSON.parse(user); - if (userData.dmdCoNm) { - setFacilityName(userData.dmdCoNm); - form.setValue("sdmNm", userData.dmdCoNm); // 수요처명(회사명) - } - } catch (error) { - console.error("Error parsing user data:", error); - } - } - } - }, [successLogin, form]); - - useEffect(() => { - console.log("Form state changed:", form.formState.errors); - }, [form.formState.errors]); - - // 체크박스 상태가 변경될 때마다 money 필드의 에러를 초기화 - useEffect(() => { - if (ismoneyDisabled) { - form.clearErrors("money"); - form.setValue("money", ""); - } - }, [ismoneyDisabled, form]); - const router = useRouter(); - const onSubmit: SubmitHandler<SdmInfoFormData> = async (data) => { - try { - const dmSubsInfo = data.candidateTitle?.filter( - (item) => item.ruseUtztnFcltNm && item.ruseUtztnFcltCd - ); - // Transform form data to madata.candidateTitletch API schema - const apiData: DemandCompBizType = { - dmdInfoId: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.dmdInfoId?.toString() || - searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), - dmdInfoFk: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.dmdInfoId?.toString() || - searchParams.get("dmdInfoId") + (searchParams.get("bznmTotNo") || ""), - dmdCoNm: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.dmdCoNm?.toString() || data.sdmNm, - ctpvCd: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.ctpvCd?.toString() || - searchParams.get("ctpvCd") || - "", - sggCd: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.sggCd?.toString() || - searchParams.get("sggCd") || - "", - daddr: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.daddr?.toString() || - searchParams.get("daddr") || - "", - telno: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.telno?.toString() || - searchParams.get("telno") || - "", - rgtr: - JSON.parse( - sessionStorage.getItem("user") || "{}" - )?.rgtr?.toString() || - searchParams.get("rgtr") || - "", - useUsgCd: data.usePurpose || "", - wntUntprc: Number(data.money) || "", - dmndTmng: data.demandYmd || new Date(), - dmndQty: Number(data.demandQnty) || "", - grssClfg: data.grssClfg === null ? -1 : Number(data.grssClfg), - tn: Number(data.tn) || null, - combResdch: Number(data.combResdch) || null, - tph: Number(data.tph) || null, - trbt: Number(data.trbt) || null, - ph: data.ph || "", - blgcOxygDmndQnt: Number(data.bod) || null, - clIonQnt: Number(data.clIonQnt) || null, - smll: data.smll || "", - cdtnv: Number(data.cdtnv) || null, - crmty: Number(data.crmty) || null, - rmrk: data.rmrk || "", - bzmnNo: "", - dmdAplyPswd: "", - index: 0, - reqDt: "", - dmdInfoSn: 0, - status: "", - splyCndtList: dmSubsInfo || [], - }; - - await insertSdmInfoData(apiData); - setTimeout(() => { - router.push("/"); - }, 1000); // 0.8초 후 이동 - } catch (error) { - console.error("Error submitting form:", error); - } - }; - - return ( - <div className="min-h-screen bg-[#F2F2F2]"> - <div className="container mx-auto px-4 py-8"> - <div className="text-center mb-8"> - <h1 className="text-4xl font-bold text-gray-900 underline"> - 수요정보 등록 - </h1> - </div> - - <div className="max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6"> - <FormProvider {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> - <SdmInfoRegForm - form={form} - onCheckboxChange={setIsmoneyDisabled} - facilityName={facilityName} - /> - <div className="flex justify-center pt-4"> - <Button - color="sky" - size={"sm"} - type="submit" - className="w-40 font-semibold py-2 px-4 rounded-md " - > - 수요정보 등록 - </Button> - </div> - </form> - </FormProvider> - </div> - </div> - - {!isLogin ? ( - <UiModal - body={<SdmLogin />} - bodyClass="sm:max-w-[90vw] w-[530px] h-[430px]" - open={!isLogin} - onOpenChange={(open) => { - if (!open) { - toast.dismiss(); - router.push("/"); - } - }} - noHeaderYn="Y" - noFooterYn="Y" - /> - ) : null} - <Toaster position="top-center" /> - </div> - ); -} - -export default function MyForm() { - return ( - <Suspense fallback={<div>Loading...</div>}> - <SdmInfoInsertContent /> - </Suspense> - ); -} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx deleted file mode 100644 index 79eb07830..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmInsert/page.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import { useDmStore } from "@/app/store"; -import SdmInsertForm from '@common/business_components/ui/sdmInsertForm'; -import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; -import { useRouter } from "next/navigation"; -import { useCallback, useEffect, useRef } from "react"; -import { Toaster } from "react-hot-toast"; - -export default function MyForm() { - const { Common, initializeCommonData, getDemandCompList, insertSdmData, Sdm } = useDmStore((state) => state); - const { selectSidoData, selectSigunData } = Common; - const { demandCompList, successNextPage } = Sdm; - const router = useRouter(); - const formDataRef = useRef<DemandCompType | null>(null); - - useEffect(() => { - initializeCommonData(); - }, [initializeCommonData]); - - useEffect(() => { - if (successNextPage && formDataRef.current) { - const data = formDataRef.current; - console.log(data); - const queryParams = new URLSearchParams({ - dmdCoNm: data.dmdCoNm || '', - ctpvCd: data.ctpvCd || '', - sggCd: data.sggCd || '', - dmdInfoId: data.dmdInfoId || '', - daddr: data.daddr || '', - telno: data.telno || '', - }); - router.push(`/pages/sdmMng/sdmInfoInsert?${queryParams.toString()}`); - } - }, [successNextPage, router]); - - const callback = useCallback((searchText: string, pageCnt: number) => { - searchText = searchText.trim(); - if (searchText.length > 0) { - getDemandCompList(searchText, pageCnt); - } - }, [getDemandCompList]); - - const handleInsertSdmData = useCallback((data: import('@common/business_components/ui/sdmInsertForm/form').DemandCompType) => { - const convertedData: DemandCompType = { - ...data, - }; - formDataRef.current = convertedData; - insertSdmData(convertedData) - }, [insertSdmData]); - - return ( - <div className="min-h-screen bg-[#F2F2F2]"> - <div className="pt-12"> - <SdmInsertForm - selectSidoData={selectSidoData} - selectSigunData={selectSigunData} - dataList={demandCompList} - rtnValue={callback} - rtnInputData={handleInsertSdmData} - openNetwork={true} - /> - <Toaster position="top-center" /> - </div> - </div> - ); -} diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogin/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogin/page.tsx deleted file mode 100644 index 2fd6fc461..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogin/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client' - -import { SdmLoginForm } from '@/components/ui/sdmLoginForm/component'; - -export default function SdmLogin() { - return ( - <div className="container mx-auto px-4 py-8"> - <div className="text-center mb-8"> - <h1 className="text-4xl font-bold text-gray-900 underline"> - 수요처 로그인 - </h1> - </div> - <div className="max-w-3xl mx-auto bg-white rounded-lg shadow-md p-6"> - <SdmLoginForm /> - </div> - </div> - ); -} \ No newline at end of file diff --git a/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx b/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx deleted file mode 100644 index 2c682bdbf..000000000 --- a/apps/dm/app/(base-layout)/pages/sdmMng/sdmLogout/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; -/** - * 수요처 로그아웃 화면 - * - * 🔗 사용 API - * - */ -import { useDmStore } from "@/app/store"; -import { useRouter } from "next/navigation"; -import { useEffect } from "react"; - -export default function SdmLogout() { - const router = useRouter(); - const { actionLogout } = useDmStore((state) => state); - - useEffect(() => { - actionLogout(); - router.push("/pages/sdmMng/sdmInfoInsert"); - }, [actionLogout, router]); - - return null; -} diff --git a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx b/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx deleted file mode 100644 index da3324ffc..000000000 --- a/apps/dm/app/(base-layout)/pages/wrAbout/page.tsx +++ /dev/null @@ -1,339 +0,0 @@ -'use client'; - -import { useEffect, useRef, useState } from 'react'; -import Image from 'next/image'; - -const NAVS = [ - { id: 'card1', label: '물재이용이란' }, - { id: 'card2', label: '물재이용 필요성' }, - { id: 'card3', label: '빗물 재이용' }, - { id: 'card4', label: '중수 재이용' }, - { id: 'card5', label: '하수 재이용' }, - { id: 'card6', label: '물재이용 목표' }, -]; - -export default function WrAboutPage() { - const [activeIdx, setActiveIdx] = useState(0); - const navRefs = useRef<(HTMLLIElement | null)[]>([]); - const cardRefs = useRef<(HTMLElement | null)[]>([]); - - // 클릭 시 이동 - const handleNavClick = (idx: number) => { - setActiveIdx(idx); - const targetCard = cardRefs.current[idx]; - if (targetCard) { - targetCard.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } - }; - - // 키보드 이벤트 처리 - const handleKeyDown = (e: React.KeyboardEvent, idx: number) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - handleNavClick(idx); - } - }; - - useEffect(() => { - const observerOptions = { - root: null, - rootMargin: '-200px 0px 0px 0px', // 상단에서 200px 지점을 기준으로 - threshold: 0 - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const idx = cardRefs.current.findIndex(ref => ref === entry.target); - if (idx !== -1) { - setActiveIdx(idx); - } - } - }); - }, observerOptions); - - // 각 카드 요소를 관찰 - const cardsSnapshot = [...cardRefs.current]; - - cardsSnapshot.forEach((card) => { - if (card) { - observer.observe(card); - } - }); - - return () => { - cardsSnapshot.forEach((card) => { - if (card) { - observer.unobserve(card); - } - }); - }; - }, []); - - return ( - <div className='bg-[#F2F2F2]'> - <main className="relative w-[950px] mx-auto pt-[100px] font-sans"> - {/* 사이드바 */} - <nav className="fixed left-[290px] top-[195px] w-[210px] z-50" aria-label="물재이용 정보 섹션 내비게이션"> - <ul role="list" className="relative space-y-6"> - {NAVS.map((nav, idx) => ( - <li - key={nav.id} - ref={(el) => { navRefs.current[idx] = el; }} - role="listitem" - tabIndex={0} - aria-current={idx === activeIdx ? 'true' : 'false'} - aria-label={`현재 위치: ${nav.label}`} - onClick={() => handleNavClick(idx)} - onKeyDown={(e) => handleKeyDown(e, idx)} - className={`font-NotoSansKR nav-item relative pl-6 text-2xl font-bold text-[#383838] cursor-pointer transition-all duration-300 - after:content-[''] after:absolute after:top-6 after:left-[0.5rem] after:w-[2px] after:h-[24px] after:bg-[#C5C5C5] last:after:hidden - ${idx === activeIdx ? 'text-4xl font-bold text-[#383838]' : ''}`} - > - <span className={`absolute left-[0.5rem] top-1 w-4 h-4 rounded-full -translate-x-1/2 transition-all duration-300 - ${idx === activeIdx ? 'bg-blue-500 border-blue-500' : 'bg-white border-gray-400 border'}`}></span> - {nav.label} - </li> - ))} - </ul> - </nav> - - <article ref={(el) => { cardRefs.current[0] = el; }} className="card-wrap pl-[100px] mb-24 !pt-0" id="card1" aria-labelledby="card1-title"> - <h2 id="card1-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 물재이용이란 - </h2> - <section aria-labelledby="card1-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card1-desc" className="font-NotoSansKR text-[#269DDA] text-5xl font-bold mb-8">물 재이용이 무엇인가요?</h1> - <p className="font-NotoSansKR text-[#231815] text-2xl mb-10 leading-relaxed"> - 물 재이용은 빗물, 오수, 하수처리수, 폐수처리수 및 발전소 온배수를<br /> - 물 재이용시설을 이용하여 처리하고, 그 처리된 물을 생활, 공업, 농업, 조경,<br /> - 하천 유지 등의 용도로 이용하는 것을 말합니다. - </p> - <Image - src="/img/wrAbout/img_01_1.png" - alt="이 이미지는 물 재이용 시스템의 개요를 나타냅니다. 가정, 공공기관, 하수처리장 등에서 발생한 생활하수, 빗물, 하수처리수를 물 재이용 시설에서 처리한 후, 청소나 화장실, 농업, 살수차, 하천 유지, 공장, 공원 및 골프장 등 다양한 용도로 활용하는 과정을 도식화한 것입니다." - width={800} - height={400} - className="w-[90%] mx-auto mb-8 rounded" - /> - <p className="font-NotoSansKR text-center font-normal text-2xl"> - 환경부는 2011년에 '물의 재이용 촉진 및 지원에 관한 법률'을 제정하여<br /> - 한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다. - </p> - </section> - </article> - - {/* 카드2 */} - <article ref={(el) => { cardRefs.current[1] = el; }} className="card-wrap pl-[100px] mb-24" id="card2" aria-labelledby="card2-title"> - <h2 id="card2-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 물재이용 필요성 - </h2> - <section aria-labelledby="card2-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card2-desc" className="font-NotoSansKR text-[#D63F1F] text-5xl font-semibold mb-8">물 재이용, 왜 필요한가요?</h1> - <p className="font-NotoSansKR text-2xl font-normal mb-10 leading-relaxed"> - 빗물, 중수, 하수처리수는 지속적으로 이용 가능한 순환자원입니다.<br /> - 물 재이용을 통한 효율적인 수자원 활용은 물 스트레스국가인 우리나라에서<br />가뭄대응능력 향상 및 하천수질보전을 위해 반드시 필요한 일입니다. - </p> - <Image - src="/img/wrAbout/img_02_1.png" - alt="이 이미지는 기후변화로 인한 온도 상승 추이를 설명한다. 왼쪽에는 지구 그림과 온도계가 함께 표시되어 있으며, 지구 평균 기온은 지난 100년간 약 0.9℃ 상승했음을 나타낸다. 오른쪽에는 대한민국 지도와 함께 온도계가 있으며, 지난 100년간 약 1.8℃ 상승했음을 보여준다. 이는 지구 평균보다 2배 높은 수치이며, 자료 출처는 「기후변화 평가보고서 2020, 환경부·기상청」이다." - width={800} - height={400} - className="w-[90%] mx-auto mb-8 w-[80%]" - /> - <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed"> - - 우리나라의 기후변화 진행속도는 세계 평균을 상회하고 있습니다.<br /> - - 기후변화로 인한 국지적인 가뭄은 더욱 자주, 더욱 심하게 일어나고 있으며, 우리의 물 안보를 위협하고 있습니다. - </p> - <Image - src="/img/wrAbout/img_02_2.png" - alt="세계 평균 1인당 강수량은 15,044m³이며, 대한민국은 2,546m³로 약 1/6 수준입니다. 계절별 강수량은 봄 10%, 여름 70%, 가을 15%, 겨울 5%입니다. 이는 여름철에 강수량이 집중되는 문제를 나타냅니다." - width={800} - height={400} - className="w-[90%] mx-auto mb-8" - /> - <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed"> - - 우리나라의 1인당 강수량은 2,546㎥ 으로 세계 평균 15,044㎥의 약 1/6 수준입니다.<br /> - - 우리나라 강수량의 70%는 여름철에 집중되어 있어 이용 가능한 수자원확보가 어렵습니다. - </p> - <Image - src="/img/wrAbout/img_02_3.png" - alt="물 재이용은 환경을 보호하며 지속 가능한 발전이 가능하게 하고, 안정적인 수자원 확보, 깨끗하고 경제적인 공업용수 공급, 하천 수질 개선을 통한 생태계 회복에 기여합니다." - width={800} - height={400} - className="w-[93%] mx-auto" - /> - </section> - </article> - - {/* 카드3 */} - <article ref={(el) => { cardRefs.current[2] = el; }} className="card-wrap pl-[100px] mb-24" id="card3" aria-labelledby="card3-title"> - <h2 id="card3-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 빗물 재이용 - </h2> - <section aria-labelledby="card3-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card3-desc" className="font-NotoSansKR leading-snug text-[#269DDA] text-5xl font-bold mb-6">친환경 수자원 빗물,<br />편리하게 사용할 수 있어요!</h1> - <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">가뭄을 극복하고 홍수를 예방하기 위해<br />비가 내릴 때 빗물을 모아두었다가 사용합니다.</p> - <Image - src="/img/wrAbout/img_03_1.png" - alt="주택 지붕에 내린 빗물을 저장조에 모아 조경용수, 화장실용수, 세척·살수용수로 재활용하는 과정을 보여줍니다." - width={800} - height={400} - className="w-[90%] mx-auto mb-6" - /> - <Image - src="/img/wrAbout/img_03_2.png" - alt="빗물이용" - width={200} - height={100} - className="mb-5 w-[20%]" - /> - <p className="font-NotoSansKR text-2xl leading-relaxed"> - - 의무 설치대상: 지붕면적 1,000㎡ 이상의 운동장, 체육관, 공공업무시설 등<br /> - - 설 치 기 준: 집수시설, 처리시설, 빗물 저류조(지붕 집수면적 x 0.05m 이상)<br /> - - 활 용 용 도: 골프장 등의 조경용수, 화장실용수, 세척·살수용수 등 - </p> - </section> - </article> - - {/* 카드4 */} - <article ref={(el) => { cardRefs.current[3] = el; }} className="card-wrap pl-[100px] mb-24" id="card4" aria-labelledby="card4-title"> - <h2 id="card4-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 중수 재이용 - </h2> - <section aria-labelledby="card4-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card4-desc" className="font-NotoSansKR leading-snug text-[#9771B0] text-5xl font-bold mb-6">요모조모 쓸모 많은 중수,<br />알뜰하게 재이용할 수 있어요!</h1> - <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">한번 사용한 물을 중수도 시설을 통해 재생하여<br />개별적 또는 지역적으로 재이용할 수 있습니다.</p> - <Image - src="/img/wrAbout/img_04_1.png" - alt="욕실, 세면대, 주방, 세탁기 등에서 발생한 생활용수를 중수도 시설에서 처리하여 화장실 변기용수와 조경용수로 재이용하는 시스템 흐름도입니다." - width={800} - height={400} - className="w-[90%] mx-auto mb-6" - /> - <Image - src="/img/wrAbout/img_04_2.png" - alt="중수재이용" - width={200} - height={100} - className="mb-5 w-[20%]" - /> - <p className="font-NotoSansKR text-2xl leading-relaxed"> - - 의무 설치대상: 일정규모 이상의 대규모점포, 숙박/목욕장, 공장, 각종 개발사업 등<br /> - - 의무 재이용량: 물 사용량의 10% 이상<br /> - - 활 용 용 도: 청소·화장실용수, 세척·살수용수, 조경용수, 공업용수 등 - </p> - </section> - </article> - - {/* 카드5 */} - <article ref={(el) => { cardRefs.current[4] = el; }} className="card-wrap pl-[100px] mb-24" id="card5" aria-labelledby="card5-title"> - <h2 id="card5-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 하수 재이용 - </h2> - <section aria-labelledby="card5-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card5-desc" className="font-NotoSansKR leading-snug text-[#00818A] text-5xl font-bold mb-6">아직 할 일이 많은 하수처리수,<br />풍족하게 재이용할 수 있어요!</h1> - <p className="font-NotoSansKR text-2xl mb-10 leading-relaxed">사용하고 버려져 공공하수처리장으로 모여든 물은<br />용도에 맞는 처리 후 재이용 할 수 있습니다.</p> - <Image - src="/img/wrAbout/img_05_1.png" - alt="주택과 건물에서 발생한 하수가 공공하수처리장을 거쳐 처리된 후, 물 재이용 시설에서 정화되어 공업용수, 하천유지용수, 농업용수, 세척·살수용수로 재활용됩니다." - width={800} - height={400} - className="w-[90%] mx-auto mb-6" - /> - <Image - src="/img/wrAbout/img_05_2.png" - alt="하수처리 재이용" - width={200} - height={100} - className="mb-5 w-[20%]" - /> - <p className="font-NotoSansKR text-2xl leading-relaxed"> - - 의무 이용대상: 공공하수처리장(시설용량 5,000㎥/일 이상)<br /> - - 의무 재이용량: 일 하수처리량의 10% 이상<br /> - - 활 용 용 도: 공업용수, 하천유지용수, 농업용수 등 - </p> - </section> - </article> - - {/* 카드6 */} - <article ref={(el) => { cardRefs.current[5] = el; }} className="card-wrap pl-[100px] mb-24" id="card6" aria-labelledby="card6-title"> - <h2 id="card6-title" className="font-NotoSansKR relative text-center text-4xl font-medium text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 물재이용 목표 - </h2> - <section aria-labelledby="card6-desc" className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px] "> - <h1 id="card6-desc" className="font-NotoSansKR leading-snug text-[#269DDA] text-5xl font-bold mb-6">2030년 물 재이용 목표를 위해<br />모두의 관심과 노력이 필요합니다.</h1> - <p className="font-NotoSansKR text-2xl mb-6 leading-relaxed"> - 환경부는 2030년까지의 제2차 물 재이용 기본계획을 수립하여<br />한국환경공단과 함께 물 재이용 확대 및 촉진을 위해 노력하고 있습니다. - </p> - <Image - src="/img/wrAbout/img_06_1.png" - alt="지속가능한 물 재이용 정착으로 건전한 물순환 확산" - width={800} - height={400} - className="w-[80%] mx-auto mb-6" - /> - <div className="flex gap-3 flex-wrap justify-between bg-gray-100 p-6 rounded-lg mb-8"> - <div className="flex-1 text-center"> - <Image - src="/img/wrAbout/icon_01.png" - alt="" - width={70} - height={70} - className="mx-auto mb-4 w-[auto] h-[70px]" - /> - <h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">도시 물 순환이용<br />체계로의 전환</h3> - <ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1"> - <li className="text-nowrap">- 지속가능한 물 순환이용체계 구축</li> - <li>- 물 재이용시설 관리체계 강화</li> - </ul> - </div> - <div className="flex-1 text-center"> - <Image - src="/img/wrAbout/icon_02.png" - alt="" - width={70} - height={70} - className="mx-auto mb-4 w-[auto] h-[70px]" - /> - <h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">유역기반 하수처리수 재이용<br />수요확대 및 공급체계 구축</h3> - <ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1"> - <li>- 유역기반 통합물관리 연계 강화</li> - <li>- 하수처리수 재이용 공업용수 공급 활성화</li> - <li>- 하수처리수 재이용을 활용한 물순환 촉진</li> - </ul> - </div> - <div className="flex-1 text-center"> - <Image - src="/img/wrAbout/icon_03.png" - alt="" - width={70} - height={70} - className="mx-auto mb-4 w-[auto] h-[70px]" - /> - <h3 className="font-NotoSansKR text-center font-medium text-nowrap mb-2 text-2xl">물 재이용 인식제고<br />및 산업발전 기반 마련</h3> - <ul className="font-NotoSansKR text-xl text-gray-600 leading-relaxed space-y-1"> - <li>- 물 재이용 교육 및 대국민 홍보 강화</li> - <li>- 핵심기술 육성 및 산업발전 지원 강화</li> - </ul> - </div> - </div> - <Image - src="/img/wrAbout/img_06_2.png" - alt="물 재이용량은 2014년 13.3억톤에서 2030년 25.9억톤까지 증가할 것으로 예상됩니다. 그에 따른 기대 효과로는 연간 상수대체 17.9억톤, 물산업 시장성장 약 2조3,541억 원, 건천화된 73개 하천 해소, 약 20,550명의 고용 효과가 포함됩니다." - width={800} - height={400} - className="w-[90%] mx-auto" - /> - </section> - </article> - </main> - </div> - ); -} diff --git a/apps/dm/app/(base-layout)/pages/wrExample/page.tsx b/apps/dm/app/(base-layout)/pages/wrExample/page.tsx deleted file mode 100644 index 4849c02b0..000000000 --- a/apps/dm/app/(base-layout)/pages/wrExample/page.tsx +++ /dev/null @@ -1,364 +0,0 @@ -import Image from "next/image"; - -export default function WrExamplePage() { - return ( - <div className="bg-[#F2F2F2]"> - <div className="relative w-[950px] mx-auto pt-[50px] font-sans"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 rounded-full"></span> - 물 재이용사례 - </h2> - </div> - <div className="bg-[#F2F2F2] text-gray-800"> - <div className="relative w-[950px] mx-auto pt-[50px] font-sans"> - {/* 카드 1 */} - <div className="card-wrap mb-24" id="card1"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 사업사례 1 - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]"> - {/* 사업명 */} - <section> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 경기권역 반도체 사업장 물 재이용 사업<br /> - : 삼성전자 반도체 사업장 하수재이용수 공급(재이용시설 설치, 공급망 구축 등) - </p> - </section> - {/* 사업 배경 및 목적 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 배경 및 목적</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 삼성전자는 환경경영전략 실현과 반도체 공장의 안정적 용수 공급을 위해 하수 재이용수를 공업용수로 도입하기로 결정 - </p> - </section> - {/* 사업 주요 내용 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 1단계 사업[화성·오산(12만㎥/일) → 삼성 화성·기흥(29년 공급)]<br /> - 2단계 사업[수원(21만㎥/일) → 삼성 평택(30년 공급)] - </p> - </section> - {/* 특이사항 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 본 사업은 광역 하수 재이용 공급망 구축의 첫 사업으로 초기 민관협의체를 구성·운영 후 관계기관(환경부, 경기도, 지자체, 공단, 수공, 삼성전자) 간 MOU 체결을 통해 사업을 추진하였으며, 반도체 사업장에 하수 재이용수를 공업용수로 공급하는 국내 첫 사업으로 위협부담 해소를 위해 사업을 단계별로 시행 - </p> - </section> - {/* 단계별 시각 정보 */} - <section className="bg-white px-4 mt-8"> - <Image - src="/img/wrExample/img_ex_01_1.png" - alt="사업단계 설명" - width={800} - height={400} - className="w-[100%] mx-auto mb-8" - /> - </section> - </div> - </div> - - {/* 카드2 */} - <div className="card-wrap mb-24" id="card2"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 사업사례 2 - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]"> - {/* 사업명 */} - <section> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 경기도 파주시 하수재이용<br /> - : 운정·금촌하수처리시설의 하수처리수를 막공법(U/F, R/O)으로 재처리하여 파주 LCD 산업단지내 3개 업체*에 공업용수 공급<br /><br /> - *LG디스플레이(P10공장), 희성전자, 엔비텍 등 - </p> - </section> - {/* 사업 방식 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 민간투자사업(시행사 : 파주그린허브(주), 운영사 : 테크로스) - </p> - </section> - {/* 사업 주요 내용 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 처리공정 : 전처리분리막(U/F) + 역삼투서비(R/O)<br /> - 공급량 : 4만m³/일<br /> - 관로 : 13.6km<br /> - 사업비 : 651억원 - </p> - </section> - {/* 특이사항 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 하수처리수 재이용시설 자체 농축폐수 처리공정 개발 및 적용 성공사례 - </p> - <div className="grid grid-cols-1 gap-0 items-start"> - {/* 좌측 이미지 */} - <div> - <Image - src="/img/wrExample/img_ex_02_1.png" - alt="적용 성공사례 지도" - width={1000} - height={1200} - className="w-[100%] mx-auto" - /> - </div> - - {/* 우측 표 */} - <div className="space-y-3"> - {/* 하수처리수 공급 */} - <div> - <table className="min-w-full h-[500px]text-center border border-black"> - <colgroup> - <col style={{ width: "20%" }} /> - <col style={{ width: "40%" }} /> - <col style={{ width: "40%" }} /> - </colgroup> - <tbody> - <tr> - <td colSpan={3} className="font-NotoSansKR bg-[#ffc000] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">하수처리수 공급</td> - </tr> - <tr className="text-xl h-[30px]"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">위치</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">금촌하수</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">운정하수</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">시설용량</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">44,000㎥/일</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">60,000㎥/일</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">유입량</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">27,000㎥/일<br />자연유하</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">30,000㎥/일<br />이송펌프장</td> - </tr> - </tbody> - </table> - {/* 첫 화살표 (자연유하 / 관로이송) */} - <div className="flex h-[30px] justify-between items-center my-3 px-4"> - <div className="flex text-xl items-center gap-1 text-orange-600 font-bold pl-6"> - <span className="font-NotoSansKR">자연유하: 161m</span> - <Image - src="/img/wrExample/img_arrow_down_1.png" - alt="오른쪽 화살표" - width={24} - height={24} - className="h-6 w-6" - /> - </div> - <div className="flex text-xl items-center gap-1 text-orange-600 font-bold"> - <Image - src="/img/wrExample/img_arrow_down_1.png" - alt="왼쪽 화살표" - width={24} - height={24} - className="h-6 w-6" - /> - <span className="font-NotoSansKR">관로이송: 3.2km</span> - </div> - </div> - </div> - - {/* 물재이용 처리시설 */} - <div> - <table className="min-w-full text-center border border-black"> - <colgroup> - <col style={{ width: "50%" }} /> - <col style={{ width: "50%" }} /> - </colgroup> - <tbody> - <tr> - <td colSpan={2} className="font-NotoSansKR bg-[#00b050] text-xl h-[30px] text-white font-semibold text-center pt-[6px] pb-[6px] px-0">물재이용 처리시설</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">유입유량</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">57,000㎥/일</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">재이용시설</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">농축폐수처리</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">17,000㎥/일</td> - </tr> - </tbody> - </table> - {/* 두 번째 화살표 (공급관로) */} - <div className="flex flex-col items-center my-3"> - <div className="flex h-[30px] items-center gap-1 text-xl text-blue-600 font-semibold"> - <span className="font-NotoSansKR">공급관로 L=10.4km</span> - <Image - src="/img/wrExample/img_arrow_down_2.png" - alt="아래 화살표" - width={24} - height={24} - className="h-6 w-6" - /> - </div> - </div> - </div> - - {/* 수요처 공급 */} - <div> - <table className="min-w-full text-center border border-black"> - <colgroup> - <col style={{ width: "50%" }} /> - <col style={{ width: "50%" }} /> - </colgroup> - <tbody> - <tr> - <td colSpan={2} className="font-NotoSansKR bg-[#3399ff] h-[30px] text-xl text-white font-semibold text-center pt-[6px] pb-[6px] px-0">수요처 공급</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">수요처</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">LG디스플레이 외 2개사</td> - </tr> - <tr className="text-sm"> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">수요량</td> - <td className="font-NotoSansKR text-xl h-[30px] border-black border p-1 font-bold">40,000㎥/일</td> - </tr> - </tbody> - </table> - </div> - </div> - </div> - </section> - </div> - </div> - - {/* 카드3 */} - <div className="card-wrap mb-24" id="card3"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 사업사례 3 - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]"> - {/* 사업명 */} - <section> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 충남 보령 하수재이용<br /> - : 보령·신보령화력발전소에 하루 1만톤의 탈황용수 공급을 위해 보령하수처리시설의 하수처리수 재이용 사업 추진 - </p> - </section> - {/* 사업 방식 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 방식</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 2017년 한국환경공단과 사업대행 위 · 수탁 협약 체결 - </p> - </section> - {/* 사업 주요 내용 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 처리공정 : 전처리분리막(U/F) + 역삼투서비(R/O)<br /> - 공급량 : 1만m3/일<br /> - 관로 : 8.7km<br /> - 사업비 : 315억원(국비 216억원, 재원협의(안)) - </p> - </section> - {/* 특이사항 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">특이사항</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 국내 유일 하수재이용 공업용수 공급 재정사업으로 2016년 충남 서부권 가뭄 시 지역사회 기여 차원에서 기관간 MOU (환경부, 충청남도, 보령시, 한국중부발전㈜)를 바탕 - 으로 추진 되어 경제성보다 공익성을 바탕으로 국가전력 기반시설의 안정적 운영 도모 - </p> - <div className="p-2"> - <Image - src="/img/wrExample/img_ex_03_1.png" - alt="국가전력 기반시설 설명" - width={700} - height={350} - className="w-[100%] mx-auto" - /> - </div> - </section> - </div> - </div> - - {/* 카드4 */} - <div className="card-wrap mb-24" id="card4"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 사업사례 4 - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]"> - {/* 사업명 */} - <section> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업명</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - 경기도 수원시 빗물이용과 중수도 - </p> - </section> - {/* 사업 내용 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 내용</h2> - <p className="font-NotoSansKR text-2xl font-semibold text-gray-800 leading-relaxed"> - (빗물) 월드컵경기장 등 4개소에 노면 빗물분사시스템 및 빗물 공급기를 설치하여 혹서기 도시 열섬효과 및 도로 비산먼지 저감<br /> -   ※ 시설 가동중 도로 노면 온도의 7.2 ~ 26.3℃ 감소 효과 확인(21.7월, 수원시청).<br /> -   다만, 가뭄기나 강우량이 적을 경우 빗물을 원활히 공급하기 다소 곤란<br /><br /> - - (중수도) 수원시청, 장안구청, 월드컵경기장에 빗물 연계 중수도를 화장실·조경용수 등으로 사용하여 수도요금 절감(연 7백~30백만원) - </p> - <div className="grid grid-cols-1 md:grid-cols-2 gap-2 items-start p-2"> - <div> - <Image - src="/img/wrExample/img_ex_04_1.png" - alt="월드컵경기장 인근도로 노면 살수 이미지" - width={350} - height={250} - className="w-[100%] mx-auto" - /> - <span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><월드컵경기장 인근도로 노면 살수></span> - </div> - <div> - <Image - src="/img/wrExample/img_ex_04_2.png" - alt="주유기 모양의 빗물 공급기 이미지" - width={350} - height={250} - className="w-[100%] mx-auto" - /> - <span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><주유기 모양의 빗물 공급기></span> - </div> - <div> - <Image - src="/img/wrExample/img_ex_04_3.png" - alt="빗물주유기에서 살수용수 공급 이미지" - width={350} - height={250} - className="w-[100%] mx-auto" - /> - <span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><빗물주유기에서 살수용수 공급></span> - </div> - <div> - <Image - src="/img/wrExample/img_ex_04_4.png" - alt="빗물 공급기 및 빗물분사장치 위치" - width={350} - height={250} - className="w-[100%] mx-auto" - /> - <span className="font-NotoSansKR text-xl block text-center font-semibold my-2"><빗물 공급기 및 빗물분사장치 위치></span> - </div> - </div> - </section> - </div> - </div> - </div> - </div> - </div> - ); -} - diff --git a/apps/dm/app/(base-layout)/pages/wrOverview/page.tsx b/apps/dm/app/(base-layout)/pages/wrOverview/page.tsx deleted file mode 100644 index 3e5825899..000000000 --- a/apps/dm/app/(base-layout)/pages/wrOverview/page.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import Image from "next/image"; - -export default function WrOverviewPage() { - return ( - <div className="bg-[#F2F2F2]"> - <div className="relative w-[950px] mx-auto pt-[50px] font-sans"> - {/* 카드 1 - 물재이용 사업 추진 절차 */} - <div className="card-wrap mb-24" id="card1"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 물재이용 사업 추진 절차 - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] py-[50px]"> - <table className="table-fixed w-full text-center text-sm"> - <caption className="sr-only">물 재이용 추진 절차 상세 표</caption> - <colgroup> - <col style={{ width: "28%" }} /> - <col style={{ width: "2%" }} /> - <col style={{ width: "35%" }} /> - <col style={{ width: "5%" }} /> - <col style={{ width: "30%" }} /> - </colgroup> - <tbody> - {/* 1단계 */} - <tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">타당성 조사</td><td></td><td></td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 2단계 */} - <tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">매칭시스템 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">사업계획(안) 수립</td><td></td><td></td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 3단계 */} - <tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">하수도정보시스템 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">국고지원 신청</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 4단계 */} - <tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">국고지원 확정 통보</td><td></td><td></td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 5단계 */} - <tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">사업계획 수립(실시설계)</td><td></td><td></td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 6단계 */} - <tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">재원협의(지방환경관서장)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 7단계 */} - <tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">설치승인(시도지사)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 8단계 */} - <tr><td></td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">공사시행</td><td></td><td></td></tr> - <tr><td></td><td></td><td className="font-NotoSansKR text-2xl font-bold text-center">↓</td><td></td><td></td></tr> - {/* 9단계 */} - <tr><td className="font-NotoSansKR text-2xl bg-[#F4D62D] text-black font-bold py-5">(행정관) 기술검토 신청</td><td></td><td className="font-NotoSansKR text-[17px] bg-[#EAEFF3] font-bold py-5">준공(국고정산)</td><td className="font-NotoSansKR text-2xl font-bold">→</td><td className="font-NotoSansKR text-2xl text-[#fff] bg-[#00B076] font-bold py-5">기술검토(한국환경공단)</td></tr> - </tbody> - </table> - </div> - </div> - - {/* 카드 2 - 물재이용 매칭 시스템 */} - <div className="mt-36 card-wrap mb-24" id="card2"> - <h2 className="font-NotoSansKR relative text-center text-4xl font-bold text-gray-900 mb-6"> - <span className="absolute left-1/2 top-[-15px] -translate-x-1/2 w-24 h-2 bg-blue-500 rounded-full"></span> - 물재이용 매칭 시스템이란? - </h2> - <div className="bg-white border border-gray-300 rounded-xl p-[60px] pt-[70px] pb-[70px]"> - {/* 개요 */} - <section> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">개요</h2> - <p className="font-NotoSansKR text-2xl font-bold text-gray-800 leading-relaxed"> - 기후변화 및 지자체의 개발속도 차이에 따라 물 자원의 수요-공급 불균형이 발생하여 빗물, 중수도, 하수처리 재처리수 등의 공급 여유가 있는 지자체의 재이용수를 부족한 지자체 수요처에 공급하여 불균형 해소를 통한 수자원 절약 및 물 자원 순환 - </p> - </section> - - {/* 구성 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">구성</h2> - <ul className=" font-NotoSansKR list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2"> - <li>공급자: 하수처리장, 빗물이용시설, 중수도 등</li> - <li>수요자: 산업체(냉각수), 공공시설(조경, 도로청소), 농민(농업용수) 등</li> - <li>시스템: 수질, 수량, 거리 기반 매칭, 중개</li> - <li>운영주체: 한국환경공단</li> - </ul> - </section> - - {/* 절차 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">사업 주요 내용</h2> - <p className="font-NotoSansKR text-2xl font-bold text-gray-800 leading-relaxed"> - ① 공급자 등록 → ② 수요자 조건 입력 → ③ 매칭 검토 → ④ 시스템 매칭 → ⑤ 계약·공급 이행 - </p> - </section> - - {/* 기대효과 */} - <section className="mt-8"> - <h2 className="font-NotoSansKR text-2xl text-[#657D91] font-bold bg-[#EAEFF3] p-3 rounded mb-4">기대효과</h2> - <ul className="font-NotoSansKR list-[square] pl-6 text-2xl font-bold text-gray-800 leading-relaxed space-y-2"> - <li>물 자원 수요-공급 조정을 통한 효율적 분배</li> - <li>물 재이용시설 데이터 수집으로 체계적 관리</li> - <li>지자체, 사업시행자 등 재이용 정보 획득 용이</li> - </ul> - <div className="text-center my-6"> - <Image - src="/img/wrOverview/img_ov_01_1.png" - alt="하수 재이용 시스템 흐름도. 저장탱크에서 재처리된 물이 손 씻기, 수도, 세정 등에 공급되며 재이용 활성화를 통해 최적의 물공급 방안이 마련되는 과정을 시각화함." - width={650} - height={500} - className="h-[500px] mx-auto mb-4 rounded" - /> - <p className="font-NotoSansKR text-2xl font-bold text-gray-800 mt-10">(하수 재이용 시스템 개요도)</p> - </div> - </section> - </div> - </div> - </div> - </div> - ); -} \ No newline at end of file diff --git a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx deleted file mode 100644 index f8d9704d4..000000000 --- a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx +++ /dev/null @@ -1,387 +0,0 @@ -'use client'; - -import { regionData, regionIdMap } from '@/app/common/comFunction'; -import { useDmStore } from '@/app/store'; -import type { DmStatusDataType } from '@/app/store/wrStatus/type'; -import { CHART_FORM, MainChart, type ChartConfig } from '@common/business_components'; -import React, { useEffect, useRef, useState, useCallback } from 'react'; -import { setCookie } from "../../../../src/shared/lib"; - -setCookie("site-id-key", process.env.NEXT_PUBLIC_SITE_ID || "DM"); - -interface WrStatusChartProps { - chartForm: string; - chartConfig: ChartConfig; - chartData: any; - chartTitle: string; - chartClassName?: string; - graphSize?: number; - yFieldText? : string | undefined; - yFieldText2? : string | undefined; - showLegend? : boolean; - setXAxisYearTick ?: boolean; -} - -export default function WrStatusPage() { - - - - - const { Common, dmStatus, getDmStatusData,initializeCommonData} = useDmStore((state) => state); - const { year:defaultYear, statusData,facilityTotals ,chartData} = dmStatus; - const [selectedRegion, setSelectedRegion] = useState<string | null>(null); - const [hoveredRegion, setHoveredRegion] = useState<string | null>(null); - const [tooltipPosition, setTooltipPosition] = useState<{ x: number; y: number } | null>(null); - const [chartOption, setChartOption] = useState<WrStatusChartProps>({} as WrStatusChartProps); - - const svgContainerRef = useRef<HTMLDivElement>(null); - const colorLegend = [ - { label: '하수', color: '#FF6B6B', key: 'sewage' }, - { label: '빗물', color: '#4ECDC4', key: 'rainwater' }, - { label: '중수도', color: '#45B7D1', key: 'reclaimed' }, - ]; - const facilityTotalsColor = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FED16A']; - // const [defaultYear, setDefaultYear] = useState<string>(""); - // const defaultYear= '2023'; - - - const handleMarkerClick = (regionId: string) => { - if (selectedRegion === regionId) { - setSelectedRegion(null); - } else { - setSelectedRegion(regionId); - } - }; - - - useEffect(() => { - initializeCommonData(); - }, [initializeCommonData]); - - const createDonutMarker = useCallback((svg: SVGSVGElement, x: number, y: number, name: string, values: number[]) => { - const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FED16A']; - const radius = 26; - const innerRadius = 18; - // const regionId = regionIdMap[name]; - const regionId = regionIdMap[name]; - if (!regionId) { - console.warn(`Invalid region name: ${name}`); - return; - } - const g = document.createElementNS('http://www.w3.org/2000/svg', 'g'); - g.setAttribute('transform', `translate(${x}, ${y})`); - g.setAttribute('id', `marker-${name}`); - g.style.cursor = 'pointer'; - g.setAttribute('role', 'button'); - g.setAttribute('tabindex', '0'); - g.setAttribute('aria-label', `${name} 지역 선택`); - - const bg = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - bg.setAttribute('r', radius.toString()); - bg.setAttribute('fill', 'white'); - bg.setAttribute('pointer-events', 'all'); - g.appendChild(bg); - - let startAngle = 0; - values.forEach((value, idx) => { - const endAngle = startAngle + value * 2 * Math.PI; - const x1 = radius * Math.sin(startAngle); - const y1 = -radius * Math.cos(startAngle); - const x2 = radius * Math.sin(endAngle); - const y2 = -radius * Math.cos(endAngle); - const largeArc = endAngle - startAngle > Math.PI ? 1 : 0; - const d = `M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} L 0 0 Z`; - - const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); - path.setAttribute('d', d); - path.setAttribute('fill', colors[idx % colors.length] as string); - g.appendChild(path); - - startAngle = endAngle; - }); - - const center = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); - center.setAttribute('r', innerRadius.toString()); - center.setAttribute('fill', 'white'); - g.appendChild(center); - - const text = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - text.setAttribute('x', '0'); - text.setAttribute('y', '2'); - text.setAttribute('text-anchor', 'middle'); - text.setAttribute('font-size', '13'); - text.setAttribute('font-weight', 'bold'); - text.setAttribute('dominant-baseline', 'middle'); - text.setAttribute('fill', 'black'); - text.textContent = name || ''; - g.appendChild(text); - - g.addEventListener('mouseenter', (e) => { - setHoveredRegion(regionId); - setTooltipPosition({ x: e.clientX, y: e.clientY }); - }); - - g.addEventListener('mouseleave', () => { - setHoveredRegion(null); - setTooltipPosition(null); - }); - - g.addEventListener('click', () => { - handleMarkerClick(regionId); - }); - - g.addEventListener('keydown', (e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - - const matrix = g.getScreenCTM(); - if (!matrix) return; // 또는 console.warn('matrix is null'); return; - - const point = svg.createSVGPoint(); - point.x = 0; - point.y = 0; - const transformed = point.matrixTransform(matrix); - - setTooltipPosition({ x: transformed.x, y: transformed.y }); - setHoveredRegion(regionId); - handleMarkerClick(regionId); - } - }); - - svg.appendChild(g); - }, [handleMarkerClick, regionIdMap]); - - - useEffect(() => { - if(Common.selectSidoData&&Common.selectSidoData.length>1) { - getDmStatusData(Common.selectSidoData); - } - },[Common.selectSidoData, getDmStatusData]) - - useEffect(() => { - const loadSVG = async () => { - try { - const response = await fetch('/img/wrStatus/southKoreaMap.svg'); - const svgText = await response.text(); - - if (svgContainerRef.current) { - svgContainerRef.current.innerHTML = svgText; - - const svg = svgContainerRef.current.querySelector('svg') as SVGSVGElement; - if (svg) { - svg.style.width = '58%'; - svg.style.height = 'auto'; - svg.style.objectFit = 'contain'; - - // Set default fills - const paths = svg.querySelectorAll('path'); - paths.forEach(path => { - if (!path.getAttribute('fill') || path.getAttribute('fill') === 'none') { - path.setAttribute('fill', '#ffffff'); - } - }); - - // Create markers - regionData.forEach(region => { - createDonutMarker(svg, region.x, region.y, region.name, region.values); - }); - } - } - } catch (error) { - console.error('SVG 로딩 실패:', error); - if (svgContainerRef.current) { - svgContainerRef.current.innerHTML = "<p className='text-red-500'>SVG 로딩 실패</p>"; - } - } - }; - - loadSVG(); - }, [createDonutMarker, regionData]); - - useEffect(() => { - const svg = svgContainerRef.current?.querySelector('svg'); - if (svg) { - const paths = svg.querySelectorAll('path'); - paths.forEach(path => { - const pathId = path.getAttribute('id'); - if (pathId) { - if (selectedRegion === pathId) { - path.setAttribute('fill', '#2563eb'); - } else { - const original = '#ffffff'; - path.setAttribute('fill', original); - } - } - }); - } - }, [selectedRegion]); - useEffect(() => { - if(chartData.length>0){ - const chartConfig = chartData.reduce((config, element) => { - config[element.label] = { - label: element.label, - color : "hsl(var(--chart-1))" - }; - return config; - }, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig; - - const Option = { - chartForm : CHART_FORM.BAR, - chartConfig : chartConfig, - chartData : chartData, - chartTitle : '', - chartClassName : "w-full h-[350px] ", - graphSize : 10, - yFieldText : '시설(개소)', - showLegend : true - } - setChartOption(Option) - }else{ - setChartOption({} as WrStatusChartProps) - } - }, [chartData]); - - return ( - <div className="flex h-screen bg-[#F2F2F2] text-gray-800"> - {/* 우측 콘텐츠 영역 */} - <main className="flex-1 p-6 overflow-auto"> - {/* 상단 제목 */} - <div className="mb-6 flex justify-between items-center"> - <h2 className="text-2xl font-bold">물재이용 현황</h2> - </div> - - {/* 콘텐츠 본문 */} - <div className="grid grid-cols-1 xl:grid-cols-2 gap-4"> - {/* 지도 카드 */} - <div className="bg-white rounded-xl shadow border border-[#DBE2E6] overflow-hidden max-h-[900px]"> - {/* 타이틀 영역 */} - <div className="border-b border-[#DBE2E6] px-4 py-3"> - <h3 className="text-[12px] font-semibold">{`재이용시설현황(${defaultYear})`}</h3> - <p className="text-[12px] text-[#999] font-medium">행정구역별 재이용시설현황(개소)</p> - </div> - - {/* 본문 콘텐츠 */} - <div className="p-4"> - <div className="bg-[#E2EDF3] p-4 rounded shadow relative"> - <div className="flex justify-center"> - <div - ref={svgContainerRef} - className="w-full max-w-5xl max-h-[680px] overflow-y-auto block mx-auto" - aria-label="대한민국 지역별 재이용시설 분포도" - role="region" - tabIndex={0} - /> - </div> - - {typeof window !== 'undefined' && hoveredRegion && tooltipPosition && ( - <div - className="fixed bg-white text-[12px] text-gray-700 border border-gray-300 shadow-lg rounded px-3 py-2 z-50" - style={{ - top: tooltipPosition.y + 10, - left: tooltipPosition.x + 10, - pointerEvents: 'none', - }} - > - <div className="font-semibold mb-1">{hoveredRegion}</div> - <div className="space-y-1"> - {colorLegend.map(({ label, color, key }) => ( - <div key={key+'' +color} className="flex items-center space-x-2"> - <span className="text-[12px] inline-block w-3 h-3 rounded-sm" style={{ backgroundColor: color }}></span> - <span className="text-[12px]"> - {label}:{' '} - {statusData?.find(d => d.region === hoveredRegion)?.[key as keyof DmStatusDataType] || '-'} - </span> - </div> - ))} - </div> - </div> - )} - - {/* 범례 */} - <div className="absolute bottom-2 right-2 bg-white rounded-lg shadow-md p-4 text-sm space-y-2"> - {facilityTotals?.map((item, index) => ( - <div key ={'fcltTotal' +index}className="flex items-center space-x-2"> - <span className="inline-block w-4 h-4 rounded-sm" style={{backgroundColor:facilityTotalsColor[index]}}></span> - <span className="text-[12px] text-gray-900">{item}</span> - </div> - )) } - </div> - </div> - </div> - </div> - - {/* 우측 콘텐츠 (표 + 차트) */} - <div className="col-span-1 flex flex-col space-y-4"> - {/* 재이용시설현황 표 */} - <div className="bg-white rounded-xl shadow border border-[#DBE2E6] overflow-hidden"> - {/* 타이틀 + 버튼 영역 */} - <div className="flex justify-between items-center border-b border-[#436d85] px-4 py-3"> - <div> - <h3 className="text-[12px] font-semibold">{`재이용시설현황(${defaultYear})`}</h3> - <p className="text-[12px] text-[#999] font-medium">행정구역별 재이용시설현황[개소(천m³/년)]</p> - </div> - </div> - <div className="p-4"> - <div className="rounded border border-[#DBE2E6] shadow overflow-auto max-h-[292px] p-4"> - <table className="min-w-full text-sm text-center"> - <caption className="sr-only text-[12px]">행정구역별 하수, 빗물, 중수도, 온배수 재이용시설 수</caption> - <thead className="bg-gray-50 text-xs text-gray-600 uppercase border-b"> - <tr> - <th className="text-[12px] font-semibold px-4 py-2">번호</th> - <th className="text-[12px] font-semibold px-4 py-2">시도</th> - <th className="text-[12px] font-semibold px-4 py-2">하수재이용시설</th> - <th className="text-[12px] font-semibold px-4 py-2">빗물이용시설</th> - <th className="text-[12px] font-semibold px-4 py-2">중수도시설</th> - {/* <th className="text-[12px] font-semibold px-4 py-2">하수(이용량,천𝓂³/년)</th> - <th className="text-[12px] font-semibold px-4 py-2">빗물(이용량,천𝓂³/년)</th> - <th className="text-[12px] font-semibold px-4 py-2">중수도(이용량,천𝓂³/년)</th> - <th className="text-[12px] font-semibold px-4 py-2">온배수(이용량,천𝓂³/년)</th> */} - </tr> - </thead> - <tbody className="divide-y"> - {statusData?.map((row, index) => ( - <tr key={index} className="hover:bg-gray-50"> - <td className="text-[12px] px-4 py-2 text-center"> - {index+1} - </td> - <td className="text-[12px]">{row.region}</td> - <td className="text-[12px]">{row.sewage}</td> - <td className="text-[12px]">{row.rainwater}</td> - <td className="text-[12px]">{row.reclaimed}</td> - </tr> - ))} - </tbody> - </table> - </div> - </div> - </div> - - {/* 재이용통계 차트 */} - <div className="bg-white rounded-xl shadow border border-[#DBE2E6] overflow-hidden"> - <div className="border-b border-[#DBE2E6] px-4 py-3"> - <h3 className="text-[12px] font-semibold">{`재이용통계(${defaultYear})`}</h3> - <p className="text-[12px] text-[#999] font-medium">행정구역별 재이용시설 현황 개소수 그래프</p> - </div> - <div className="p-4 bg-[#f9fbfd]"> - <div className="h-[305px] text-gray-500"> - {chartOption? - <MainChart - chartForm={chartOption.chartForm} - chartConfig={chartOption.chartConfig} - chartData={chartOption.chartData?.filter(item=>item.label!= '')} - chartTitle={chartOption.chartTitle} - chartClassName={chartOption.chartClassName} - graphSize={chartOption.graphSize} - yFieldText={chartOption.yFieldText} - showLegend={chartOption.showLegend} - /> - :<></>} - </div> - </div> - </div> - </div> - </div> - </main> - </div> - ); -}; diff --git a/apps/dm_new/app/(landing)/reuse/_components/CaseTabsCarousel.tsx b/apps/dm/app/(landing)/reuse/_components/CaseTabsCarousel.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/CaseTabsCarousel.tsx rename to apps/dm/app/(landing)/reuse/_components/CaseTabsCarousel.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/ImageSlider.tsx b/apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/ImageSlider.tsx rename to apps/dm/app/(landing)/reuse/_components/ImageSlider.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx b/apps/dm/app/(landing)/reuse/_components/Lnb.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/Lnb.tsx rename to apps/dm/app/(landing)/reuse/_components/Lnb.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/LoginStatus.tsx b/apps/dm/app/(landing)/reuse/_components/LoginStatus.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/LoginStatus.tsx rename to apps/dm/app/(landing)/reuse/_components/LoginStatus.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/PostcodeModal.tsx b/apps/dm/app/(landing)/reuse/_components/PostcodeModal.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/PostcodeModal.tsx rename to apps/dm/app/(landing)/reuse/_components/PostcodeModal.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/SideIndicator.tsx b/apps/dm/app/(landing)/reuse/_components/SideIndicator.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/SideIndicator.tsx rename to apps/dm/app/(landing)/reuse/_components/SideIndicator.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/SubPageHeader.tsx b/apps/dm/app/(landing)/reuse/_components/SubPageHeader.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/SubPageHeader.tsx rename to apps/dm/app/(landing)/reuse/_components/SubPageHeader.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_components/TopNav.tsx b/apps/dm/app/(landing)/reuse/_components/TopNav.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_components/TopNav.tsx rename to apps/dm/app/(landing)/reuse/_components/TopNav.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_constants/lnb.ts b/apps/dm/app/(landing)/reuse/_constants/lnb.ts similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_constants/lnb.ts rename to apps/dm/app/(landing)/reuse/_constants/lnb.ts diff --git a/apps/dm_new/app/(landing)/reuse/_hooks/useScrollSpy.ts b/apps/dm/app/(landing)/reuse/_hooks/useScrollSpy.ts similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_hooks/useScrollSpy.ts rename to apps/dm/app/(landing)/reuse/_hooks/useScrollSpy.ts diff --git a/apps/dm_new/app/(landing)/reuse/_sections/Cases.tsx b/apps/dm/app/(landing)/reuse/_sections/Cases.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_sections/Cases.tsx rename to apps/dm/app/(landing)/reuse/_sections/Cases.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx b/apps/dm/app/(landing)/reuse/_sections/Hero.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_sections/Hero.tsx rename to apps/dm/app/(landing)/reuse/_sections/Hero.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx b/apps/dm/app/(landing)/reuse/_sections/IntroCards.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_sections/IntroCards.tsx rename to apps/dm/app/(landing)/reuse/_sections/IntroCards.tsx diff --git a/apps/dm_new/app/(landing)/reuse/_sections/ProcessFlow.tsx b/apps/dm/app/(landing)/reuse/_sections/ProcessFlow.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/_sections/ProcessFlow.tsx rename to apps/dm/app/(landing)/reuse/_sections/ProcessFlow.tsx diff --git a/apps/dm_new/app/(landing)/reuse/apply/layout.tsx b/apps/dm/app/(landing)/reuse/apply/layout.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/apply/layout.tsx rename to apps/dm/app/(landing)/reuse/apply/layout.tsx diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx b/apps/dm/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx rename to apps/dm/app/(landing)/reuse/apply/modal/CandidateSiteModal.tsx diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx b/apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx similarity index 98% rename from apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx rename to apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx index f997d88e1..774ab81b8 100644 --- a/apps/dm_new/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx +++ b/apps/dm/app/(landing)/reuse/apply/modal/SimilarCaseModal.tsx @@ -71,16 +71,18 @@ export default function SimilarCaseModal({ onClose, rows, detailsByCaseId, + onSearch }: { open: boolean; onClose: () => void; rows: SimilarCaseRow[]; detailsByCaseId: Record<string, SimilarCaseDetail | undefined>; + onSearch :(criterion: Criterion) => void; }) { const titleId = useId(); const [mounted, setMounted] = useState(false); - const [criterion, setCriterion] = useState<Criterion>(""); + const [criterion, setCriterion] = useState<Criterion>("supplyAmt"); const [selectedCaseId, setSelectedCaseId] = useState<string>(""); useEffect(() => setMounted(true), []); @@ -88,7 +90,8 @@ export default function SimilarCaseModal({ // 모달 열릴 때 초기화 useEffect(() => { if (!open) return; - setCriterion(""); + setCriterion("supplyAmt"); + onSearch("supplyAmt"); setSelectedCaseId(""); }, [open]); @@ -192,12 +195,14 @@ export default function SimilarCaseModal({ const v = e.target.value as Criterion; setCriterion(v); setSelectedCaseId(""); + + if(v){ + onSearch(v); + } }} className="h-10 w-[180px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" > - <option value="">유사기준선택</option> <option value="supplyAmt">공급량</option> - <option value="treatment">처리공법</option> <option value="usePurpose">이용용도</option> </select> </div> diff --git a/apps/dm_new/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx b/apps/dm/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx rename to apps/dm/app/(landing)/reuse/apply/modal/WaterQualityRefModal.tsx diff --git a/apps/dm_new/app/(landing)/reuse/apply/place/page.tsx b/apps/dm/app/(landing)/reuse/apply/place/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/apply/place/page.tsx rename to apps/dm/app/(landing)/reuse/apply/place/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/apply/register/page.tsx b/apps/dm/app/(landing)/reuse/apply/register/page.tsx similarity index 99% rename from apps/dm_new/app/(landing)/reuse/apply/register/page.tsx rename to apps/dm/app/(landing)/reuse/apply/register/page.tsx index 2a9bbaf9a..9350f59f4 100644 --- a/apps/dm_new/app/(landing)/reuse/apply/register/page.tsx +++ b/apps/dm/app/(landing)/reuse/apply/register/page.tsx @@ -633,6 +633,9 @@ export default function DemandRegisterPage() { onClose={() => setOpenSimilar(false)} rows={similarRows} detailsByCaseId={similarDetailsByCaseId} + onSearch={async (criterion) => { + console.log(criterion); + }} /> </> ); diff --git a/apps/dm_new/app/(landing)/reuse/apply/status/page.tsx b/apps/dm/app/(landing)/reuse/apply/status/page.tsx similarity index 98% rename from apps/dm_new/app/(landing)/reuse/apply/status/page.tsx rename to apps/dm/app/(landing)/reuse/apply/status/page.tsx index b69b0fdd9..3282f54a5 100644 --- a/apps/dm_new/app/(landing)/reuse/apply/status/page.tsx +++ b/apps/dm/app/(landing)/reuse/apply/status/page.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import SubPageHeader from "../../_components/SubPageHeader"; +//import { useDmStore } from "@/app/store"; type WaterDetail = { toc: string; @@ -114,12 +115,17 @@ type PairField = { label: string; value: string }; export default function DemandStatusPage() { const rows = useMemo(() => MOCK, []); - // ✅ rows가 빈 경우도 대비 (런타임 오류 방지) + // rows가 빈 경우도 대비 (런타임 오류 방지) const [selectedId, setSelectedId] = useState<number | null>(rows[0]?.id ?? null); - // ✅ 상세영역 스크롤 이동용 + // 상세영역 스크롤 이동용 const detailRef = useRef<HTMLDivElement | null>(null); + // update + /* const updateSdmInfoData = useDmStore( + (state) => state.updateSdmInfoData + ); */ + // ✅ selected 계산 안정화 const selected = useMemo(() => { if (!rows.length) return null; diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/australia/page.tsx b/apps/dm/app/(landing)/reuse/business/globalCase/australia/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/globalCase/australia/page.tsx rename to apps/dm/app/(landing)/reuse/business/globalCase/australia/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/eu/page.tsx b/apps/dm/app/(landing)/reuse/business/globalCase/eu/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/globalCase/eu/page.tsx rename to apps/dm/app/(landing)/reuse/business/globalCase/eu/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/overview/page.tsx b/apps/dm/app/(landing)/reuse/business/globalCase/overview/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/globalCase/overview/page.tsx rename to apps/dm/app/(landing)/reuse/business/globalCase/overview/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/singapore/page.tsx b/apps/dm/app/(landing)/reuse/business/globalCase/singapore/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/globalCase/singapore/page.tsx rename to apps/dm/app/(landing)/reuse/business/globalCase/singapore/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/globalCase/taiwan/page.tsx b/apps/dm/app/(landing)/reuse/business/globalCase/taiwan/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/globalCase/taiwan/page.tsx rename to apps/dm/app/(landing)/reuse/business/globalCase/taiwan/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/layout.tsx b/apps/dm/app/(landing)/reuse/business/layout.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/layout.tsx rename to apps/dm/app/(landing)/reuse/business/layout.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/rain/suwon/page.tsx b/apps/dm/app/(landing)/reuse/business/rain/suwon/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/rain/suwon/page.tsx rename to apps/dm/app/(landing)/reuse/business/rain/suwon/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/bolyeong/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/bolyeong/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/bolyeong/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/bolyeong/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/cheongju/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/cheongju/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/cheongju/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/cheongju/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/guri/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/guri/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/guri/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/guri/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/hwanggu/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/hwanggu/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/hwanggu/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/hwanggu/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/paju/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/paju/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/paju/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/paju/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/business/sewage/yeosu/page.tsx b/apps/dm/app/(landing)/reuse/business/sewage/yeosu/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/business/sewage/yeosu/page.tsx rename to apps/dm/app/(landing)/reuse/business/sewage/yeosu/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx b/apps/dm/app/(landing)/reuse/cases/greywater/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/cases/greywater/page.tsx rename to apps/dm/app/(landing)/reuse/cases/greywater/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/cases/layout.tsx b/apps/dm/app/(landing)/reuse/cases/layout.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/cases/layout.tsx rename to apps/dm/app/(landing)/reuse/cases/layout.tsx diff --git a/apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx b/apps/dm/app/(landing)/reuse/cases/rain/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/cases/rain/page.tsx rename to apps/dm/app/(landing)/reuse/cases/rain/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx b/apps/dm/app/(landing)/reuse/cases/treated/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/cases/treated/page.tsx rename to apps/dm/app/(landing)/reuse/cases/treated/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx b/apps/dm/app/(landing)/reuse/intro/greywater/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/intro/greywater/page.tsx rename to apps/dm/app/(landing)/reuse/intro/greywater/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/intro/law/page.tsx b/apps/dm/app/(landing)/reuse/intro/law/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/intro/law/page.tsx rename to apps/dm/app/(landing)/reuse/intro/law/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/intro/layout.tsx b/apps/dm/app/(landing)/reuse/intro/layout.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/intro/layout.tsx rename to apps/dm/app/(landing)/reuse/intro/layout.tsx diff --git a/apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx b/apps/dm/app/(landing)/reuse/intro/rain/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/intro/rain/page.tsx rename to apps/dm/app/(landing)/reuse/intro/rain/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx b/apps/dm/app/(landing)/reuse/intro/treated/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/intro/treated/page.tsx rename to apps/dm/app/(landing)/reuse/intro/treated/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/layout.tsx b/apps/dm/app/(landing)/reuse/layout.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/layout.tsx rename to apps/dm/app/(landing)/reuse/layout.tsx diff --git a/apps/dm_new/app/(landing)/reuse/page.tsx b/apps/dm/app/(landing)/reuse/page.tsx similarity index 100% rename from apps/dm_new/app/(landing)/reuse/page.tsx rename to apps/dm/app/(landing)/reuse/page.tsx diff --git a/apps/dm_new/app/(landing)/reuse/reuse.module.css b/apps/dm/app/(landing)/reuse/reuse.module.css similarity index 100% rename from apps/dm_new/app/(landing)/reuse/reuse.module.css rename to apps/dm/app/(landing)/reuse/reuse.module.css diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index e410ad2a7..1c625bcac 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -29,10 +29,15 @@ const Map = () => { ruseUtztnFcltCd: fclt.pbnstRuseFcltId, })); + if (!window.opener || typeof window.opener.postMessage !== "function") { + alert("부모 창을 찾을 수 없습니다. 팝업이 'noopener'로 열렸거나, 보안 정책으로 차단되었을 수 있어요."); + return; + } + window.opener.postMessage( { type: "SET_SUBS_INFO", - payload: subsInfoList, // 또는 필요한 데이터 + payload: subsInfoList, // 배열로 전송 (최대 3개) }, "*" ); @@ -52,9 +57,23 @@ const Map = () => { //하수처리시설코드로 재이용시설 목록 조회 const reuseData = async (ruseFcltPk: any) => { + if (!ruseFcltPk) { + console.warn("[reuseData] ruseFcltPk가 없습니다."); + return; + } try { + console.log("[reuseData] API 호출 - ruseFcltPk:", ruseFcltPk); const response = await mapService.getPbnstRuseFclts(ruseFcltPk); - const data = response.data || []; + console.log("[reuseData] API 응답 전체:", response); + // 응답이 배열이면 바로 사용, 객체면 .data 추출 + const data = Array.isArray(response) + ? response + : Array.isArray(response?.data) + ? response.data + : response?.data + ? [response.data] + : []; + console.log("[reuseData] 파싱된 data:", data); setSelectedFclts((prev) => { const newItems = data.filter( (item: any) => @@ -62,7 +81,7 @@ const Map = () => { Object.keys(item).length > 0 && !prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId) ); - console.log(newItems); + console.log("[reuseData] 추가될 newItems:", newItems); return [...prev, ...newItems]; }); } catch (error) { @@ -75,11 +94,45 @@ const Map = () => { fetchData(); }, []); + // 부모창에 초기 선택값 요청 (팝업 열릴 때 기존 선택 목록 받아오기) + useEffect(() => { + const origin = window.location.origin; + if (window.opener) { + window.opener.postMessage({ type: "REQUEST_INIT_SUBS_INFO" }, origin); + } + }, []); + + // 부모창에서 초기 선택값 수신 + useEffect(() => { + const origin = window.location.origin; + + const handler = (event: MessageEvent) => { + if (event.origin !== origin) return; + + const data = event.data; + if (!data || typeof data !== "object") return; + + if (data.type === "INIT_SUBS_INFO") { + const list = Array.isArray(data.payload) ? data.payload : []; + setSelectedFclts( + list.map((x) => ({ + ruseUtztnFcltNm: x.ruseUtztnFcltNm ?? "", + pbnstRuseFcltId: x.ruseUtztnFcltCd ?? "", + })) + ); + } + }; + + window.addEventListener("message", handler); + return () => window.removeEventListener("message", handler); + }, []); + // 레이어 생성 후 초기화 처리 const onCheckedLayersChange = () => { //수요처 ID const urlParams = new URLSearchParams(window.location.search); const dmdInfoId = urlParams.get("dmdInfoId"); + console.log("[하이라이트] dmdInfoId:", dmdInfoId); //수요처 위치로 화면 이동 const maps = gisMaps.current; @@ -88,22 +141,42 @@ const Map = () => { const featureId = `${layerNm}.${dmdInfoId}`; let foundFeature: any = null; + // 현재 로드된 레이어 목록 출력 + const allLayerNames = maps + .getLayers() + .getArray() + .map((l: any) => l.get("lyrTyNm") || l.get("name") || "(unnamed)"); + console.log("[하이라이트] 전체 레이어 목록:", allLayerNames); + console.log("[하이라이트] 탐색 대상 레이어:", layerNm); + console.log("[하이라이트] 탐색 대상 featureId:", featureId); + maps .getLayers() .getArray() .forEach((layer: any) => { if (layer.get("lyrTyNm") == layerNm) { + console.log("[하이라이트] 레이어 발견:", layerNm); const source = layer.getSource?.(); if (source?.getFeatureById) { const feature = source.getFeatureById(String(featureId).trim()); + console.log("[하이라이트] feature 탐색결과:", feature); if (feature) { foundFeature = feature; return; - } + } + // feature 못 찾을 때 소스 내 전체 feature ID 출력 + if (source?.getFeatures) { + const allIds = source.getFeatures().map((f: any) => f.getId()); + console.log("[하이라이트] 소스 내 전체 feature ID:", allIds); + } } } }); + if (!foundFeature) { + console.warn("[하이라이트] feature를 찾지 못했습니다. 위 레이어 목록/ID를 확인하세요."); + } + if (foundFeature) { const geometry = foundFeature.getGeometry(); @@ -133,7 +206,7 @@ const Map = () => { { width: 30 } ); } else { - alert("수요처의 좌표정보를 찾을수 없으므로, 상단 조회조건의 시설명을 입력하여 위치를 조회하시기 바랍니다."); + console.warn("수요처의 좌표정보를 찾을 수 없습니다. dmdInfoId:", dmdInfoId); } const formatNumber = (val: any) => { @@ -185,6 +258,7 @@ const Map = () => { // 검색해서 재이용시설 선택하면 해당 시설로 화면 이동 처리 useEffect(() => { + console.warn(selectDataPk); if (!selectDataPk || Object.keys(selectDataPk).length === 0) return; const maps = gisMaps.current; let foundFeature: any = null; @@ -226,8 +300,12 @@ const Map = () => { // 재이용시설 클릭 시 선택 시설목록에 추가 const onFeatureSelect = (data: any) => { + // feature의 모든 속성 출력 (어떤 키가 있는지 확인) + const properties = data.getProperties?.() ?? {}; + console.log("[onFeatureSelect] feature 속성 전체:", properties); // 처리시설코드 const swtrmtFcltCd = data.get("swtrmt_fclt_cd"); + console.log("[onFeatureSelect] swtrmt_fclt_cd:", swtrmtFcltCd); reuseData(swtrmtFcltCd); }; diff --git a/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx b/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx index bdf4d7e2c..5c742684f 100644 --- a/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/selectedFcltList.tsx @@ -22,36 +22,32 @@ export default function SelectedFcltList({ const TableContent = ( <> - <thead className="bg-gray-100 text-gray-700 text-[13px]"> + <thead className="bg-gray-100 text-gray-600 text-[11px] font-medium border-b border-cyan-800"> <tr> - <th className="px-3 py-2 text-left border-b">시설명</th> - <th className="px-3 py-2 text-right border-b"> - 하수처리량 - <br /> - (㎥/일) + <th className="px-2 py-1.5 text-left w-1/3">시설명</th> + <th className="px-2 py-1.5 text-right whitespace-nowrap"> + 처리량(㎥/일) </th> - <th className="px-3 py-2 text-right border-b"> - 재이용량 - <br /> - (㎥/일) + <th className="px-2 py-1.5 text-right whitespace-nowrap"> + 재이용량(㎥/일) </th> - <th className="px-3 py-2 border-b">삭제</th> + <th className="px-2 py-1.5 text-center w-10">삭제</th> </tr> </thead> <tbody> {data.map((item) => ( - <tr key={item.pbnstRuseFcltId} className="border-b last:border-none"> - <td className="px-3 py-2 text-[12px]">{item.ruseUtztnFcltNm}</td> - <td className="px-3 py-2 text-right text-[12px]"> + <tr key={item.pbnstRuseFcltId} className="border-b border-gray-100 last:border-none hover:bg-gray-50/50"> + <td className="px-2 py-1.5 text-[11px] font-medium text-gray-800 break-keep">{item.ruseUtztnFcltNm}</td> + <td className="px-2 py-1.5 text-right text-[11px] text-gray-600"> {Number(item.splyExtraCpct || 0).toLocaleString()} </td> - <td className="px-3 py-2 text-right text-[12px]"> + <td className="px-2 py-1.5 text-right text-[11px] text-gray-600"> {Number(item.ruseCpct || 0).toLocaleString()} </td> - <td className="px-3 py-2 text-center"> + <td className="px-2 py-1.5 text-center"> <button onClick={() => handleDelete(item.pbnstRuseFcltId)} - className="text-red-500 hover:text-red-700 text-sm font-bold" + className="text-gray-400 hover:text-red-500 text-[11px] font-bold transition-colors" aria-label="삭제" title="삭제" > @@ -64,15 +60,13 @@ export default function SelectedFcltList({ </> ); - if (data.length === 0) return null; - return ( - <div className="fixed right-40 top-[8rem] w-[40rem] max-h-[130rem] overflow-auto bg-white shadow-lg rounded p-4 border border-gray-300 z-50"> - <h3 className="bg-cyan-700 text-white text-xl font-bold mb-2 p-2"> + <div className="fixed right-20 top-[3.5rem] w-[22rem] max-h-[80vh] overflow-hidden bg-white/95 backdrop-blur-sm shadow-[0_4px_12px_rgba(0,0,0,0.1)] rounded-lg border border-gray-200 z-50 flex flex-col"> + <h3 className="bg-cyan-700 text-white text-[13px] font-semibold p-2"> 선택된 시설 </h3> - <div className="overflow-x-auto"> - <table className="w-full text-sm text-gray-800 border border-gray-200"> + <div className="overflow-y-auto w-full"> + <table className="w-full text-sm text-gray-800 border-t border-gray-200 table-fixed"> {TableContent} </table> </div> diff --git a/apps/dm/app/(popup-layout)/map/service/map.ts b/apps/dm/app/(popup-layout)/map/service/map.ts index 00e3ab561..dea14f0d0 100644 --- a/apps/dm/app/(popup-layout)/map/service/map.ts +++ b/apps/dm/app/(popup-layout)/map/service/map.ts @@ -6,7 +6,7 @@ import { DEV_URL_WR, DEV_URL_SN } from "@/components/ui/config"; export type LyrStngProps = { lyr_sn: string lyr_nm: string - upper_lyr_sn: string + upper_lyr_sn: string drawOrdr: string flterSql: string gobjTy: string @@ -29,7 +29,7 @@ export type LyrStngProps = { export const mapService = { //레이어 설정 조회 getLyrStngs: async () => { - const response = await fetch(`${DEV_URL_SN}/api/v1/map/lyrStngs/DM_NETWORK`, { + const response = await fetch(`${DEV_URL_SN}/map/lyrStngs/DM_NETWORK`, { method: 'GET', headers: { 'Accept': 'application/json', @@ -42,7 +42,7 @@ export const mapService = { return response.json(); }, - getPbnstRuseFclts: async (ruseFcltPk:any) => { + getPbnstRuseFclts: async (ruseFcltPk: any) => { const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}`, { method: 'GET', headers: { diff --git a/apps/dm_new/app/(with-layout)/apply/dmdco/page.tsx b/apps/dm/app/(with-layout)/apply/dmdco/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/apply/dmdco/page.tsx rename to apps/dm/app/(with-layout)/apply/dmdco/page.tsx diff --git a/apps/dm_new/app/(with-layout)/apply/layout.tsx b/apps/dm/app/(with-layout)/apply/layout.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/apply/layout.tsx rename to apps/dm/app/(with-layout)/apply/layout.tsx diff --git a/apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx b/apps/dm/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx rename to apps/dm/app/(with-layout)/apply/modal/WaterQualityRefModal.tsx diff --git a/apps/dm/app/(with-layout)/apply/register/page.tsx b/apps/dm/app/(with-layout)/apply/register/page.tsx new file mode 100644 index 000000000..db1c47cb5 --- /dev/null +++ b/apps/dm/app/(with-layout)/apply/register/page.tsx @@ -0,0 +1,895 @@ +// app/(with-layout)/apply/register/page.tsx +/** + * 수요정보등록(수정) 화면 + * + * 🔗 사용 API + * - 수요정보등록: POST /reuseFc/request/demandInfo + * - 수요정보조회: GET /reuseFc/request/' + dmdInfoId - getSdmInfoStatusList + */ +"use client"; + +import { useMemo, useState, useRef, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import SubPageHeader from "@/components/SubPageHeader"; +import WaterQualityRefModal from "../modal/WaterQualityRefModal"; +import CandidateSiteModal, { type CandidateSite } from "@/components/CandidateSiteModal"; +import { useDmStore } from "@/app/store"; +import type { SdmInfoFormData } from "@/app/store/sdmMng"; +import SimilarCaseModal, { type SimilarCaseDetail, type SimilarCaseRow } from "@/app/(landing)/reuse/apply/modal/SimilarCaseModal"; +//import type { SubsInfo } from "@/app/store/map/type"; + +type PHRange = { min: string; max: string }; + +export default function DemandRegisterPage() { + const router = useRouter(); + const today = useMemo(() => { + const d = new Date(); + const yyyy = d.getFullYear(); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + const dd = String(d.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; + }, []); + + const [openRef, setOpenRef] = useState(false); + + const [openCand, setOpenCand] = useState(false); + const [selectedCands, setSelectedCands] = useState<CandidateSite[]>([]); + + // 기본정보 + const [reqDate, setReqDate] = useState<string>(today); + const [reqQty, setReqQty] = useState<string>(""); + const [usePurpose, setUsePurpose] = useState<string>(""); + const [hopePrice, setHopePrice] = useState<string>(""); + + // 요구수질 + const [coliform, setColiform] = useState<string>(""); // 총대장균군수 + const [noDetect, setNoDetect] = useState<boolean>(false); + + const [tn, setTn] = useState<string>(""); + const [residualCl, setResidualCl] = useState<string>(""); + const [tp, setTp] = useState<string>(""); + const [turbidity, setTurbidity] = useState<string>(""); + const [ph, setPh] = useState<PHRange>({ min: "", max: "" }); + const [bod, setBod] = useState<string>(""); + const [chloride, setChloride] = useState<string>(""); + const [odor, setOdor] = useState<string>("불쾌하지 않을 것"); + const [ec, setEc] = useState<string>(""); + const [color, setColor] = useState<string>(""); + const [remark, setRemark] = useState<string>(""); + + // 공급처 후보지 선택(필수아님) + const [cand1, setCand1] = useState(""); + const [cand2, setCand2] = useState(""); + const [cand3, setCand3] = useState(""); + //const [candidateList, setCandidateList] = useState<SubsInfo[]>([]); + + const useTypesData = useDmStore((state) => state.Sdm.useTypesData); + const getUseTypesData = useDmStore((state) => state.getUseTypesData); + const [openSimilar, setOpenSimilar] = useState(false); + + // 후보지 팝업 메시지 핸들러 + const candsRef = useRef<{ name: string; code: string }[]>([]); + + useEffect(() => { + const origin = window.location.origin; + + const handleMessage = (event: MessageEvent) => { + const data = event.data; + if (!data || typeof data !== "object") return; + + if (data.type === "SET_SUBS_INFO") { + // 팝업에서 배열로 전송 (최대 3개) + const list = Array.isArray(data.payload) ? data.payload : []; + setCand1(list[0]?.ruseUtztnFcltNm ?? ""); + setCand2(list[1]?.ruseUtztnFcltNm ?? ""); + setCand3(list[2]?.ruseUtztnFcltNm ?? ""); + // 추후 REQUEST_INIT_SUBS_INFO 응답용 ref 갱신 + candsRef.current = list.map((x: any) => ({ + name: x.ruseUtztnFcltNm ?? "", + code: x.ruseUtztnFcltCd ?? "", + })); + } + + if (data.type === "REQUEST_INIT_SUBS_INFO") { + // 팝업이 현재 선택된 후보지 초기값을 요청하면 응답 + const initList = candsRef.current + .filter((x) => x.name) + .map((x) => ({ + ruseUtztnFcltNm: x.name, + ruseUtztnFcltCd: x.code, + })); + const target = event.source as Window | null; + target?.postMessage( + { type: "INIT_SUBS_INFO", payload: initList }, + { targetOrigin: origin } + ); + } + }; + + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, []); + + // 유사사례 + const similarCaseList = useDmStore((state) => state.Sdm.similarCaseList); + const getSimilarCaseList = useDmStore((state) => state.getSimilarCaseList); + const [farmText, setFarmText] = useState("이하"); + + const insertSdmInfoData = useDmStore( + (state) => state.insertSdmInfoData + ); + + useEffect(() => { + getUseTypesData(); + }, [getUseTypesData]); + + useEffect(() => { + const handler = (event: MessageEvent) => { + if (!event.data) return; + + if (event.data.type === "SET_SUBS_INFO") { + const list = event.data.payload || []; + + // 최대 3개 + setCand1(list[0]?.ruseUtztnFcltNm || ""); + setCand2(list[1]?.ruseUtztnFcltNm || ""); + setCand3(list[2]?.ruseUtztnFcltNm || ""); + //setCandidateList(list); + } + }; + + window.addEventListener("message", handler); + + return () => { + window.removeEventListener("message", handler); + }; + }, []); + + // option + // 'req':필수 / 'num' : 숫자 / 'num+[숫자] : 자릿수 + const validation = (val, text, option: string[] = []) => { + if (option.includes('req')) { + if (val.trim() === "" || val === undefined) { + alert(`${text} 항목은 필수 입력값입니다.`); + return false; + } + } else { + if (val.trim() === "") { + return true; + } + } + + const numOption = option.find((opt) => opt.startsWith("num")); + if (numOption) { + const isDot = numOption.includes("Dot"); + + // 3. 숫자 체크 + const regex = isDot ? /^\d+(\.\d+)?$/ : /^\d+$/; + + if (!regex.test(val)) { + alert( + `${text}은(는) ${isDot ? "숫자 또는 소수" : "숫자" + }만 입력 가능합니다.` + ); + return false; + } + + // 4. 자리수 체크 + const lengthStr = numOption.replace("numDot", "").replace("num", ""); + + if (lengthStr) { + const length = Number(lengthStr); + + if (isDot) { + // 🔥 소수 자리수 체크 (소수점 기준) + const decimalPart = val.split(".")[1]; + + if (decimalPart && decimalPart.length > length) { + alert(`${text}은(는) 소수점 ${length}자리까지 가능합니다.`); + return false; + } + } else { + // 정수 자리수 체크 + if (val.length !== length) { + alert(`${text}은(는) ${length}자리여야 합니다.`); + return false; + } + } + } + } + return true; + } + + const rows: SimilarCaseRow[] = similarCaseList.map((el, idx) => ({ + caseId: String(idx), + projectName: el.ruseBizNm, + period: `${el.bizBgngDate} ~ ${el.bizEndDate}`, + + supplyFacilityType: el.splyFcltKnd, + supplyName: el.splyFcltNm, + supplyCapacity: Number(el.splyFcltCpct), + + demandName: el.dmdFcltNm, + demandAddress: el.dmdFcltAddr, + + facilityCapacity: Number(el.ruseFcltCpct), + pipeKm: Number(el.ruseFcltPplLen), + purpose: el.ruseFcltUsg, + + supplyAmt: Number(el.ruseFcltSplyCpct), + treatment: el.ruseFcltPrcsMsrc, + usePurpose: el.ruseFcltUsg, + })); + + const detailsByCaseId: Record<string, SimilarCaseDetail> = {}; + similarCaseList.forEach((el, idx) => { + detailsByCaseId[String(idx)] = { + coliform: el.grnteWqGrssClfg, + residualCl: el.grnteWqCombResdch, + turbidity: el.grnteWqTrbt, + bod: el.grnteWqBod, + odor: el.grnteWqSmll, + color: el.grnteWqCrmty, + tn: el.grnteWqTn, + tp: el.grnteWqTp, + ph: el.grnteWqPh, + chloride: el.grnteWqClIonQnt, + ec: el.grnteWqCndctStat, + note: el.grnteWqRmrk, + + costTotal: Number(el.reqBizGrssCst), + costFacility: Number(el.reqBizReuseFcltCst), + costPipe: Number(el.reqBizPplCst), + costDesign: Number(el.reqBizDesignCst), + costSupervision: Number(el.reqBizSpvsCst), + costOther: Number(el.reqBizEtc), + + fundingTotal: Number(el.prcmPlanGrssCst), + fundingNational: Number(el.prcmPlanNlep), + fundingLocal: Number(el.prcmPlanLcep), + fundingSelf: Number(el.prcmPlanBrdnCst), + fundingRate: Number(el.prcmPlanEcqrRt), + }; + }); + + // 데이터 저장 + const saveInfo = async () => { + if (!validation(reqQty, '요구수량', ['req', 'numDot'])) return false; + if (!validation(usePurpose, '사용용도', ['req'])) return false; + if (!validation(hopePrice, '희망단가', ['req', 'numDot'])) return false; + /* if(!noDetect && !validation(coliform, '총대장균군수', ['req','numDot'])) return false; + if(!validation(residualCl, ['req', '결합잔류염소', 'numDot'])) return false; + if(!validation(turbidity, '탁도', ['req', 'numDot'])) return false; + if(!validation(bod, '생물화학적산소요구량', ['req', 'numDot'])) return false; + if(!validation(odor, '냄새', ['req'])) return false; */ + + // 필수는 아니지만 숫자로는 들어와야함. + /* if(!validation(tn, '총질소', ['numDot'])) return false; + if(!validation(residualCl, '결합잔류염소', ['numDot'])) return false; + if(!validation(tp, '총인', ['numDot'])) return false; + if(!validation(ph.min, 'ph', ['numDot'])) return false; + if(!validation(ph.max, 'ph', ['numDot'])) return false; + if(!validation(chloride, '염화물', ['numDot'])) return false; + if(!validation(ec, '전기전도도', ['numDot'])) return false; */ + + const userStr = sessionStorage.getItem("user"); + const user = userStr ? JSON.parse(userStr) : null; + + if (user) { + // 후보지 목록 (candsRef에 코드까지 보관됨) + const splyCndtList = candsRef.current + .filter((x) => x.name) + .map((x) => ({ + ruseUtztnFcltNm: x.name, + ruseUtztnFcltCd: x.code, + })); + + const payload: SdmInfoFormData = { + dmndTmng: new Date(reqDate), + dmdInfoId: user.dmdInfoId, + dmndQty: Number(reqQty), + wntUntprc: Number(hopePrice), + useUsgCd: usePurpose, + // 수질 + grssClfg: Number(coliform), + tn: Number(tn) || null, + combResdch: Number(residualCl) || null, + tph: Number(tp) || null, + trbt: Number(turbidity), + ph: ph.min || '' + ((ph.min || ph.max) ? '~' : '') + ph.max || '', + blgcOxygDmndQnt: Number(bod), + clIonQnt: Number(chloride) || null, + smll: odor, + cdtnv: Number(ec) || null, + crmty: Number(color) || null, + rmrk: remark, + splyCndtList, // 후보지 목록 추가 + } + const success = await insertSdmInfoData(payload); + if (success) { + router.push("/apply/status"); + } + } + } + + const searchCandidate = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const dmdInfoId = JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString(); + + const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; + const width = window.screen.availWidth; + const height = window.screen.availHeight; + const left = 0; + const top = 0; + const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`; + window.open(url, "candidatePopup", popupOptions); + }; + + const getCriterionCode = (criterion: string) => { + switch (criterion) { + case "supplyAmt": + return "S"; + case "money": + return "M"; + case "usePurpose": + return "U"; + default: + return ""; + } + }; + + const searchSimilarCase = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setOpenSimilar(true); + }; + + const initQualityForm = () => { + setColiform(""); + setNoDetect(false); + setTn(""); + setResidualCl(""); + setTp(""); + setTurbidity(""); + setPh({ min: "", max: "" }); + setBod(""); + setChloride(""); + setOdor("불쾌하지 않을 것"); + setEc(""); + setColor(""); + setRemark(""); + setFarmText("이하"); + } + + const fncSetOptionValue = (e: React.ChangeEvent<HTMLSelectElement>) => { + const value = e.target.value; + initQualityForm(); + + switch (value) { + // 청소화장실용도 + case '01': + setTp('0.2'); + setTurbidity('2'); + setPh({ min: '5.8', max: '8.5' }); + setBod('5'); + setColor('20'); + break; + // 세척살수용도 + case '02': + setTp('0.2'); + setTurbidity('2'); + setPh({ min: '5.8', max: '8.5' }); + setBod('5'); + break; + // 조경용도 + case '03': + setTp('0.2'); + setTurbidity('2'); + setPh({ min: '5.8', max: '8.5' }); + setBod('5'); + setChloride('250'); + break; + // 지하수 + case '05': + break; + // 농업용도 + case '06': + setTurbidity('5'); + setPh({ min: '5.8', max: '8.5' }); + setBod('8'); + setEc('2000'); + setFarmText("이하 (** 간접:2000,직접:700 )"); + break; + // 하천 등 유지보수 + case '07': + setColiform('1000'); + setTn('10'); + setTp('0.5'); + setPh({ min: '5.8', max: '8.5' }); + setBod('5'); + break; + // 공업 + case '08': + setPh({ min: '5.8', max: '8.5' }); + break; + default: + console.log('잘못된 선택입니다.'); + } + + setUsePurpose(value); + } + + return ( + <> + + <main className="bg-white"> + + {/* 서브 타이틀 + 브레드크럼 */} + <SubPageHeader + title="수요정보등록" + crumbs={[ + { label: "Home", href: "/reuse" }, + { label: "수요신청", href: "/reuse/apply" }, + { label: "수요정보등록" }, + ]} + /> + + {/* 컨텐츠 영역 */} + <div> + + {/* 기본정보 테이블 */} + <div className="border border-neutral-200 bg-white"> + <FormRow label="요구시기" required> + <DateInput value={reqDate} onChange={setReqDate} /> + </FormRow> + + <Divider /> + + <FormRow label="요구수량(m²/d)" required> + <TextInput value={reqQty} onChange={setReqQty} placeholder="" /> + </FormRow> + + <Divider /> + + <FormRow label="사용용도" required> + <select + value={usePurpose} + onChange={(e) => fncSetOptionValue(e)} + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option value="">선택하세요</option> + {useTypesData.map((item) => ( + <option key={item.val} value={item.val}> + {item.text} + </option> + ))} + </select> + </FormRow> + + <Divider /> + + <FormRow label="희망단가(원)" required> + <TextInput value={hopePrice} onChange={setHopePrice} /> + </FormRow> + </div> + + {/* 요구수질 섹션 타이틀 바 */} + <div className="grid grid-cols-12"> + <div className="col-span-12 lg:col-span-2 inline-flex justify-center items-center gap-3 px-6 py-6 text-center"> + <span className="text-lg font-semibold text-neutral-800">요구수질</span> + <button + type="button" + onClick={() => setOpenRef(true)} + className="rounded border-[2px] border-[#DE3412] px-2 py-3 text-[15px] font-medium text-[#4F4F4F] hover:bg-orange-50" + > + 수질기준 참고 + </button> + {/* 수질기준참고 모달 */} + <WaterQualityRefModal + open={openRef} + onClose={() => setOpenRef(false)} + imageSrc="/images/landing/water_quality_ref_1.png" + /> + </div> + </div> + + {/* 요구수질 테이블 */} + <div className="border border-neutral-200 bg-white"> + {/* 1행 */} + <QualityRow + leftLabel="총대장균군수(개/mg)" + + leftControl={ + <div className="flex items-center gap-4"> + <TextInput + value={coliform} + onChange={setColiform} + disabled={noDetect} + className={noDetect ? "bg-neutral-100 text-neutral-400" : ""} + /> + <label className="flex items-center gap-2 text-sm text-neutral-700"> + <input + type="checkbox" + checked={noDetect} + onChange={(e) => { + setNoDetect(e.target.checked); + if (e.target.checked) setColiform(""); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 불검출 + </label> + </div> + } + rightLabel="총질소(T-N)(mg/L)" + rightControl={ + <InlineSuffixInput value={tn} onChange={setTn} suffix="이하" /> + } + /> + <Divider /> + + {/* 2행 */} + <QualityRow + leftLabel="결합잔류염소(mg/L)" + /* leftRequired */ + leftControl={<TextInput value={residualCl} onChange={setResidualCl} />} + rightLabel="총인(T-P)(mg/L)" + rightControl={ + <InlineSuffixInput value={tp} onChange={setTp} suffix="이하" /> + } + /> + <Divider /> + + {/* 3행 */} + <QualityRow + leftLabel="탁도(NTU)" + /* leftRequired */ + leftControl={ + <InlineSuffixInput value={turbidity} onChange={setTurbidity} suffix="이하" /> + } + rightLabel="수소이온농도(pH)" + rightControl={ + <div className="flex items-center gap-2"> + <TextInput + value={ph.min} + onChange={(v) => setPh((p) => ({ ...p, min: v }))} + className="w-32" + /> + <span className="text-neutral-500">~</span> + <TextInput + value={ph.max} + onChange={(v) => setPh((p) => ({ ...p, max: v }))} + className="w-32" + /> + </div> + } + /> + <Divider /> + + {/* 4행 */} + <QualityRow + leftLabel={ + <> + 생물화학적산소요구량 + <br /> + (BOD)(mg/L) + </> + } + /* leftRequired */ + leftControl={<InlineSuffixInput value={bod} onChange={setBod} suffix="이하" />} + rightLabel="염화물(mgCL/L)" + rightControl={<InlineSuffixInput value={chloride} onChange={setChloride} suffix="이하" />} + /> + <Divider /> + + {/* 5행 */} + <QualityRow + leftLabel="냄새" + /* leftRequired */ + leftControl={ + <select + value={odor} + onChange={(e) => setOdor(e.target.value)} + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option>불쾌하지 않을 것</option> + <option>약간 있음</option> + <option>있음</option> + </select> + } + rightLabel="전기전도도(µs/cm)" + rightControl={<InlineSuffixInput value={ec} onChange={setEc} suffix={farmText} />} + /> + <Divider /> + + {/* 6행 */} + <QualityRow + leftLabel="색도(도)" + /* leftRequired */ + leftControl={<InlineSuffixInput value={color} onChange={setColor} suffix="이하" />} + rightLabel="비고" + rightControl={ + <textarea + value={remark} + onChange={(e) => setRemark(e.target.value)} + className="h-11 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm outline-none focus:border-neutral-500" + /> + } + /> + <Divider /> + + {/* 후보지 선택 */} + <div className="grid grid-cols-12"> + <LabelCell> + 공급처 후보지 선택 + <br /> + (필수아님) + </LabelCell> + + <div className="col-span-12 md:col-span-10 p-6"> + <div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:gap-4"> + <TextInput value={cand1} onChange={setCand1} className="max-w-[220px]" /> + <TextInput value={cand2} onChange={setCand2} className="max-w-[220px]" /> + <TextInput value={cand3} onChange={setCand3} className="max-w-[220px]" /> + + <div className="flex items-center gap-3 xl:ml-2"> + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-700" + onClick={(e) => { + searchCandidate(e); + + //splyList => 해당객체가 대민에서 선택한 후보처 리스트 + //setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트 + }}> + 후보지 조회 + </button> + + <button + type="button" + className="h-11 rounded-md bg-[#888] px-5 text-base text-white hover:bg-neutral-600" + onClick={(e) => { + // TODO: 유사사례 연결 + searchSimilarCase(e); + }} + > + 유사사례 + </button> + </div> + </div> + </div> + </div> + </div> + + {/* 하단 버튼 */} + <div className="flex justify-center py-12"> + <button + type="button" + className="h-11 rounded-md bg-[#0B4EA2] px-10 text-lg text-white hover:opacity-90" + onClick={() => { + // TODO: 저장 처리 + saveInfo(); + }} + > + 수요정보 등록 + </button> + </div> + </div> + </main> + + <SimilarCaseModal + open={openSimilar} + onClose={() => setOpenSimilar(false)} + rows={rows} // 실제 데이터 + detailsByCaseId={detailsByCaseId} + onSearch={async (criterion) => { + const code = getCriterionCode(criterion); + if (!code) return; + // 값은 상황에 맞게 (예시: usePurpose) + console.log(code+' req: '+reqQty+' purpose :'+usePurpose); + await getSimilarCaseList(code, code == 'S' ? reqQty : code == 'U' ? usePurpose : ''); + }} + /> + + <CandidateSiteModal + open={openCand} + onClose={() => setOpenCand(false)} + initialSelected={selectedCands} + onApply={(list) => { + setSelectedCands(list); + + // ✅ cand1~3 입력칸에 반영(시설명만 넣는 버전) + setCand1(list[0]?.name ?? ""); + setCand2(list[1]?.name ?? ""); + setCand3(list[2]?.name ?? ""); + + setOpenCand(false); + }} + /> + </> + ); +} + +/* ----------------------------- UI helpers ----------------------------- */ + +function Divider() { + return <div className="h-px w-full bg-neutral-200" />; +} + +function LabelCell({ + children, + required, +}: { + children: React.ReactNode; + required?: boolean; +}) { + return ( + <div className="col-span-12 md:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + {children} + {required ? <span className="text-red-500">*</span> : null} + </span> + </div> + ); +} + +function FormRow({ + label, + required, + children, +}: { + label: string; + required?: boolean; + children: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + <LabelCell required={required}>{label}</LabelCell> + <div className="col-span-12 md:col-span-10 p-6">{children}</div> + </div> + ); +} + +function QualityRow({ + leftLabel, + leftRequired, + leftControl, + rightLabel, + rightControl, +}: { + leftLabel: React.ReactNode; + leftRequired?: boolean; + leftControl: React.ReactNode; + rightLabel: React.ReactNode; + rightControl: React.ReactNode; +}) { + return ( + <div className="grid grid-cols-12"> + {/* 좌측 라벨 */} + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="inline-flex items-center gap-1"> + <span className="leading-6">{leftLabel}</span> + {leftRequired ? <span className="text-red-500">*</span> : null} + </span> + </div> + + {/* 좌측 입력 */} + <div className="col-span-12 lg:col-span-4 p-6">{leftControl}</div> + + {/* 우측 라벨 */} + <div className="col-span-12 lg:col-span-2 flex items-center justify-center text-center bg-emerald-50/70 px-6 py-6 text-lg font-semibold text-neutral-800"> + <span className="leading-6">{rightLabel}</span> + </div> + + {/* 우측 입력 */} + <div className="col-span-12 lg:col-span-4 p-6">{rightControl}</div> + </div> + ); +} + +function TextInput({ + value, + onChange, + placeholder, + disabled, + className, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +}) { + return ( + <input + value={value} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className={[ + "h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none", + "focus:border-neutral-500", + disabled ? "cursor-not-allowed" : "", + className ?? "", + ].join(" ")} + /> + ); +} + +function InlineSuffixInput({ + value, + onChange, + suffix, +}: { + value: string; + onChange: (v: string) => void; + suffix: string; +}) { + return ( + <div className="flex items-center gap-3"> + <TextInput value={value} onChange={onChange} className="max-w-[200px]" /> + <span className="text-sm text-neutral-600">{suffix}</span> + </div> + ); +} + +function DateInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + + const ref = useRef<HTMLInputElement | null>(null); + + const openPicker = () => { + const el = ref.current; + if (!el) return; + + // Chrome/Edge 등 지원 + if (typeof (el as any).showPicker === "function") { + (el as any).showPicker(); + return; + } + + // fallback (일부 브라우저) + el.focus(); + el.click(); + }; + return ( + <div className="relative w-full max-w-[200px]"> + <input + ref={ref} + type="date" + value={value} + onChange={(e) => onChange(e.target.value)} + className={[ + "h-11 w-full rounded-md border border-neutral-300 bg-white px-3 pr-10 text-sm outline-none focus:border-neutral-500", + // ✅ 기본 달력 아이콘 숨김 (Tailwind arbitrary selector) + "[&::-webkit-calendar-picker-indicator]:opacity-0", + "[&::-webkit-calendar-picker-indicator]:absolute", + "[&::-webkit-calendar-picker-indicator]:right-0", + "[&::-webkit-calendar-picker-indicator]:w-10", + "[&::-webkit-calendar-picker-indicator]:h-full", + "[&::-webkit-calendar-picker-indicator]:cursor-pointer", + ].join(" ")} + /> + + {/* ✅ 클릭 가능한 아이콘 버튼 */} + <button + type="button" + onClick={openPicker} + className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-neutral-500 hover:text-neutral-700" + aria-label="달력 열기" + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> + <path d="M7.14286 7.99104H16.7812M6.48512 2.5625V4.19126M17.3125 2.5625V4.19106M20.5 7.19106L20.5 18.5625C20.5 20.2194 19.1569 21.5625 17.5 21.5625H6.5C4.84315 21.5625 3.5 20.2194 3.5 18.5625V7.19106C3.5 5.53421 4.84315 4.19106 6.5 4.19106H17.5C19.1569 4.19106 20.5 5.53421 20.5 7.19106Z" stroke="#1A1A1A" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" /> + </svg> + </button> + </div> + ); +} diff --git a/apps/dm/app/(with-layout)/apply/status/page.tsx b/apps/dm/app/(with-layout)/apply/status/page.tsx new file mode 100644 index 000000000..dab5d46bb --- /dev/null +++ b/apps/dm/app/(with-layout)/apply/status/page.tsx @@ -0,0 +1,727 @@ +"use client"; + +import { useDmStore } from "@/app/store"; +import type { DemandCompBizType } from "@/app/store/sdmMng"; +import SubPageHeader from "@/components/SubPageHeader"; +import { useEffect, useMemo, useRef, useState } from "react"; + +type TopField = { label: string; key: string, value: string; required?: boolean }; +type PairField = { label: string; key: string, value: string }; +type SessionUser = { dmdInfoId?: string }; + +const formatDate = (value: string | Date | "" | undefined) => { + if (!value) return "-"; + const date = new Date(value); + if (Number.isNaN(date.getTime())) return String(value); + const yyyy = date.getFullYear(); + const mm = String(date.getMonth() + 1).padStart(2, "0"); + const dd = String(date.getDate()).padStart(2, "0"); + return `${yyyy}-${mm}-${dd}`; +}; + +const formatValue = (value: string | number | null | undefined) => { + if (value === null || value === undefined || value === "") return "-"; + return String(value); +}; + +const formatBelow = (value: string | number | null | undefined) => { + if (value === null || value === undefined || value === "") return "-"; + if (Number(value) === -1) return "불검출"; + return `${value} 이하`; +}; + +function StatusBadge({ v }: { v: string }) { + const cls = v === "신청" ? "text-red-600" : "text-lime-600"; + return <span className={["font-medium", cls].join(" ")}>{v || "-"}</span>; +} + +function TextInput({ + value, + onChange, + placeholder, + disabled, + className, +}: { + value: string; + onChange: (v: string) => void; + placeholder?: string; + disabled?: boolean; + className?: string; +}) { + return ( + <input + value={value} + onChange={(e) => onChange(e.target.value)} + placeholder={placeholder} + disabled={disabled} + className={[ + "h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none", + "focus:border-neutral-500", + disabled ? "cursor-not-allowed" : "", + className ?? "", + ].join(" ")} + /> + ); +} + +function DateInput({ + value, + onChange, +}: { + value: string; + onChange: (v: string) => void; +}) { + + const ref = useRef<HTMLInputElement | null>(null); + + const openPicker = () => { + const el = ref.current; + if (!el) return; + + // Chrome/Edge 등 지원 + if (typeof (el as any).showPicker === "function") { + (el as any).showPicker(); + return; + } + + // fallback (일부 브라우저) + el.focus(); + el.click(); + }; + return ( + <div className="relative w-full max-w-[200px]"> + <input + ref={ref} + type="date" + value={value} + onChange={(e) => onChange(e.target.value)} + className={[ + "h-11 w-full rounded-md border border-neutral-300 bg-white px-3 pr-10 text-sm outline-none focus:border-neutral-500", + // ✅ 기본 달력 아이콘 숨김 (Tailwind arbitrary selector) + "[&::-webkit-calendar-picker-indicator]:opacity-0", + "[&::-webkit-calendar-picker-indicator]:absolute", + "[&::-webkit-calendar-picker-indicator]:right-0", + "[&::-webkit-calendar-picker-indicator]:w-10", + "[&::-webkit-calendar-picker-indicator]:h-full", + "[&::-webkit-calendar-picker-indicator]:cursor-pointer", + ].join(" ")} + /> + + {/* ✅ 클릭 가능한 아이콘 버튼 */} + <button + type="button" + onClick={openPicker} + className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-neutral-500 hover:text-neutral-700" + aria-label="달력 열기" + > + <svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true"> + <path d="M7.14286 7.99104H16.7812M6.48512 2.5625V4.19126M17.3125 2.5625V4.19106M20.5 7.19106L20.5 18.5625C20.5 20.2194 19.1569 21.5625 17.5 21.5625H6.5C4.84315 21.5625 3.5 20.2194 3.5 18.5625V7.19106C3.5 5.53421 4.84315 4.19106 6.5 4.19106H17.5C19.1569 4.19106 20.5 5.53421 20.5 7.19106Z" stroke="#1A1A1A" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" /> + </svg> + </button> + </div> + ); +} + +export default function DemandStatusPage() { + const { getSdmInfoStatusList, updateSdmInfoData, Sdm, Common } = useDmStore((state) => state); + const { successLogin, sdmInfoStatusList } = Sdm; + const { selectSidoData, selectSigunData } = Common; + + const detailRef = useRef<HTMLDivElement | null>(null); + + const [isLoginStorageInfo, setIsLoginStorageInfo] = useState(false); + const [selectedDmdInfoSn, setSelectedDmdInfoSn] = useState<number | "" | null>(null); + const [transformedSdmInfoStatusList, setTransformedSdmInfoStatusList] = useState<DemandCompBizType[]>([]); + + // 수정 유무 + const [ isEditMode, setIsEditMode ] = useState(false); + // 수정용 (UI에서 바뀌는 값) + const [editData, setEditData] = useState<any>(null); + // 원본 백업 (취소용) + const [originData, setOriginData] = useState<any>(null); + // 사용용도 + const useTypesData = useDmStore((state) => state.Sdm.useTypesData); + const getUseTypesData = useDmStore((state) => state.getUseTypesData); + + useEffect(() => { + const user = sessionStorage.getItem("user"); + if (user) { + setIsLoginStorageInfo(true); + } + }, []); + + useEffect(() => { + getUseTypesData(); + }, [getUseTypesData]); + + useEffect(() => { + if (!successLogin && !isLoginStorageInfo) return; + + const user = sessionStorage.getItem("user"); + if (!user) return; + + try { + const userData = JSON.parse(user) as SessionUser; + const dmdInfoId = userData.dmdInfoId?.trim(); + if (dmdInfoId) { + getSdmInfoStatusList(dmdInfoId); + } + } catch (error) { + console.error("Error parsing user data:", error); + } + }, [successLogin, isLoginStorageInfo, getSdmInfoStatusList]); + + useEffect(() => { + const mappedList = sdmInfoStatusList.map((item) => ({ + ...item, + ctpvCd: selectSidoData.find((sido) => sido.val === item.ctpvCd?.trim())?.text || item.ctpvCd, + sggCd: selectSigunData.find((sigun) => sigun.val === item.sggCd?.trim())?.text || item.sggCd, + })); + + setTransformedSdmInfoStatusList(mappedList); + }, [sdmInfoStatusList, selectSidoData, selectSigunData]); + + useEffect(() => { + const first = transformedSdmInfoStatusList[0]; + if (!first) { + setSelectedDmdInfoSn(null); + return; + } + + if (selectedDmdInfoSn == null) { + setSelectedDmdInfoSn(first.dmdInfoSn); + return; + } + + const exists = transformedSdmInfoStatusList.some( + (item) => item.dmdInfoSn === selectedDmdInfoSn, + ); + if (!exists) { + setSelectedDmdInfoSn(first.dmdInfoSn); + } + }, [transformedSdmInfoStatusList, selectedDmdInfoSn]); + + const selectedSdmInfoStatus = useMemo(() => { + if (!transformedSdmInfoStatusList.length) return null; + return ( + transformedSdmInfoStatusList.find((item) => item.dmdInfoSn === selectedDmdInfoSn) ?? + transformedSdmInfoStatusList[0] + ); + }, [transformedSdmInfoStatusList, selectedDmdInfoSn]); + + const topFields: TopField[] = useMemo(() => { + if (!selectedSdmInfoStatus) return []; + + return [ + { label: "요구시기", key: "dmndTmng", value: formatDate(selectedSdmInfoStatus.dmndTmng), required: true }, + { label: "요구수량(㎥/d)", key: "dmndQty", value: formatValue(selectedSdmInfoStatus.dmndQty) }, + { label: "사용용도", key: "useUsgCd", value: formatValue(selectedSdmInfoStatus.useUsgCd) }, + { label: "희망단가(원/㎥)", key: "wntUntprc", value: formatValue(selectedSdmInfoStatus.wntUntprc) }, + ]; + }, [selectedSdmInfoStatus]); + + const waterFields: PairField[] = useMemo(() => { + if (!selectedSdmInfoStatus) return []; + + return [ + { label: "총대장균군수(개/100mL)", key: "grssClfg", value: formatBelow(selectedSdmInfoStatus.grssClfg) }, + { label: "결합잔류염소(mg/L)", key:"combResdch", value: formatBelow(selectedSdmInfoStatus.combResdch) }, + { label: "탁도(NTU)", key:"trbt", value: formatBelow(selectedSdmInfoStatus.trbt) }, + + { label: "생물학적산소요구량(BOD)(mg/L)", key:"blgcOxygDmndQnt", value: formatBelow(selectedSdmInfoStatus.blgcOxygDmndQnt) }, + { label: "냄새", key:"smll", value: formatValue(selectedSdmInfoStatus.smll) }, + { label: "색도(도)", key:"crmty", value: formatBelow(selectedSdmInfoStatus.crmty) }, + + { label: "총질소(T-N)(mg/L)", key:"tn", value: formatBelow(selectedSdmInfoStatus.tn) }, + { label: "총인(T-P)(mg/L)", key:"tph", value: formatBelow(selectedSdmInfoStatus.tph) }, + { label: "수소이온농도(pH)", key:"ph", value: formatValue(selectedSdmInfoStatus.ph) }, + + { label: "염화물(mgCL/L)", key:"clIonQnt", value: formatBelow(selectedSdmInfoStatus.clIonQnt) }, + { label: "전기전도도(μS/cm)", key:"cdtnv", value: formatBelow(selectedSdmInfoStatus.cdtnv) }, + { label: "비고", key:"rmrk", value: formatValue(selectedSdmInfoStatus.rmrk) }, + ]; + }, [selectedSdmInfoStatus]); + + const candidateNames = useMemo(() => { + const list = isEditMode + ? editData?.splyCndtList ?? [] + : selectedSdmInfoStatus?.splyCndtList ?? []; + return [ + list[0]?.ruseUtztnFcltNm ?? "", + list[1]?.ruseUtztnFcltNm ?? "", + list[2]?.ruseUtztnFcltNm ?? "", + ]; + }, [isEditMode, editData, selectedSdmInfoStatus]); + + const onSelectSdmInfoStatus = (dmdInfoSn: number | "") => { + if(isEditMode){ + if(confirm('저장되지 않은 데이터는 사라집니다.\n진행하시겠습니까?')){ + setIsEditMode(false); + } + } + setSelectedDmdInfoSn(dmdInfoSn); + requestAnimationFrame(() => { + detailRef.current?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + }; + + // ph 전후로 나눔 + const getRange = (val: string) => { + const a = val.split('~')[0]||''; const b = val.split('~')[1]||''; + return { a, b }; + }; + + const searchCandidate = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + const dmdInfoId = JSON.parse( + sessionStorage.getItem("user") || "{}" + )?.dmdInfoId?.toString(); + + const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; + const width = window.screen.availWidth; + const height = window.screen.availHeight; + const left = 0; + const top = 0; + const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`; + window.open(url, "candidatePopup", popupOptions); + }; + + const saveInfo = () => { + if(confirm('수정된 데이터를 저장하시겠습니까?')){ + const payload = { + ...editData, + splyCndtList : editData.splyCndtList ?? [] + }; + + updateSdmInfoData(payload).then((res) => { + if (res) { + const user = JSON.parse(sessionStorage.getItem("user") || "{}"); + if (user?.dmdInfoId) { + getSdmInfoStatusList(user.dmdInfoId); + } + setIsEditMode(false); + } + }); + } + } + + useEffect(() => { + const handler = (event: MessageEvent) => { + if (!event.data) return; + + if (event.data.type === "SET_SUBS_INFO") { + const list = event.data.payload || []; + setEditData((prev) => ({ + ...prev, + splyCndtList: list, + })); + } + }; + + window.addEventListener("message", handler); + + return () => { + window.removeEventListener("message", handler); + }; + }, []); + + return ( + + <main className="bg-white"> + <SubPageHeader + title="수요신청현황" + crumbs={[ + { label: "Home", href: "/main" }, + { label: "수요신청", href: "/apply/status" }, + { label: "수요신청현황" }, + ]} + hideFooterLine + hideGuideText + /> + + <div> + <section className="mt-6"> + <div className="text-2xl font-bold text-slate-700">수요정보 신청현황</div> + + <div className="mt-3 border border-neutral-200"> + <table className="w-full table-fixed"> + <thead className="bg-[#E7F4E2]"> + <tr className="text-sm text-slate-700"> + <th className="w-[60px] px-3 py-3 text-center">NO.</th> + <th className="w-[120px] px-3 py-3 text-center">시도</th> + <th className="w-[120px] px-3 py-3 text-center">시군구</th> + <th className="w-[180px] px-3 py-3 text-center">회사명</th> + <th className="px-3 py-3 text-center">공장주소</th> + <th className="w-[140px] px-3 py-3 text-center">전화번호</th> + <th className="w-[120px] px-3 py-3 text-center">요구시기</th> + <th className="w-[120px] px-3 py-3 text-center">등록일자</th> + <th className="w-[120px] px-3 py-3 text-center">수정일자</th> + <th className="w-[110px] px-3 py-3 text-center">진행상태</th> + </tr> + </thead> + + <tbody className="bg-white text-base text-slate-700"> + {transformedSdmInfoStatusList.length === 0 ? ( + <tr> + <td colSpan={8} className="px-3 py-10 text-center text-slate-400"> + 조회된 신청내역이 없습니다. + </td> + </tr> + ) : ( + transformedSdmInfoStatusList.map((sdmInfoStatus, index) => { + const active = sdmInfoStatus.dmdInfoSn === selectedSdmInfoStatus?.dmdInfoSn; + return ( + <tr + key={`${sdmInfoStatus.dmdInfoSn}-${index}`} + role="row" + tabIndex={0} + onClick={() => onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn)} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + onSelectSdmInfoStatus(sdmInfoStatus.dmdInfoSn); + } + }} + className={[ + "border-t border-neutral-200 outline-none", + "cursor-pointer", + active ? "bg-slate-50" : "hover:bg-slate-50", + "focus-visible:ring-2 focus-visible:ring-slate-300", + ].join(" ")} + aria-selected={active} + > + <td className="px-3 py-3 text-center">{sdmInfoStatus.index || index + 1}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.ctpvCd)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.sggCd)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.dmdCoNm)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.daddr)}</td> + <td className="px-3 py-3 text-center">{formatValue(sdmInfoStatus.telno)}</td> + <td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.dmndTmng)}</td> + <td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.regDt)}</td> + <td className="px-3 py-3 text-center">{formatDate(sdmInfoStatus.mdfcnDt)}</td> + <td className="px-3 py-3 text-center"> + <StatusBadge v={formatValue(sdmInfoStatus.status)} /> + </td> + </tr> + ); + }) + )} + </tbody> + </table> + </div> + </section> + + <section className="mt-10 pb-16" ref={detailRef}> + <div className="flex justify-between items-center"> + <div className="text-2xl font-bold text-slate-700">수요신청 상세현황</div> + { + !isEditMode ? ( + <button + type="button" + className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + onClick={() => { + setOriginData(selectedSdmInfoStatus); + setEditData({...selectedSdmInfoStatus}); + setIsEditMode(true)} + } + > + 수정 + </button> + ) : ( + <div className="flex gap-2"> + <button + type="button" + className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + onClick={() => { + saveInfo(); + }} + > + 저장 + </button> + <button + type="button" + className="h-8 rounded-md bg-[#888] px-4 text-base text-white hover:bg-neutral-700" + onClick={() => { + setEditData(originData); + setIsEditMode(false) + }} + > + 취소 + </button> + </div> + ) + } + </div> + + {!selectedSdmInfoStatus ? ( + <div className="mt-3 border border-neutral-200 px-6 py-10 text-center text-sm text-slate-400"> + 상세 정보를 표시할 신청건이 없습니다. + </div> + ) : ( + <div className="mt-3 border border-neutral-200"> + <div className="bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수량 + </div> + + <div className="grid grid-cols-12 text-sm"> + {topFields.map((field) => ( + <div + key={field.label} + className="col-span-12 md:col-span-6 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-4 bg-sky-50 px-4 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {field.label} + {field.required ? <span className="ml-1 text-red-500">*</span> : null} + </div> + { + !isEditMode ? + <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center"> + {field.key==='useUsgCd'? useTypesData.filter(item=>item.val===field.value)[0]?.text: field.value} + </div> + : <div className="col-span-8 px-4 py-4 flex items-center justify-center text-center"> + {field.key ==='dmndTmng'? + <DateInput value={editData?.[field.key] ?? ""} onChange={(v) => + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + } /> + : field.key ==='useUsgCd'? + <select + value={editData?.[field.key] ?? ""} + onChange={(e) => + setEditData((prev) => ({ + ...prev, + [field.key]: e.target.value, + })) + } + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" + > + <option value="">선택하세요</option> + {useTypesData.map((item) => ( + <option key={item.val} value={item.val}> + {item.text} + </option> + ))} + </select> + + : <TextInput value={editData?.[field.key] ?? ""} onChange={(v) => + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + }></TextInput> + } + </div> + } + </div> + ))} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 요구수질 + </div> + + {waterFields.map((field) => { + const { a, b } = editData?.ph ? getRange(editData.ph) : { a: "", b: "" }; + return ( + <div + key={field.label} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + {field.label} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + { + !isEditMode ? field.value + : + field.key === "grssClfg" ? ( + <div className="flex items-center gap-4"> + <TextInput + value={ + editData?.grssClfg === -1 + ? "" + : editData?.grssClfg ?? "" + } + onChange={(v) => + setEditData((prev) => ({ + ...prev, + grssClfg: v, + })) + } + disabled={editData?.grssClfg === -1} + className={ + editData?.grssClfg === -1 + ? "bg-neutral-100 text-neutral-400" + : "" + } + /> + <label className="flex items-center gap-2 w-3/4 text-sm text-neutral-700"> + <input + type="checkbox" + checked={editData?.grssClfg === -1} + onChange={(e) => { + const checked = e.target.checked; + + setEditData((prev) => ({ + ...prev, + grssClfg: checked ? -1 : "", + })); + }} + className="h-4 w-4 rounded border-neutral-300" + /> + 불검출 + </label> + </div> + ) : field.key === 'smll' ? ( + <select + value={ + editData?.smll === -1 + ? "" + : editData?.smll ?? "" + } + onChange={(v) => + setEditData((prev) => ({ + ...prev, + grssClfg: v, + })) + } + className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500"> + <option>불쾌하지 않을 것</option> + <option>약간 있음</option> + <option>있음</option> + </select> + ) : field.key === 'ph' ? ( + <> + <TextInput + value={a} + onChange={(v) => + setEditData((prev) => ({ + ...prev, + [field.key]: `${v}~${b}`, + })) + } + /> + ~ + <TextInput + value={b} + onChange={(v) => + setEditData((prev) => ({ + ...prev, + [field.key]: `${a}~${v}`, + })) + } + /> + </> + ) : ( + <> + <TextInput + value={editData?.[field.key] ?? ""} + onChange={(v) => + setEditData((prev) => ({ + ...prev, + [field.key]: v, + })) + } + /> + { + field.key==='tn'||field.key === 'tph'||field.key === 'trbt'||field.key==='blgcOxygDmndQnt'||field.key==='clIonQnt'||field.key==='cdtnv'|| field.key=='crmty' + ?<>이하</>:<></> + } + </> + ) + } + </div> + </div> + )})} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-b border-neutral-200"> + 공급처 후보지 + { + isEditMode && + <button + type="button" + className="h-8 w-25 ml-12 rounded-md bg-[#888] px-2 text-base text-white hover:bg-neutral-700" + onClick={(e) => { + searchCandidate(e); + + //splyList => 해당객체가 대민에서 선택한 후보처 리스트 + //setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트 + }}> + 후보지 조회 + </button> + } + + </div> + + {candidateNames.map((candidateName, index) => ( + isEditMode ? ( + <div + key={`candidate-${index}`} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 후보지{index + 1} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + <TextInput + value={candidateName} + disabled={true} + onChange={(v) => { + setEditData((prev) => { + const newList = [...(prev.splyCndtList || [])]; + newList[index] = { + ...newList[index], + ruseUtztnFcltNm: v, + }; + return { + ...prev, + splyCndtList: newList, + }; + }); + }} + /> + </div> + </div> + ) : + <div + key={`candidate-${index}`} + className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200" + > + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 후보지{index + 1} + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {candidateName} + </div> + </div> + ))} + + <div className="col-span-12 bg-sky-50 px-4 py-3 text-sm font-semibold text-slate-700 border-t border-b border-neutral-200"> + 수요신청 진행상태 + </div> + + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 진행상태 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {formatValue(selectedSdmInfoStatus.status)} + </div> + </div> + <div className="col-span-12 md:col-span-4 grid grid-cols-12 border-b md:border-b-0 border-neutral-200"> + <div className="col-span-5 bg-sky-50 px-3 py-4 flex items-center justify-center text-center font-semibold text-slate-700"> + 공급처매칭결과 + </div> + <div className="col-span-7 px-3 py-4 flex items-center justify-center text-center"> + {formatValue(selectedSdmInfoStatus.ruseUtztnFcltNm)} + </div> + </div> + </div> + </div> + )} + </section> + </div> + </main> + ); +} diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/australia/page.tsx b/apps/dm/app/(with-layout)/example/globalCase/australia/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/globalCase/australia/page.tsx rename to apps/dm/app/(with-layout)/example/globalCase/australia/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/eu/page.tsx b/apps/dm/app/(with-layout)/example/globalCase/eu/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/globalCase/eu/page.tsx rename to apps/dm/app/(with-layout)/example/globalCase/eu/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/overview/page.tsx b/apps/dm/app/(with-layout)/example/globalCase/overview/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/globalCase/overview/page.tsx rename to apps/dm/app/(with-layout)/example/globalCase/overview/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/singapore/page.tsx b/apps/dm/app/(with-layout)/example/globalCase/singapore/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/globalCase/singapore/page.tsx rename to apps/dm/app/(with-layout)/example/globalCase/singapore/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/globalCase/taiwan/page.tsx b/apps/dm/app/(with-layout)/example/globalCase/taiwan/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/globalCase/taiwan/page.tsx rename to apps/dm/app/(with-layout)/example/globalCase/taiwan/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/layout.tsx b/apps/dm/app/(with-layout)/example/layout.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/layout.tsx rename to apps/dm/app/(with-layout)/example/layout.tsx diff --git a/apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx b/apps/dm/app/(with-layout)/example/rain/suwon/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/rain/suwon/page.tsx rename to apps/dm/app/(with-layout)/example/rain/suwon/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx b/apps/dm/app/(with-layout)/example/sewage/bolyeong/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/bolyeong/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/bolyeong/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx b/apps/dm/app/(with-layout)/example/sewage/cheongju/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/cheongju/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/cheongju/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx b/apps/dm/app/(with-layout)/example/sewage/guri/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/guri/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/guri/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx b/apps/dm/app/(with-layout)/example/sewage/hwanggu/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/hwanggu/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/hwanggu/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx b/apps/dm/app/(with-layout)/example/sewage/paju/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/paju/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/paju/page.tsx diff --git a/apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx b/apps/dm/app/(with-layout)/example/sewage/yeosu/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/example/sewage/yeosu/page.tsx rename to apps/dm/app/(with-layout)/example/sewage/yeosu/page.tsx diff --git a/apps/dm_new/app/(with-layout)/intro/greywater/page.tsx b/apps/dm/app/(with-layout)/intro/greywater/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/intro/greywater/page.tsx rename to apps/dm/app/(with-layout)/intro/greywater/page.tsx diff --git a/apps/dm_new/app/(with-layout)/intro/law/page.tsx b/apps/dm/app/(with-layout)/intro/law/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/intro/law/page.tsx rename to apps/dm/app/(with-layout)/intro/law/page.tsx diff --git a/apps/dm_new/app/(with-layout)/intro/layout.tsx b/apps/dm/app/(with-layout)/intro/layout.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/intro/layout.tsx rename to apps/dm/app/(with-layout)/intro/layout.tsx diff --git a/apps/dm_new/app/(with-layout)/intro/rain/page.tsx b/apps/dm/app/(with-layout)/intro/rain/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/intro/rain/page.tsx rename to apps/dm/app/(with-layout)/intro/rain/page.tsx diff --git a/apps/dm_new/app/(with-layout)/intro/sewage/page.tsx b/apps/dm/app/(with-layout)/intro/sewage/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/intro/sewage/page.tsx rename to apps/dm/app/(with-layout)/intro/sewage/page.tsx diff --git a/apps/dm_new/app/(with-layout)/layout.tsx b/apps/dm/app/(with-layout)/layout.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/layout.tsx rename to apps/dm/app/(with-layout)/layout.tsx diff --git a/apps/dm_new/app/(with-layout)/status/greywater/page.tsx b/apps/dm/app/(with-layout)/status/greywater/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/status/greywater/page.tsx rename to apps/dm/app/(with-layout)/status/greywater/page.tsx diff --git a/apps/dm_new/app/(with-layout)/status/layout.tsx b/apps/dm/app/(with-layout)/status/layout.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/status/layout.tsx rename to apps/dm/app/(with-layout)/status/layout.tsx diff --git a/apps/dm_new/app/(with-layout)/status/rain/page.tsx b/apps/dm/app/(with-layout)/status/rain/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/status/rain/page.tsx rename to apps/dm/app/(with-layout)/status/rain/page.tsx diff --git a/apps/dm_new/app/(with-layout)/status/sewage/page.tsx b/apps/dm/app/(with-layout)/status/sewage/page.tsx similarity index 100% rename from apps/dm_new/app/(with-layout)/status/sewage/page.tsx rename to apps/dm/app/(with-layout)/status/sewage/page.tsx diff --git a/apps/dm_new/app/api/postcode/route.ts b/apps/dm/app/api/postcode/route.ts similarity index 100% rename from apps/dm_new/app/api/postcode/route.ts rename to apps/dm/app/api/postcode/route.ts diff --git a/apps/dm/app/base.tsx b/apps/dm/app/base.tsx deleted file mode 100644 index bae02acb2..000000000 --- a/apps/dm/app/base.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import { BaseLayout } from "@common/components"; -import type { MenuItem } from "./store/common"; -import { useDmStore } from "./store"; -import { usePathname, useRouter } from "next/navigation"; - -export default function Base({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - const { getMenuAuth } = useDmStore((state) => state); - - const router = useRouter(); - const pathname = usePathname(); - - const handleClick = (id: MenuItem) => { - getMenuAuth(id.menuCd); - router.push(id.urlPath); - }; - - return ( - <BaseLayout currentPath={pathname ?? ""} handleMenuClick={handleClick}> - {children} - </BaseLayout> - ); -} diff --git a/apps/dm_new/app/globals.css b/apps/dm/app/commonOrigin.css similarity index 93% rename from apps/dm_new/app/globals.css rename to apps/dm/app/commonOrigin.css index 2fee793a1..fdc8d0eb8 100644 --- a/apps/dm_new/app/globals.css +++ b/apps/dm/app/commonOrigin.css @@ -1,5 +1,4 @@ -/* @import "../../../packages/common/components/src/base.css"; */ -/* @import "../../../packages/common/components/src/base.css"; */ +@import "../../../packages/common/components/src/base.css"; @tailwind base; @tailwind components; @@ -80,4 +79,4 @@ body { font-family: "NotoSansKR", sans-serif; -} +} \ No newline at end of file diff --git a/apps/dm/app/globals.css b/apps/dm/app/globals.css index f30aa3031..2fee793a1 100644 --- a/apps/dm/app/globals.css +++ b/apps/dm/app/globals.css @@ -1,4 +1,5 @@ -@import "../../../packages/common/components/src/base.css"; +/* @import "../../../packages/common/components/src/base.css"; */ +/* @import "../../../packages/common/components/src/base.css"; */ @tailwind base; @tailwind components; @@ -68,3 +69,15 @@ @apply bg-background text-foreground; } } + +@font-face { + font-family: "NotoSansKR"; + src: url("/fonts/NotoSansKR-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +body { + font-family: "NotoSansKR", sans-serif; +} diff --git a/apps/dm/app/layout.tsx b/apps/dm/app/layout.tsx index 7224c9dc2..0e45865e7 100644 --- a/apps/dm/app/layout.tsx +++ b/apps/dm/app/layout.tsx @@ -15,7 +15,11 @@ export default function RootLayout({ <head> <title>물재이용 대민 - {children} + +
+ {children} +
+ ); } diff --git a/apps/dm_new/app/main/SideIndicator.tsx b/apps/dm/app/main/SideIndicator.tsx similarity index 100% rename from apps/dm_new/app/main/SideIndicator.tsx rename to apps/dm/app/main/SideIndicator.tsx diff --git a/apps/dm_new/app/main/_hooks/useScrollSpy.ts b/apps/dm/app/main/_hooks/useScrollSpy.ts similarity index 100% rename from apps/dm_new/app/main/_hooks/useScrollSpy.ts rename to apps/dm/app/main/_hooks/useScrollSpy.ts diff --git a/apps/dm_new/app/main/_sections/Cases.tsx b/apps/dm/app/main/_sections/Cases.tsx similarity index 100% rename from apps/dm_new/app/main/_sections/Cases.tsx rename to apps/dm/app/main/_sections/Cases.tsx diff --git a/apps/dm_new/app/main/_sections/Hero.tsx b/apps/dm/app/main/_sections/Hero.tsx similarity index 100% rename from apps/dm_new/app/main/_sections/Hero.tsx rename to apps/dm/app/main/_sections/Hero.tsx diff --git a/apps/dm_new/app/main/_sections/IntroCards.tsx b/apps/dm/app/main/_sections/IntroCards.tsx similarity index 100% rename from apps/dm_new/app/main/_sections/IntroCards.tsx rename to apps/dm/app/main/_sections/IntroCards.tsx diff --git a/apps/dm_new/app/main/_sections/ProcessFlow.tsx b/apps/dm/app/main/_sections/ProcessFlow.tsx similarity index 100% rename from apps/dm_new/app/main/_sections/ProcessFlow.tsx rename to apps/dm/app/main/_sections/ProcessFlow.tsx diff --git a/apps/dm_new/app/main/layout.tsx b/apps/dm/app/main/layout.tsx similarity index 100% rename from apps/dm_new/app/main/layout.tsx rename to apps/dm/app/main/layout.tsx diff --git a/apps/dm_new/app/main/page.tsx b/apps/dm/app/main/page.tsx similarity index 100% rename from apps/dm_new/app/main/page.tsx rename to apps/dm/app/main/page.tsx diff --git a/apps/dm/app/page.tsx b/apps/dm/app/page.tsx deleted file mode 100644 index fdb323086..000000000 --- a/apps/dm/app/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client" - -import Link from 'next/link'; -import { Button } from '@common/components/ui'; - -export default function Home() { - return ( - <> -
- - - - - - -
-
- -
- - ); -} diff --git a/apps/dm_new/app/reuse.module.css b/apps/dm/app/reuse.module.css similarity index 100% rename from apps/dm_new/app/reuse.module.css rename to apps/dm/app/reuse.module.css diff --git a/apps/dm/app/store/common/index.tsx b/apps/dm/app/store/common/index.tsx index 6721d2620..e875c2eca 100644 --- a/apps/dm/app/store/common/index.tsx +++ b/apps/dm/app/store/common/index.tsx @@ -327,12 +327,95 @@ const fetchEnvPartData = async (): Promise => { } }; +function sortMenus(items: MenuItem[]): MenuItem[] { + return [...items] + .sort((a, b) => a.sortSeq - b.sortSeq) + .map((item) => ({ + ...item, + children: item.children ? sortMenus(item.children) : [], + })); +} + +function isVisible(item: MenuItem): boolean { + return item.useYn !== "N" && item.otptYn !== "N"; +} + +function normalizeMenus(items: MenuItem[]): MenuItem[] { + return sortMenus(items) + .filter(isVisible) + .map((item) => ({ + ...item, + children: item.children ? normalizeMenus(item.children) : [], + })); +} + +export const REUSE_SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "DM"; +const anonymousMenuCache = new Map(); +const anonymousMenuInFlight = new Map>(); + +export async function fetchAnonymousMenu(siteId = REUSE_SITE_ID): Promise { + const cached = anonymousMenuCache.get(siteId); + if (cached) return cached; + + const inFlight = anonymousMenuInFlight.get(siteId); + if (inFlight) return inFlight; + + const request = (async () => { + const response = await fetch(`/cms-service/api/v1/menu/anonymous/user/${siteId}`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + cache: "no-store", + }); + + if (!response.ok) { + throw new Error(`Failed to load menu: ${response.status}`); + } + + const data = (await response.json()) as MenuItem[]; + const normalized = Array.isArray(data) ? normalizeMenus(data) : []; + anonymousMenuCache.set(siteId, normalized); + return normalized; + })(); + + anonymousMenuInFlight.set(siteId, request); + try { + return await request; + } finally { + anonymousMenuInFlight.delete(siteId); + } +} + +function isPathMatched(pathname: string, urlPath: string) { + if (!urlPath) return false; + return pathname === urlPath || pathname.startsWith(`${urlPath}/`); +} + +function hasMatchedPathInTree(menu: MenuItem, pathname: string): boolean { + if (isPathMatched(pathname, menu.urlPath)) return true; + return (menu.children ?? []).some((child) => hasMatchedPathInTree(child, pathname)); +} + +export function findActiveTopMenu(menus: MenuItem[], pathname: string): MenuItem | null { + const level1Menus = menus.filter((menu) => menu.levelNo === 1 || menu.parentId === null); + const matched = level1Menus.filter((menu) => hasMatchedPathInTree(menu, pathname)); + + return matched[0] ?? level1Menus[0] ?? null; +} + +export function hasAnyChild(item: MenuItem): boolean { + return Array.isArray(item.children) && item.children.length > 0; +} + +export function isMenuActive(pathname: string, menu: MenuItem): boolean { + return isPathMatched(pathname, menu.urlPath); +} /**==================================== * Store - expaort ====================================*/ export type CommonAction = { initializeCommonData: () => Promise; getMenuAuth: (menuCd:number) => Promise; + fetchAnonymousMenu: (siteId?: string) => Promise; } export type CommonType = { @@ -413,7 +496,7 @@ export const CommonReducer:(set:any)=>CommonAction=(set: any) => { } }) )}, + fetchAnonymousMenu: async (siteId = REUSE_SITE_ID) => fetchAnonymousMenu(siteId) } } - export type CommonStore = CommonType & CommonAction; diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index 2e52ac13d..1f4bfa572 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -1,4 +1,4 @@ -import { fetchType, getFetch, postFetch } from "@/components/ui/api"; +import { fetchType, getFetch, postFetch, getFetchWithCount } from "@/components/ui/api"; import type { SelectDataType } from "@common/business_components/ui"; import type { TableUpperProps } from "../common"; import toast from "react-hot-toast"; @@ -10,34 +10,27 @@ import { z } from "zod"; =====================================*/ export const sdmInfoFormSchema = z.object({ - sdmNm: z.string().min(1, "수요처를 선택해주세요"), - demandYmd: z.date().optional(), - demandQnty: z - .string() - .min(1, "요구수량을 입력해주세요") - .regex(/^[0-9]+$/, "요구수량은 숫자만 입력 가능합니다"), - usePurpose: z.string().optional(), - money: z.string().regex(/^[0-9]+$/, "희망단가는 숫자만 입력 가능합니다"), - /**요구수질 */ - reqWaterQlty: z.string().optional(), + dmdInfoId: z.string().optional(), + dmndTmng: z.date().optional(), + dmndQty: z.number().min(1, "요구수량을 입력해주세요"), + wntUntprc: z.number().min(1, "희망단가를 입력해주세요"), + useUsgCd: z.string().min(1, "사용용도를 선택해주세요"), + /** 수질 */ grssClfg: z.number().nullish(), tn: z.number().nullish(), combResdch: z.number().nullish(), tph: z.number().nullish(), trbt: z.number().nullish(), ph: z.string().nullish(), - bod: z.number().nullish(), - /**컬럼확인필요 */ + blgcOxygDmndQnt: z.number().nullish(), clIonQnt: z.number().nullish(), smll: z.string(), cdtnv: z.number().nullish(), crmty: z.number().nullish(), + rmrk: z.string().optional(), - /* telno: z.string() - .min(8, "전화번호는 8자 이상 입력해주세요") - .max(12, "전화번호는 12자 이내로 입력해주세요") - .regex(/^[0-9-]+$/, "전화번호는 숫자와 하이픈(-)만 입력 가능합니다"), */ - candidateTitle: z + + splyCndtList: z .array( z.object({ ruseUtztnFcltNm: z.string(), @@ -45,58 +38,6 @@ export const sdmInfoFormSchema = z.object({ }) ) .optional(), -}).superRefine((data, ctx) => { - const disabledFields = disableRules[data.usePurpose ?? ""] || []; - // 모든 수질 관련 필드 - // 총대장균군수는 null 값을 불검출로 표시하기때문에 해당 검사에서 제외함. - const allFields: (keyof typeof data)[] = [ - "tn", "combResdch", "tph", - "trbt", "ph", "bod", "clIonQnt", - "smll", "cdtnv", "crmty", - ]; - - const allFieldsName: Record = { - tn: "총질소(T-N)(mg/L)", - combResdch: "결합잔류염소(mg/L)", - tph: "총인(T-P)(mg/L)", - trbt: "탁도(NTU)", - ph: "수소이온농도(pH)", - bod: "생물학적 산소요구량(BOD)(mg/L)", - clIonQnt: "염화물(mgCL/L)", - smll: "냄새", - cdtnv: "전기전도도(㎲/cm)", - crmty: "색도(도)", - }; - allFields.forEach((field) => { - const isDisabled = disabledFields.includes(field); - if (!isDisabled) { - // disabled가 아닌 필드는 반드시 값이 있어야 함 - const value = data[field]; - if (typeof value === "string") { - if (value.trim().length === 0) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: [field], - message: `${allFieldsName[field]} 을 입력해주세요.`, - }); - } - } else if (typeof value === "number") { - if (value <= 0 || Number.isNaN(value)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: [field], - message: `${allFieldsName[field]} 을 입력해주세요.`, - }); - } - } else if (value == null) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: [field], - message: `${allFieldsName[field]} 을 입력해주세요.`, - }); - } - } - }); }); export type SdmInfoFormData = z.infer; @@ -109,6 +50,7 @@ export type DemandCompBizType = { dmdInfoFk: string | ''; bzmnNo: string | ''; dmdCoNm: string | ''; + zip: string | ''; daddr: string | ''; telno: string | ''; rgtr: string | ''; @@ -135,8 +77,9 @@ export type DemandCompBizType = { dmdInfoSn: number | ''; status: string | ''; splyCndtList: SupplyCandidate[] | [], - ruseUtztnFcltNm?: string | '' - + ruseUtztnFcltNm?: string | ''; + regDt: string | ''; + mdfcnDt : string | ''; } // 공급 후보자 타입 정의 @@ -393,7 +336,7 @@ const defaultRules: Record = { // 사용용도 const useTypesData: SelectDataType[] = [ - { text: '청소', val: "청소" }, +/* { text: '청소', val: "청소" }, { text: "화장실용수", val: "화장실용수" }, { text: "세척", val: "세척" }, { text: "살수", val: "살수" }, @@ -401,7 +344,7 @@ const useTypesData: SelectDataType[] = [ { text: "농업_간접용수", val: "농업_간접용수" }, { text: "농업_직접용수", val: "농업_직접용수" }, { text: "하천", val: "하천" }, - { text: "공업", val: "공업" }, + { text: "공업", val: "공업" }, */ ]; // 유사사례 모달 헤더 @@ -512,13 +455,28 @@ const postSdmData = async (data: DemandCompType) => { } // 수요정보 등록 -const postSdmInfoData = async (data: DemandCompBizType) => { +const postSdmInfoData = async (data: SdmInfoFormData): Promise => { try { await postFetch("/reuseFc/request/demandInfo", data); - toast.success('수요 정보 등록에 성공하였습니다.'); + alert('수요 정보 등록에 성공하였습니다.'); + return true; } catch (error) { console.log(error); - toast.error('수요 정보 등록에 실패하였습니다.'); + alert('수요 정보 등록에 실패하였습니다.'); + return false; + } +} + +// 수요정보 수정 +const fetchUpdateSdmInfoData = async (data: SdmInfoFormData): Promise => { + try { + await postFetch("/reuseFc/request/updateDemandInfo", data); + alert('수요 정보 수정에 성공하였습니다.'); + return true; + } catch (error) { + console.log(error); + alert('수요 정보 수정에 실패하였습니다.'); + return false; } } @@ -532,6 +490,8 @@ const postSdmLoginData = async (data: DemandCompType) => { let loginSuccess = false; if (status === 200 && loginData != null) { alert("수요처 로그인에 성공하였습니다."); + delete loginData.dmdAplyPswd; + delete loginData.rgtr; sessionStorage.setItem("user", JSON.stringify(loginData)); loginSuccess = true; } else if (status === 401) { @@ -592,12 +552,15 @@ const fetchGetSimilarCaseList = async (part: string, value: number | string) => export type SdmActions = { initializeSdmData: () => void; insertSdmData: (data: DemandCompType) => Promise; - insertSdmInfoData: (data: DemandCompBizType) => Promise; + insertSdmInfoData: (data: SdmInfoFormData) => Promise; + updateSdmInfoData: (data: SdmInfoFormData) => Promise; getDemandCompList: (searchText: string, pageCnt: number) => Promise; actionLogin: (data: DemandCompType) => Promise; getSdmInfoStatusList: (dmdInfoId: string) => Promise; getSimilarCaseList: (part: string, value: number | string) => Promise; actionLogout: () => Promise; + + getUseTypesData: () => Promise; } export type SdmType = { @@ -665,8 +628,12 @@ export const SdmReducer = (set: any): SdmActions => { } }, - insertSdmInfoData: async (data: DemandCompBizType) => { - postSdmInfoData(data); + insertSdmInfoData: async (data: SdmInfoFormData): Promise => { + return await postSdmInfoData(data); + }, + + updateSdmInfoData: async (data: SdmInfoFormData): Promise => { + return await fetchUpdateSdmInfoData(data); }, // 수요처 정보 @@ -717,6 +684,7 @@ export const SdmReducer = (set: any): SdmActions => { bzmnNo: res.bzmnNo, dmdCoNm: res.dmdCoNm, dmdInfoId: res.dmdInfoId, + zip: res.zip || '', daddr: res.daddr, telno: res.telno.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'), rgtr: res.rgtr, @@ -762,7 +730,6 @@ export const SdmReducer = (set: any): SdmActions => { ruseBizNm: maskedBizNm, }; }); - console.log(similarCaseList); if (similarCaseList && similarCaseList.length !== 0) { set((state: SdmType) => ({ @@ -791,6 +758,24 @@ export const SdmReducer = (set: any): SdmActions => { successLogin: false, } })) + }, + + getUseTypesData: async () => { + //http://dev.onasys.co.kr:3000/server/cms-service/api/v1/lwpo-cds?lwpoCdNm=&sort=regDt,desc&upCd=WR018&page=0&size=10 + + const upCd = await getFetchWithCount( + fetchType.CS, + "/cms-service/api/v1/lwpo-cds/?lwpoCdNm=&sort=regDt,desc&upCd=WR018&page=0&size=100" + ); + + const comUseTypes = upCd.content.filter(el=>el.lwpoCdNm != '친수용수').map((el: any) => ({ text: el.lwpoCdNm, val: el.lwpoCd })); + + set((state: SdmType) => ({ + Sdm: { + ...state.Sdm, + useTypesData: comUseTypes, + } + })); } } } diff --git a/apps/dm_new/app/store/sdmMng/postcode-service.ts b/apps/dm/app/store/sdmMng/postcode-service.ts similarity index 100% rename from apps/dm_new/app/store/sdmMng/postcode-service.ts rename to apps/dm/app/store/sdmMng/postcode-service.ts diff --git a/apps/dm/app/store/store.tsx b/apps/dm/app/store/store.tsx index f885a3bd0..25508bae2 100644 --- a/apps/dm/app/store/store.tsx +++ b/apps/dm/app/store/store.tsx @@ -33,7 +33,7 @@ export const StoreZus = ( ) => { return createStore()((set) => { if (typeof window !== "undefined") { - tokenManager.setToken("site-id-key", "DM"); + tokenManager.setToken("site-id-key", process.env.NEXT_PUBLIC_SITE_ID||"DM"); tokenManager.setToken("site-theme-key", "green"); } diff --git a/apps/dm/app/style/PublicReuseFaciltyInsert.module.css b/apps/dm/app/style/PublicReuseFaciltyInsert.module.css deleted file mode 100644 index 6fc6f75ee..000000000 --- a/apps/dm/app/style/PublicReuseFaciltyInsert.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.formInsideTitle{ - background-color: gray; - text-align: center; -} - - - -.smallTitle{ -} \ No newline at end of file diff --git a/apps/dm_new/components/CandidateSiteModal.tsx b/apps/dm/components/CandidateSiteModal.tsx similarity index 100% rename from apps/dm_new/components/CandidateSiteModal.tsx rename to apps/dm/components/CandidateSiteModal.tsx diff --git a/apps/dm_new/components/CaseImageSlider.tsx b/apps/dm/components/CaseImageSlider.tsx similarity index 100% rename from apps/dm_new/components/CaseImageSlider.tsx rename to apps/dm/components/CaseImageSlider.tsx diff --git a/apps/dm_new/components/CaseTabsCarousel.tsx b/apps/dm/components/CaseTabsCarousel.tsx similarity index 100% rename from apps/dm_new/components/CaseTabsCarousel.tsx rename to apps/dm/components/CaseTabsCarousel.tsx diff --git a/apps/dm_new/components/ImageSlider.tsx b/apps/dm/components/ImageSlider.tsx similarity index 100% rename from apps/dm_new/components/ImageSlider.tsx rename to apps/dm/components/ImageSlider.tsx diff --git a/apps/dm_new/components/Lnb.tsx b/apps/dm/components/Lnb.tsx similarity index 100% rename from apps/dm_new/components/Lnb.tsx rename to apps/dm/components/Lnb.tsx diff --git a/apps/dm_new/components/LoginModal.tsx b/apps/dm/components/LoginModal.tsx similarity index 100% rename from apps/dm_new/components/LoginModal.tsx rename to apps/dm/components/LoginModal.tsx diff --git a/apps/dm_new/components/LoginStatus.tsx b/apps/dm/components/LoginStatus.tsx similarity index 100% rename from apps/dm_new/components/LoginStatus.tsx rename to apps/dm/components/LoginStatus.tsx diff --git a/apps/dm_new/components/PostcodeModal.tsx b/apps/dm/components/PostcodeModal.tsx similarity index 100% rename from apps/dm_new/components/PostcodeModal.tsx rename to apps/dm/components/PostcodeModal.tsx diff --git a/apps/dm_new/components/SubPageHeader.tsx b/apps/dm/components/SubPageHeader.tsx similarity index 100% rename from apps/dm_new/components/SubPageHeader.tsx rename to apps/dm/components/SubPageHeader.tsx diff --git a/apps/dm_new/components/TopNav.tsx b/apps/dm/components/TopNav.tsx similarity index 100% rename from apps/dm_new/components/TopNav.tsx rename to apps/dm/components/TopNav.tsx diff --git a/apps/dm/components/toastFromUi.tsx b/apps/dm/components/toastFromUi.tsx deleted file mode 100644 index 24b0b985f..000000000 --- a/apps/dm/components/toastFromUi.tsx +++ /dev/null @@ -1,53 +0,0 @@ - -import { Button } from "@common/components/ui"; -import Link from "next/link"; -import toast from "react-hot-toast"; - -export function ToastFormUi(body:string,title:string,url:string,buttonName:string){ - - //const router = useRouter(); - - return( - - toast.custom((t) => ( - -
-
-
-
- -
-
-

- {title} -

-

- {body} -

-
-
-
-
- -
-
- -
-
- )) - ) -} \ No newline at end of file diff --git a/apps/dm/components/ui/api/index.ts b/apps/dm/components/ui/api/index.ts index abb0c2c73..49d244d25 100644 --- a/apps/dm/components/ui/api/index.ts +++ b/apps/dm/components/ui/api/index.ts @@ -1,6 +1,7 @@ //8880 import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config"; import { getCookie } from "@common/components/lib/cookie"; +import { DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "../config/"; const tokenId = getCookie(AUTH_USER_ID); @@ -10,7 +11,9 @@ export const fetchType = { COMMON: "common", WR: "wr", FO: "fo", - SN: "sn" + SN: "sn", + LOCAL: "LOCAL", + CS: "CS", }; const API_VERSION = "/api/v1" @@ -44,6 +47,55 @@ export const getFetch = async (type: string, url: string) => { return res; }; +export const getFetchWithCount = async (type: string, url: string) => { + const div = + type === fetchType.COMMON + ? DEV_URL_FO + : type === fetchType.LOCAL + ? LOCAL_URL + : type === fetchType.CS + ? "" + : DEV_URL_WR; + const apiUrl = div + url; + + const response = await fetch(apiUrl, { + method: "GET", + headers: { + accept: "application/json", + }, + }); + + if (!response.ok) { + console.log(`오류 발생: ${response.status}`); + } + + const contentType = response.headers.get("content-type") ?? ""; + + if (contentType.includes("application/json")) { + // JSON 응답 처리 + const res = await response.json(); + + return res; + } else if(contentType.includes('application/octet-stream')) { + // 파일 응답 처리 (ex. PDF, Excel 등) + + console.log('contentType ' ,contentType) + const blob = await response.blob(); + const blobUrl = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + const filename = "downloaded_file.PDF"; + + a.href = blobUrl; + a.download = filename; + a.click(); + window.URL.revokeObjectURL(blobUrl); + + return null; // 다운로드이므로 반환값 없음 + }else{ + throw Error('오류 발생') + } +}; + export const postFetch = async (url: string, params: RequestParams) => { const baseUrl = servers[fetchType.WR]; const response = await fetch(baseUrl + url, { diff --git a/apps/dm/components/ui/config/index.ts b/apps/dm/components/ui/config/index.ts index 1585bb767..8dc7bdb50 100644 --- a/apps/dm/components/ui/config/index.ts +++ b/apps/dm/components/ui/config/index.ts @@ -1,6 +1,6 @@ import { PROXY_HOST } from "@/src/shared/config/env"; export const LOCAL_URL = PROXY_HOST; -export const DEV_URL_FO = "/operation-service"; -export const DEV_URL_WR = "/reuse-service"; -export const DEV_URL_SN = "/sewerage-service"; +export const DEV_URL_FO = "/operation-service/api/v1"; +export const DEV_URL_WR = "/reuse-service/api/v1"; +export const DEV_URL_SN = "/sewerage-service/api/v1"; diff --git a/apps/dm/components/ui/map/SearchInputText/SearchInputText.tsx b/apps/dm/components/ui/map/SearchInputText/SearchInputText.tsx index 05f41d09b..b37fdb788 100644 --- a/apps/dm/components/ui/map/SearchInputText/SearchInputText.tsx +++ b/apps/dm/components/ui/map/SearchInputText/SearchInputText.tsx @@ -94,7 +94,7 @@ export default function SearchInputText({ value={textValue || ""} onChange={handleInputChange} onKeyDown={handleKeyDown} - className="w-full h-[3.2rem] !text-xl pl-3 text-gray-600 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + className="w-full h-8 text-xs pl-2 pr-2 text-gray-600 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-blue-500" placeholder={`${label}을(를) 입력하세요`} onClick={() => { setTextValue(""); @@ -109,10 +109,10 @@ export default function SearchInputText({ searchList.map((item, index) => (
handleSuggestionClick(item.bscFctNm)} role="button" @@ -127,7 +127,7 @@ export default function SearchInputText({
)) ) : ( -
+
검색 결과가 없습니다.
)} diff --git a/apps/dm/components/ui/map/searchBar/searchBar.tsx b/apps/dm/components/ui/map/searchBar/searchBar.tsx index 7ca64bbce..c52b85b0a 100644 --- a/apps/dm/components/ui/map/searchBar/searchBar.tsx +++ b/apps/dm/components/ui/map/searchBar/searchBar.tsx @@ -62,50 +62,51 @@ export function SearchBar({ ); return ( -
-
+
+
onSelectValue(val.toString(), "ctpvCd")} value={sidoValue || Common.selectSidoData[0]?.val || ""} /> onSelectValue(val.toString(), "sggCd")} value={sigunValue} /> - - { - let sggOrCity = ""; - if (searchNm) { - if (sigunValue === "0" || sigunValue === "") { - sggOrCity = sidoValue; - } else { - sggOrCity = sigunValue; + +
+ { + let sggOrCity = ""; + if (searchNm) { + if (sigunValue === "0" || sigunValue === "") { + sggOrCity = sidoValue; + } else { + sggOrCity = sigunValue; + } + findRuseFclt(sggOrCity, searchNm); } - findRuseFclt(sggOrCity, searchNm); - } - }} - setRtnData={(rtnData: any) => { - setHasuPk(rtnData); - }} - searchList={map.ruseFcltList || []} - /> + }} + setRtnData={(rtnData: any) => { + setHasuPk(rtnData); + }} + searchList={map.ruseFcltList || []} + /> +
- -
-
- {(form.getValues("candidateTitle") || []).map( - (candidate: any, index: number) => ( - - ) - )} -
-
- ), - }, - ]; +// { +// name: "candidateTitle", +// label: CANDIDATE || "공급처 후보지 선택(필수아님)", +// formIndex: 2, +// component: () => ( +//
+//
+// +// +//
+//
+// {(form.getValues("candidateTitle") || []).map( +// (candidate: any, index: number) => ( +// +// ) +// )} +//
+//
+// ), +// }, +// ]; - const normalFields = formFields.filter( - (f) => f.formIndex === 0 && f.component - ); +// const normalFields = formFields.filter( +// (f) => f.formIndex === 0 && f.component +// ); - const candidateFields = formFields.filter( - (f) => f.formIndex === 2 && f.component - ); +// const candidateFields = formFields.filter( +// (f) => f.formIndex === 2 && f.component +// ); - return ( - <> - {normalFields.map(({ name, label, component, required }) => - component ? ( - - name === "money" ? ( -
- - {component(field)} -
- ) : ( - // {component(field)} - - ) - } - /> - ) : null - )} +// return ( +// <> +// {normalFields.map(({ name, label, component, required }) => +// component ? ( +// +// name === "money" ? ( +//
+// +// {component(field)} +//
+// ) : ( +// // {component(field)} +// +// ) +// } +// /> +// ) : null +// )} -
- 요구수질 -
- { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "불검출" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - {/* TODO: 총대장균군수 확인 필요*/} - {field.value !== null ? ( - - 이하 - - ) : ( - <> - )} -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const cleanValue = sanitizeInput( - e.target.value, - true - ); - field.onChange( - cleanValue === "" ? null : Number(cleanValue) - ); - }} - value={field.value ?? ""} - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이상 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const cleanValue = sanitizeInput( - e.target.value, - true - ); - field.onChange( - cleanValue === "" ? null : Number(cleanValue) - ); - }} - value={field.value ?? ""} - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> -
- -
- { - const cleanValue = sanitizeInput(e.target.value, true); - isSetPh(cleanValue, 1); - }} - value={frontPHdata ?? ""} - maxLength={5} - /> - ~ - { - const cleanValue = sanitizeInput(e.target.value, true); - isSetPh(cleanValue, 0); - }} - value={backPHdata ?? ""} - maxLength={5} - /> -
-
- { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - - { - field.onChange(val.toString()); - }} - value={field.value} - readOnly={isDisabled} - onBlur={field.onBlur} - ref={field.ref} - /> - - ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - const disabledFields = usePurpose ? disableRules[usePurpose] : []; - const isDisabled = disabledFields - ? disabledFields.includes(field.name) - : true; - return ( - -
-
- { - const inputValue = e.target.value.replace( - /[^0-9]/g, - "" - ); // 정수만 - field.onChange( - inputValue === "" ? null : Number(inputValue) - ); - }} - value={ - field.value !== undefined && field.value !== null - ? `${field.value}` - : "" - } - maxLength={6} - disabled={isDisabled} - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> - - 이하 - -
-
-
- ); - }} - /> - { - return ( - -
-
- { - field.onChange(e.target.value); - }} - value={ - field.value !== undefined && field.value !== "" - ? `${field.value}` - : "" - } - onBlur={field.onBlur} - name={field.name} - ref={field.ref} - /> -
-
-
- ); - }} - /> -
-
+//
+// 요구수질 +//
+// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "불검출" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// {/* TODO: 총대장균군수 확인 필요*/} +// {field.value !== null ? ( +// +// 이하 +// +// ) : ( +// <> +// )} +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const cleanValue = sanitizeInput( +// e.target.value, +// true +// ); +// field.onChange( +// cleanValue === "" ? null : Number(cleanValue) +// ); +// }} +// value={field.value ?? ""} +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이상 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const cleanValue = sanitizeInput( +// e.target.value, +// true +// ); +// field.onChange( +// cleanValue === "" ? null : Number(cleanValue) +// ); +// }} +// value={field.value ?? ""} +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +//
+// +//
+// { +// const cleanValue = sanitizeInput(e.target.value, true); +// isSetPh(cleanValue, 1); +// }} +// value={frontPHdata ?? ""} +// maxLength={5} +// /> +// ~ +// { +// const cleanValue = sanitizeInput(e.target.value, true); +// isSetPh(cleanValue, 0); +// }} +// value={backPHdata ?? ""} +// maxLength={5} +// /> +//
+//
+// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +// { +// field.onChange(val.toString()); +// }} +// value={field.value} +// readOnly={isDisabled} +// onBlur={field.onBlur} +// ref={field.ref} +// /> +// +// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// const disabledFields = usePurpose ? disableRules[usePurpose] : []; +// const isDisabled = disabledFields +// ? disabledFields.includes(field.name) +// : true; +// return ( +// +//
+//
+// { +// const inputValue = e.target.value.replace( +// /[^0-9]/g, +// "" +// ); // 정수만 +// field.onChange( +// inputValue === "" ? null : Number(inputValue) +// ); +// }} +// value={ +// field.value !== undefined && field.value !== null +// ? `${field.value}` +// : "" +// } +// maxLength={6} +// disabled={isDisabled} +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +// +// 이하 +// +//
+//
+//
+// ); +// }} +// /> +// { +// return ( +// +//
+//
+// { +// field.onChange(e.target.value); +// }} +// value={ +// field.value !== undefined && field.value !== "" +// ? `${field.value}` +// : "" +// } +// onBlur={field.onBlur} +// name={field.name} +// ref={field.ref} +// /> +//
+//
+//
+// ); +// }} +// /> +//
+//
- {candidateFields.map(({ name, label, component, required }, index) => - component ? ( - ( - // - // {component(field)} - // - - )} - /> - ) : null - )} +// {candidateFields.map(({ name, label, component, required }, index) => +// component ? ( +// ( +// // +// // {component(field)} +// // +// +// )} +// /> +// ) : null +// )} - {isSimilarCase && ( - - } - bodyClass="sm:max-w-[100vw] w-[1400px] h-[100dvh] max-h-[900px] overflow-y-auto overflow-x-hidden" - open={isSimilarCase} - onOpenChange={(open) => !open && setIsSimilarCase(false)} - noHeaderYn="Y" - noFooterYn="Y" - /> - )} - - ); -} +// {isSimilarCase && ( +// +// } +// bodyClass="sm:max-w-[100vw] w-[1400px] h-[100dvh] max-h-[900px] overflow-y-auto overflow-x-hidden" +// open={isSimilarCase} +// onOpenChange={(open) => !open && setIsSimilarCase(false)} +// noHeaderYn="Y" +// noFooterYn="Y" +// /> +// )} +// +// ); +// } diff --git a/apps/dm/eslint.config.mjs b/apps/dm/eslint.config.mjs index f477f55e1..7de132089 100644 --- a/apps/dm/eslint.config.mjs +++ b/apps/dm/eslint.config.mjs @@ -12,9 +12,19 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + { + files: ["**/*.{ts,tsx,mts,cts}"], + languageOptions: { + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + }, + }, {rules:{ // other rules.. "@typescript-eslint/no-explicit-any": "off", + // "@next/next/no-img-element": "off", ...(isProd ? { "react-hooks/exhaustive-deps": "off" } : {}), }}, ]; diff --git a/apps/dm/middleware.ts b/apps/dm/middleware.ts index 44d4cb390..e55f65515 100644 --- a/apps/dm/middleware.ts +++ b/apps/dm/middleware.ts @@ -8,6 +8,7 @@ const serverApiUrl = process.env.NEXT_PUBLIC_CS_SERVER_API_URL; const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || ""; const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || ""; const GATEWAY_URL = process.env.NEXT_PUBLIC_GATEWAY_URL || ""; +const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; const BUSINESS_JUSO_API_URL = process.env.NEXT_PUBLIC_FINDADRESS_URL || ""; const AdressKey = process.env.NEXT_PRIVATE_WR_ADDRESS_KEY || ""; @@ -28,15 +29,6 @@ export function middleware(req: NextRequest) { console.log(`[Middleware] Request URL: ${req.url}`); const searchParams = req.nextUrl.searchParams; - // if (pathname.startsWith("/server")) { - // const queryParams = searchParams.toString(); - // const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ - // queryParams ? `?${queryParams}` : "" - // }`; - // console.log(`[Middleware] Rewriting from '/server' path to: ${newUrl}`); - // return NextResponse.rewrite(new URL(newUrl)); - // } - if (pathname.startsWith("/server")) { const decodedParams = Array.from(searchParams.entries()) .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) @@ -49,7 +41,7 @@ export function middleware(req: NextRequest) { if (pathname === "/") { console.log(" maim 화면이동 "); - return NextResponse.redirect(new URL("/pages/wrStatus", req.url)); + return NextResponse.redirect(new URL("/main", req.url)); } if (pathname.startsWith("/attach-service")) { @@ -81,6 +73,8 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/geoserver")) { let queryParams = searchParams.toString(); const typename = searchParams.get("TYPENAME"); + const startYmd = searchParams.get("START_YMD") ?? null; + const endYmd = searchParams.get("END_YMD") ?? null; console.log("typename ", typename); let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; @@ -111,8 +105,10 @@ export function middleware(req: NextRequest) { ) { const url = req.nextUrl.clone(); url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); + if (startYmd) url.searchParams.set("startYmd", startYmd); + if (endYmd) url.searchParams.set("endYmd", endYmd); queryParams = url.searchParams.toString(); - newUrl = `${GATEWAY_URL}/api/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; + newUrl = `${GATEWAY_URL}/sewerage-service/api/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; } console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); diff --git a/apps/dm/next.config.ts b/apps/dm/next.config.ts index 5e1533ec9..5b5bf85da 100644 --- a/apps/dm/next.config.ts +++ b/apps/dm/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from "next"; +// import path from "path"; const subDomain = process.env.NEXT_PUBLIC_PROXY_HOST || "http://localhost:3000"; @@ -8,6 +9,17 @@ const nextConfig: NextConfig = { experimental: { proxyTimeout: 600000, }, + + // // 🔥 반드시 필요 + // webpack(config) { + // config.resolve.alias = { + // ...(config.resolve.alias || {}), + // // monorepo 구조 기준 + // "@common": path.resolve(__dirname, "../../packages/common"), + // }; + // return config; + // }, + async rewrites() { return { beforeFiles: [ diff --git a/apps/dm/package.json b/apps/dm/package.json index 71d504f50..272584f3e 100644 --- a/apps/dm/package.json +++ b/apps/dm/package.json @@ -3,9 +3,9 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -p 3005", + "dev": "next dev -p 3015", "build": "next build", - "start:dm": "next start -p 3005", + "start:dm": "next start -p 3015", "lint": "next lint" }, "dependencies": { @@ -29,8 +29,8 @@ "@eslint/eslintrc": "^3", "@types/webpack": "^5.28.5", "eslint": "^9", - "eslint-config-next": "15.1.7", - "postcss": "^8", + "eslint-config-next": "15.3.8", + "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" } diff --git a/apps/dm/public/data/wrStatusData.json b/apps/dm/public/data/wrStatusData.json deleted file mode 100644 index 334b521c1..000000000 --- a/apps/dm/public/data/wrStatusData.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { "region": "전국", "sewage": "717(1,137,538)", "rainwater": "3544(13,945)", "reclaimed": "1018(222,476)", "thermal": "123(492)" }, - { "region": "서울특별시", "sewage": "4(114,908)", "rainwater": "816(430)", "reclaimed": "371(341)", "thermal": "0" }, - { "region": "부산광역시", "sewage": "13(146,197)", "rainwater": "68(335)", "reclaimed": "20(512)", "thermal": "1(18)" }, - { "region": "대구광역시", "sewage": "9(102,380)", "rainwater": "294(133)", "reclaimed": "14(44)", "thermal": "2(12)" }, - { "region": "인천광역시", "sewage": "17(57,691)", "rainwater": "165(642)", "reclaimed": "35(7,859)", "thermal": "15(32)" }, - { "region": "광주광역시", "sewage": "3(28,738)", "rainwater": "74(23)", "reclaimed": "15(450)", "thermal": "15(6)" }, - { "region": "대전광역시", "sewage": "2(11,966)", "rainwater": "52(2)", "reclaimed": "11(492)", "thermal": "20(100)" }, - { "region": "울산광역시", "sewage": "8(36,076)", "rainwater": "111(39)", "reclaimed": "19(28,904)", "thermal": "30(130)" }, - { "region": "세종특별자치시", "sewage": "9(2,555)", "rainwater": "65(16)", "reclaimed": "0(0)", "thermal": "0" }, - { "region": "경기도", "sewage": "176(313,298)", "rainwater": "1009(4,735)", "reclaimed": "252(77,096)", "thermal": "40(143)" }, - { "region": "강원특별자치도", "sewage": "69(19,677)", "rainwater": "69(4)", "reclaimed": "22(447)", "thermal": "0" }, - { "region": "충청북도", "sewage": "38(15,607)", "rainwater": "17(91)", "reclaimed": "91(2,211)", "thermal": "0" }, - { "region": "충청남도", "sewage": "67(41,368)", "rainwater": "54(77)", "reclaimed": "188(50,434)", "thermal": "0" }, - { "region": "전라북도", "sewage": "55(42,671)", "rainwater": "33(199)", "reclaimed": "220(9,453)", "thermal": "0" }, - { "region": "전라남도", "sewage": "83(23,560)", "rainwater": "16(885)", "reclaimed": "75(15,412)", "thermal": "0" }, - { "region": "경상북도", "sewage": "81(129,081)", "rainwater": "37(341)", "reclaimed": "70(27,818)", "thermal": "0" }, - { "region": "경상남도", "sewage": "75(50,101)", "rainwater": "27(244)", "reclaimed": "140(343)", "thermal": "0" }, - { "region": "제주특별자치도", "sewage": "8(1,666)", "rainwater": "47(5,746)", "reclaimed": "64(660)", "thermal": "0" } - ] \ No newline at end of file diff --git a/apps/dm/public/file.svg b/apps/dm/public/file.svg deleted file mode 100644 index 004145cdd..000000000 --- a/apps/dm/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dm/public/fonts/NotoSansKR-Black.otf b/apps/dm/public/fonts/NotoSansKR-Black.otf deleted file mode 100644 index 2b54d195e..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Black.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Black.woff b/apps/dm/public/fonts/NotoSansKR-Black.woff deleted file mode 100644 index 90ef00381..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Black.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Black.woff2 b/apps/dm/public/fonts/NotoSansKR-Black.woff2 deleted file mode 100644 index 8953c8fa7..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Black.woff2 and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Bold.otf b/apps/dm/public/fonts/NotoSansKR-Bold.otf deleted file mode 100644 index 2b78dd845..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Bold.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Bold.woff b/apps/dm/public/fonts/NotoSansKR-Bold.woff deleted file mode 100644 index 74b20d49c..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Bold.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Bold.woff2 b/apps/dm/public/fonts/NotoSansKR-Bold.woff2 deleted file mode 100644 index 216f94dad..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Bold.woff2 and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Light.otf b/apps/dm/public/fonts/NotoSansKR-Light.otf deleted file mode 100644 index ecab31abc..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Light.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Light.woff b/apps/dm/public/fonts/NotoSansKR-Light.woff deleted file mode 100644 index 40aabd2a4..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Light.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Light.woff2 b/apps/dm/public/fonts/NotoSansKR-Light.woff2 deleted file mode 100644 index 6b7cd1307..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Light.woff2 and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Medium.otf b/apps/dm/public/fonts/NotoSansKR-Medium.otf deleted file mode 100644 index 371959d51..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Medium.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Medium.woff b/apps/dm/public/fonts/NotoSansKR-Medium.woff deleted file mode 100644 index e15aae09e..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Medium.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Medium.woff2 b/apps/dm/public/fonts/NotoSansKR-Medium.woff2 deleted file mode 100644 index b97668034..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Medium.woff2 and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Regular.otf b/apps/dm/public/fonts/NotoSansKR-Regular.otf deleted file mode 100644 index 7ba76b49c..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Regular.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Regular.woff b/apps/dm/public/fonts/NotoSansKR-Regular.woff deleted file mode 100644 index 40fab257c..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Regular.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Thin.otf b/apps/dm/public/fonts/NotoSansKR-Thin.otf deleted file mode 100644 index 06c566278..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Thin.otf and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Thin.woff b/apps/dm/public/fonts/NotoSansKR-Thin.woff deleted file mode 100644 index 40723731f..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Thin.woff and /dev/null differ diff --git a/apps/dm/public/fonts/NotoSansKR-Thin.woff2 b/apps/dm/public/fonts/NotoSansKR-Thin.woff2 deleted file mode 100644 index 7835c23ed..000000000 Binary files a/apps/dm/public/fonts/NotoSansKR-Thin.woff2 and /dev/null differ diff --git a/apps/dm/public/globe.svg b/apps/dm/public/globe.svg deleted file mode 100644 index 567f17b0d..000000000 --- a/apps/dm/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dm/public/images/help.png b/apps/dm/public/images/help.png deleted file mode 100644 index e872802e4..000000000 Binary files a/apps/dm/public/images/help.png and /dev/null differ diff --git a/apps/dm_new/public/images/landing/australia_01.png b/apps/dm/public/images/landing/australia_01.png similarity index 100% rename from apps/dm_new/public/images/landing/australia_01.png rename to apps/dm/public/images/landing/australia_01.png diff --git a/apps/dm_new/public/images/landing/business/globalCase/australia_01.png b/apps/dm/public/images/landing/business/globalCase/australia_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/globalCase/australia_01.png rename to apps/dm/public/images/landing/business/globalCase/australia_01.png diff --git a/apps/dm_new/public/images/landing/business/globalCase/eu_01.png b/apps/dm/public/images/landing/business/globalCase/eu_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/globalCase/eu_01.png rename to apps/dm/public/images/landing/business/globalCase/eu_01.png diff --git a/apps/dm_new/public/images/landing/business/globalCase/overview_01.png b/apps/dm/public/images/landing/business/globalCase/overview_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/globalCase/overview_01.png rename to apps/dm/public/images/landing/business/globalCase/overview_01.png diff --git a/apps/dm_new/public/images/landing/business/globalCase/singapore_01.png b/apps/dm/public/images/landing/business/globalCase/singapore_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/globalCase/singapore_01.png rename to apps/dm/public/images/landing/business/globalCase/singapore_01.png diff --git a/apps/dm_new/public/images/landing/business/globalCase/taiwan_01.png b/apps/dm/public/images/landing/business/globalCase/taiwan_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/globalCase/taiwan_01.png rename to apps/dm/public/images/landing/business/globalCase/taiwan_01.png diff --git a/apps/dm_new/public/images/landing/business/rain/rain_01.png b/apps/dm/public/images/landing/business/rain/rain_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/rain/rain_01.png rename to apps/dm/public/images/landing/business/rain/rain_01.png diff --git a/apps/dm_new/public/images/landing/business/rain/rain_02.png b/apps/dm/public/images/landing/business/rain/rain_02.png similarity index 100% rename from apps/dm_new/public/images/landing/business/rain/rain_02.png rename to apps/dm/public/images/landing/business/rain/rain_02.png diff --git a/apps/dm_new/public/images/landing/business/rain/rain_03.png b/apps/dm/public/images/landing/business/rain/rain_03.png similarity index 100% rename from apps/dm_new/public/images/landing/business/rain/rain_03.png rename to apps/dm/public/images/landing/business/rain/rain_03.png diff --git a/apps/dm_new/public/images/landing/business/rain/rain_04.png b/apps/dm/public/images/landing/business/rain/rain_04.png similarity index 100% rename from apps/dm_new/public/images/landing/business/rain/rain_04.png rename to apps/dm/public/images/landing/business/rain/rain_04.png diff --git a/apps/dm_new/public/images/landing/business/sewage/bolyeong_01.png b/apps/dm/public/images/landing/business/sewage/bolyeong_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/bolyeong_01.png rename to apps/dm/public/images/landing/business/sewage/bolyeong_01.png diff --git a/apps/dm_new/public/images/landing/business/sewage/cheongju_01.png b/apps/dm/public/images/landing/business/sewage/cheongju_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/cheongju_01.png rename to apps/dm/public/images/landing/business/sewage/cheongju_01.png diff --git a/apps/dm_new/public/images/landing/business/sewage/guri_01.png b/apps/dm/public/images/landing/business/sewage/guri_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/guri_01.png rename to apps/dm/public/images/landing/business/sewage/guri_01.png diff --git a/apps/dm_new/public/images/landing/business/sewage/guri_02.png b/apps/dm/public/images/landing/business/sewage/guri_02.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/guri_02.png rename to apps/dm/public/images/landing/business/sewage/guri_02.png diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_01.png b/apps/dm/public/images/landing/business/sewage/hwanggu_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/hwanggu_01.png rename to apps/dm/public/images/landing/business/sewage/hwanggu_01.png diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_02.png b/apps/dm/public/images/landing/business/sewage/hwanggu_02.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/hwanggu_02.png rename to apps/dm/public/images/landing/business/sewage/hwanggu_02.png diff --git a/apps/dm_new/public/images/landing/business/sewage/hwanggu_03.png b/apps/dm/public/images/landing/business/sewage/hwanggu_03.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/hwanggu_03.png rename to apps/dm/public/images/landing/business/sewage/hwanggu_03.png diff --git a/apps/dm_new/public/images/landing/business/sewage/paju_01.png b/apps/dm/public/images/landing/business/sewage/paju_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/paju_01.png rename to apps/dm/public/images/landing/business/sewage/paju_01.png diff --git a/apps/dm_new/public/images/landing/business/sewage/yeosu_01.png b/apps/dm/public/images/landing/business/sewage/yeosu_01.png similarity index 100% rename from apps/dm_new/public/images/landing/business/sewage/yeosu_01.png rename to apps/dm/public/images/landing/business/sewage/yeosu_01.png diff --git a/apps/dm_new/public/images/landing/calendar.svg b/apps/dm/public/images/landing/calendar.svg similarity index 100% rename from apps/dm_new/public/images/landing/calendar.svg rename to apps/dm/public/images/landing/calendar.svg diff --git a/apps/dm_new/public/images/landing/card_01.jpg b/apps/dm/public/images/landing/card_01.jpg similarity index 100% rename from apps/dm_new/public/images/landing/card_01.jpg rename to apps/dm/public/images/landing/card_01.jpg diff --git a/apps/dm_new/public/images/landing/card_02.jpg b/apps/dm/public/images/landing/card_02.jpg similarity index 100% rename from apps/dm_new/public/images/landing/card_02.jpg rename to apps/dm/public/images/landing/card_02.jpg diff --git a/apps/dm_new/public/images/landing/card_03.jpg b/apps/dm/public/images/landing/card_03.jpg similarity index 100% rename from apps/dm_new/public/images/landing/card_03.jpg rename to apps/dm/public/images/landing/card_03.jpg diff --git a/apps/dm_new/public/images/landing/card_04.jpg b/apps/dm/public/images/landing/card_04.jpg similarity index 100% rename from apps/dm_new/public/images/landing/card_04.jpg rename to apps/dm/public/images/landing/card_04.jpg diff --git a/apps/dm_new/public/images/landing/cases/greywater_building.png b/apps/dm/public/images/landing/cases/greywater_building.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/greywater_building.png rename to apps/dm/public/images/landing/cases/greywater_building.png diff --git a/apps/dm_new/public/images/landing/cases/greywater_factory.png b/apps/dm/public/images/landing/cases/greywater_factory.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/greywater_factory.png rename to apps/dm/public/images/landing/cases/greywater_factory.png diff --git a/apps/dm_new/public/images/landing/cases/greywater_overall.png b/apps/dm/public/images/landing/cases/greywater_overall.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/greywater_overall.png rename to apps/dm/public/images/landing/cases/greywater_overall.png diff --git a/apps/dm_new/public/images/landing/cases/greywater_usage.png b/apps/dm/public/images/landing/cases/greywater_usage.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/greywater_usage.png rename to apps/dm/public/images/landing/cases/greywater_usage.png diff --git a/apps/dm_new/public/images/landing/cases/rainwater_building.png b/apps/dm/public/images/landing/cases/rainwater_building.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/rainwater_building.png rename to apps/dm/public/images/landing/cases/rainwater_building.png diff --git a/apps/dm_new/public/images/landing/cases/rainwater_usage.png b/apps/dm/public/images/landing/cases/rainwater_usage.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/rainwater_usage.png rename to apps/dm/public/images/landing/cases/rainwater_usage.png diff --git a/apps/dm_new/public/images/landing/cases/rainwater_usage_amount.png b/apps/dm/public/images/landing/cases/rainwater_usage_amount.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/rainwater_usage_amount.png rename to apps/dm/public/images/landing/cases/rainwater_usage_amount.png diff --git a/apps/dm_new/public/images/landing/cases/treated_water_facility.png b/apps/dm/public/images/landing/cases/treated_water_facility.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/treated_water_facility.png rename to apps/dm/public/images/landing/cases/treated_water_facility.png diff --git a/apps/dm_new/public/images/landing/cases/treated_water_region.png b/apps/dm/public/images/landing/cases/treated_water_region.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/treated_water_region.png rename to apps/dm/public/images/landing/cases/treated_water_region.png diff --git a/apps/dm_new/public/images/landing/cases/treated_water_usage.png b/apps/dm/public/images/landing/cases/treated_water_usage.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/treated_water_usage.png rename to apps/dm/public/images/landing/cases/treated_water_usage.png diff --git a/apps/dm_new/public/images/landing/cases/treated_water_year.png b/apps/dm/public/images/landing/cases/treated_water_year.png similarity index 100% rename from apps/dm_new/public/images/landing/cases/treated_water_year.png rename to apps/dm/public/images/landing/cases/treated_water_year.png diff --git a/apps/dm_new/public/images/landing/drop_arrow.svg b/apps/dm/public/images/landing/drop_arrow.svg similarity index 100% rename from apps/dm_new/public/images/landing/drop_arrow.svg rename to apps/dm/public/images/landing/drop_arrow.svg diff --git a/apps/dm_new/public/images/landing/eu_01.png b/apps/dm/public/images/landing/eu_01.png similarity index 100% rename from apps/dm_new/public/images/landing/eu_01.png rename to apps/dm/public/images/landing/eu_01.png diff --git a/apps/dm_new/public/images/landing/hero_bg.jpg b/apps/dm/public/images/landing/hero_bg.jpg similarity index 100% rename from apps/dm_new/public/images/landing/hero_bg.jpg rename to apps/dm/public/images/landing/hero_bg.jpg diff --git a/apps/dm_new/public/images/landing/icon_left_arrow.png b/apps/dm/public/images/landing/icon_left_arrow.png similarity index 100% rename from apps/dm_new/public/images/landing/icon_left_arrow.png rename to apps/dm/public/images/landing/icon_left_arrow.png diff --git a/apps/dm_new/public/images/landing/icon_login.png b/apps/dm/public/images/landing/icon_login.png similarity index 100% rename from apps/dm_new/public/images/landing/icon_login.png rename to apps/dm/public/images/landing/icon_login.png diff --git a/apps/dm_new/public/images/landing/icon_logout.png b/apps/dm/public/images/landing/icon_logout.png similarity index 100% rename from apps/dm_new/public/images/landing/icon_logout.png rename to apps/dm/public/images/landing/icon_logout.png diff --git a/apps/dm_new/public/images/landing/icon_right_arrow.png b/apps/dm/public/images/landing/icon_right_arrow.png similarity index 100% rename from apps/dm_new/public/images/landing/icon_right_arrow.png rename to apps/dm/public/images/landing/icon_right_arrow.png diff --git a/apps/dm_new/public/images/landing/intro/domestic_textbox.png b/apps/dm/public/images/landing/intro/domestic_textbox.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/domestic_textbox.png rename to apps/dm/public/images/landing/intro/domestic_textbox.png diff --git a/apps/dm_new/public/images/landing/intro/greywater_intro.png b/apps/dm/public/images/landing/intro/greywater_intro.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/greywater_intro.png rename to apps/dm/public/images/landing/intro/greywater_intro.png diff --git a/apps/dm_new/public/images/landing/intro/greywater_mandatory.png b/apps/dm/public/images/landing/intro/greywater_mandatory.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/greywater_mandatory.png rename to apps/dm/public/images/landing/intro/greywater_mandatory.png diff --git a/apps/dm_new/public/images/landing/intro/greywater_standard.png b/apps/dm/public/images/landing/intro/greywater_standard.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/greywater_standard.png rename to apps/dm/public/images/landing/intro/greywater_standard.png diff --git a/apps/dm_new/public/images/landing/intro/law_timeline.png b/apps/dm/public/images/landing/intro/law_timeline.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/law_timeline.png rename to apps/dm/public/images/landing/intro/law_timeline.png diff --git a/apps/dm_new/public/images/landing/intro/linkage.png b/apps/dm/public/images/landing/intro/linkage.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/linkage.png rename to apps/dm/public/images/landing/intro/linkage.png diff --git a/apps/dm_new/public/images/landing/intro/performance_left.png b/apps/dm/public/images/landing/intro/performance_left.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/performance_left.png rename to apps/dm/public/images/landing/intro/performance_left.png diff --git a/apps/dm_new/public/images/landing/intro/policy_timeline.png b/apps/dm/public/images/landing/intro/policy_timeline.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/policy_timeline.png rename to apps/dm/public/images/landing/intro/policy_timeline.png diff --git a/apps/dm_new/public/images/landing/intro/rainreuse_intro.png b/apps/dm/public/images/landing/intro/rainreuse_intro.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/rainreuse_intro.png rename to apps/dm/public/images/landing/intro/rainreuse_intro.png diff --git a/apps/dm_new/public/images/landing/intro/rainreuse_mandatory.png b/apps/dm/public/images/landing/intro/rainreuse_mandatory.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/rainreuse_mandatory.png rename to apps/dm/public/images/landing/intro/rainreuse_mandatory.png diff --git a/apps/dm_new/public/images/landing/intro/rainwater_design.png b/apps/dm/public/images/landing/intro/rainwater_design.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/rainwater_design.png rename to apps/dm/public/images/landing/intro/rainwater_design.png diff --git a/apps/dm_new/public/images/landing/intro/rainwater_standards.png b/apps/dm/public/images/landing/intro/rainwater_standards.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/rainwater_standards.png rename to apps/dm/public/images/landing/intro/rainwater_standards.png diff --git a/apps/dm_new/public/images/landing/intro/rel_note.png b/apps/dm/public/images/landing/intro/rel_note.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/rel_note.png rename to apps/dm/public/images/landing/intro/rel_note.png diff --git a/apps/dm_new/public/images/landing/intro/treatedwater_intro.png b/apps/dm/public/images/landing/intro/treatedwater_intro.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/treatedwater_intro.png rename to apps/dm/public/images/landing/intro/treatedwater_intro.png diff --git a/apps/dm_new/public/images/landing/intro/treatedwater_mandatory.png b/apps/dm/public/images/landing/intro/treatedwater_mandatory.png similarity index 100% rename from apps/dm_new/public/images/landing/intro/treatedwater_mandatory.png rename to apps/dm/public/images/landing/intro/treatedwater_mandatory.png diff --git a/apps/dm_new/public/images/landing/logo.png b/apps/dm/public/images/landing/logo.png similarity index 100% rename from apps/dm_new/public/images/landing/logo.png rename to apps/dm/public/images/landing/logo.png diff --git a/apps/dm_new/public/images/landing/overview_01.png b/apps/dm/public/images/landing/overview_01.png similarity index 100% rename from apps/dm_new/public/images/landing/overview_01.png rename to apps/dm/public/images/landing/overview_01.png diff --git a/apps/dm_new/public/images/landing/rain_01.png b/apps/dm/public/images/landing/rain_01.png similarity index 100% rename from apps/dm_new/public/images/landing/rain_01.png rename to apps/dm/public/images/landing/rain_01.png diff --git a/apps/dm_new/public/images/landing/scroll_down.svg b/apps/dm/public/images/landing/scroll_down.svg similarity index 100% rename from apps/dm_new/public/images/landing/scroll_down.svg rename to apps/dm/public/images/landing/scroll_down.svg diff --git a/apps/dm_new/public/images/landing/section_03.png b/apps/dm/public/images/landing/section_03.png similarity index 100% rename from apps/dm_new/public/images/landing/section_03.png rename to apps/dm/public/images/landing/section_03.png diff --git a/apps/dm_new/public/images/landing/sewage_01.png b/apps/dm/public/images/landing/sewage_01.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_01.png rename to apps/dm/public/images/landing/sewage_01.png diff --git a/apps/dm_new/public/images/landing/sewage_02.png b/apps/dm/public/images/landing/sewage_02.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_02.png rename to apps/dm/public/images/landing/sewage_02.png diff --git a/apps/dm_new/public/images/landing/sewage_03.png b/apps/dm/public/images/landing/sewage_03.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_03.png rename to apps/dm/public/images/landing/sewage_03.png diff --git a/apps/dm_new/public/images/landing/sewage_04.png b/apps/dm/public/images/landing/sewage_04.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_04.png rename to apps/dm/public/images/landing/sewage_04.png diff --git a/apps/dm_new/public/images/landing/sewage_05.png b/apps/dm/public/images/landing/sewage_05.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_05.png rename to apps/dm/public/images/landing/sewage_05.png diff --git a/apps/dm_new/public/images/landing/sewage_06.png b/apps/dm/public/images/landing/sewage_06.png similarity index 100% rename from apps/dm_new/public/images/landing/sewage_06.png rename to apps/dm/public/images/landing/sewage_06.png diff --git a/apps/dm_new/public/images/landing/singapore_01.png b/apps/dm/public/images/landing/singapore_01.png similarity index 100% rename from apps/dm_new/public/images/landing/singapore_01.png rename to apps/dm/public/images/landing/singapore_01.png diff --git a/apps/dm_new/public/images/landing/taiwan_01.png b/apps/dm/public/images/landing/taiwan_01.png similarity index 100% rename from apps/dm_new/public/images/landing/taiwan_01.png rename to apps/dm/public/images/landing/taiwan_01.png diff --git a/apps/dm_new/public/images/landing/water_quality_ref_1.png b/apps/dm/public/images/landing/water_quality_ref_1.png similarity index 100% rename from apps/dm_new/public/images/landing/water_quality_ref_1.png rename to apps/dm/public/images/landing/water_quality_ref_1.png diff --git a/apps/dm/public/img/wrAbout/icon_01.png b/apps/dm/public/img/wrAbout/icon_01.png deleted file mode 100644 index 30eff5745..000000000 Binary files a/apps/dm/public/img/wrAbout/icon_01.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/icon_02.png b/apps/dm/public/img/wrAbout/icon_02.png deleted file mode 100644 index 36401729f..000000000 Binary files a/apps/dm/public/img/wrAbout/icon_02.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/icon_03.png b/apps/dm/public/img/wrAbout/icon_03.png deleted file mode 100644 index 87f48c444..000000000 Binary files a/apps/dm/public/img/wrAbout/icon_03.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_01_1.png b/apps/dm/public/img/wrAbout/img_01_1.png deleted file mode 100644 index 9aab4e824..000000000 Binary files a/apps/dm/public/img/wrAbout/img_01_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_02_1.png b/apps/dm/public/img/wrAbout/img_02_1.png deleted file mode 100644 index aa1c8ee5a..000000000 Binary files a/apps/dm/public/img/wrAbout/img_02_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_02_2.png b/apps/dm/public/img/wrAbout/img_02_2.png deleted file mode 100644 index c8283053e..000000000 Binary files a/apps/dm/public/img/wrAbout/img_02_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_02_3.png b/apps/dm/public/img/wrAbout/img_02_3.png deleted file mode 100644 index f5ba9d81e..000000000 Binary files a/apps/dm/public/img/wrAbout/img_02_3.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_03_1.png b/apps/dm/public/img/wrAbout/img_03_1.png deleted file mode 100644 index a7534ae68..000000000 Binary files a/apps/dm/public/img/wrAbout/img_03_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_03_2.png b/apps/dm/public/img/wrAbout/img_03_2.png deleted file mode 100644 index 6b4faca7d..000000000 Binary files a/apps/dm/public/img/wrAbout/img_03_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_04_1.png b/apps/dm/public/img/wrAbout/img_04_1.png deleted file mode 100644 index a160072bd..000000000 Binary files a/apps/dm/public/img/wrAbout/img_04_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_04_2.png b/apps/dm/public/img/wrAbout/img_04_2.png deleted file mode 100644 index 374fc55bf..000000000 Binary files a/apps/dm/public/img/wrAbout/img_04_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_05_1.png b/apps/dm/public/img/wrAbout/img_05_1.png deleted file mode 100644 index 1418fa92d..000000000 Binary files a/apps/dm/public/img/wrAbout/img_05_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_05_2.png b/apps/dm/public/img/wrAbout/img_05_2.png deleted file mode 100644 index 0c2f92396..000000000 Binary files a/apps/dm/public/img/wrAbout/img_05_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_06_1.png b/apps/dm/public/img/wrAbout/img_06_1.png deleted file mode 100644 index 08acf4971..000000000 Binary files a/apps/dm/public/img/wrAbout/img_06_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrAbout/img_06_2.png b/apps/dm/public/img/wrAbout/img_06_2.png deleted file mode 100644 index 19b150b7c..000000000 Binary files a/apps/dm/public/img/wrAbout/img_06_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/icon_01.png b/apps/dm/public/img/wrExample/icon_01.png deleted file mode 100644 index 30eff5745..000000000 Binary files a/apps/dm/public/img/wrExample/icon_01.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/icon_02.png b/apps/dm/public/img/wrExample/icon_02.png deleted file mode 100644 index 36401729f..000000000 Binary files a/apps/dm/public/img/wrExample/icon_02.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/icon_03.png b/apps/dm/public/img/wrExample/icon_03.png deleted file mode 100644 index 87f48c444..000000000 Binary files a/apps/dm/public/img/wrExample/icon_03.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_01_1.png b/apps/dm/public/img/wrExample/img_01_1.png deleted file mode 100644 index 9aab4e824..000000000 Binary files a/apps/dm/public/img/wrExample/img_01_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_02_1.png b/apps/dm/public/img/wrExample/img_02_1.png deleted file mode 100644 index 3d61c6e5d..000000000 Binary files a/apps/dm/public/img/wrExample/img_02_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_02_2.png b/apps/dm/public/img/wrExample/img_02_2.png deleted file mode 100644 index c8283053e..000000000 Binary files a/apps/dm/public/img/wrExample/img_02_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_02_3.png b/apps/dm/public/img/wrExample/img_02_3.png deleted file mode 100644 index f5ba9d81e..000000000 Binary files a/apps/dm/public/img/wrExample/img_02_3.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_03_1.png b/apps/dm/public/img/wrExample/img_03_1.png deleted file mode 100644 index a7534ae68..000000000 Binary files a/apps/dm/public/img/wrExample/img_03_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_03_2.png b/apps/dm/public/img/wrExample/img_03_2.png deleted file mode 100644 index 6b4faca7d..000000000 Binary files a/apps/dm/public/img/wrExample/img_03_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_04_1.png b/apps/dm/public/img/wrExample/img_04_1.png deleted file mode 100644 index a160072bd..000000000 Binary files a/apps/dm/public/img/wrExample/img_04_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_04_2.png b/apps/dm/public/img/wrExample/img_04_2.png deleted file mode 100644 index 374fc55bf..000000000 Binary files a/apps/dm/public/img/wrExample/img_04_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_05_1.png b/apps/dm/public/img/wrExample/img_05_1.png deleted file mode 100644 index 1418fa92d..000000000 Binary files a/apps/dm/public/img/wrExample/img_05_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_05_2.png b/apps/dm/public/img/wrExample/img_05_2.png deleted file mode 100644 index 0c2f92396..000000000 Binary files a/apps/dm/public/img/wrExample/img_05_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_06_1.png b/apps/dm/public/img/wrExample/img_06_1.png deleted file mode 100644 index 08acf4971..000000000 Binary files a/apps/dm/public/img/wrExample/img_06_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_06_2.png b/apps/dm/public/img/wrExample/img_06_2.png deleted file mode 100644 index 19b150b7c..000000000 Binary files a/apps/dm/public/img/wrExample/img_06_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_arrow_down_1.png b/apps/dm/public/img/wrExample/img_arrow_down_1.png deleted file mode 100644 index 9e8fb76c6..000000000 Binary files a/apps/dm/public/img/wrExample/img_arrow_down_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_arrow_down_2.png b/apps/dm/public/img/wrExample/img_arrow_down_2.png deleted file mode 100644 index 2002bb607..000000000 Binary files a/apps/dm/public/img/wrExample/img_arrow_down_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_01_1.png b/apps/dm/public/img/wrExample/img_ex_01_1.png deleted file mode 100644 index 9d6bfb002..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_01_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_02_1.png b/apps/dm/public/img/wrExample/img_ex_02_1.png deleted file mode 100644 index 70c1f8ed2..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_02_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_03_1.png b/apps/dm/public/img/wrExample/img_ex_03_1.png deleted file mode 100644 index c981fa9fb..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_03_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_04_1.png b/apps/dm/public/img/wrExample/img_ex_04_1.png deleted file mode 100644 index f5547820e..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_04_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_04_2.png b/apps/dm/public/img/wrExample/img_ex_04_2.png deleted file mode 100644 index b102942ee..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_04_2.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_04_3.png b/apps/dm/public/img/wrExample/img_ex_04_3.png deleted file mode 100644 index 50ca8045f..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_04_3.png and /dev/null differ diff --git a/apps/dm/public/img/wrExample/img_ex_04_4.png b/apps/dm/public/img/wrExample/img_ex_04_4.png deleted file mode 100644 index 6e6d0c294..000000000 Binary files a/apps/dm/public/img/wrExample/img_ex_04_4.png and /dev/null differ diff --git a/apps/dm/public/img/wrOverview/img_ov_01_1.png b/apps/dm/public/img/wrOverview/img_ov_01_1.png deleted file mode 100644 index e59a10192..000000000 Binary files a/apps/dm/public/img/wrOverview/img_ov_01_1.png and /dev/null differ diff --git a/apps/dm/public/img/wrStatus/icon_favorit_off.png b/apps/dm/public/img/wrStatus/icon_favorit_off.png deleted file mode 100644 index 4de0b3ebc..000000000 Binary files a/apps/dm/public/img/wrStatus/icon_favorit_off.png and /dev/null differ diff --git a/apps/dm/public/img/wrStatus/icon_favorit_on.png b/apps/dm/public/img/wrStatus/icon_favorit_on.png deleted file mode 100644 index f2edfa4e9..000000000 Binary files a/apps/dm/public/img/wrStatus/icon_favorit_on.png and /dev/null differ diff --git a/apps/dm/public/img/wrStatus/icon_right.png b/apps/dm/public/img/wrStatus/icon_right.png deleted file mode 100644 index fef56226c..000000000 Binary files a/apps/dm/public/img/wrStatus/icon_right.png and /dev/null differ diff --git a/apps/dm/public/img/wrStatus/southKoreaMap.svg b/apps/dm/public/img/wrStatus/southKoreaMap.svg deleted file mode 100644 index 7b3c170e3..000000000 --- a/apps/dm/public/img/wrStatus/southKoreaMap.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/apps/dm/public/next.svg b/apps/dm/public/next.svg deleted file mode 100644 index 5174b28c5..000000000 --- a/apps/dm/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dm/public/vercel.svg b/apps/dm/public/vercel.svg deleted file mode 100644 index 770539603..000000000 --- a/apps/dm/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dm/public/window.svg b/apps/dm/public/window.svg deleted file mode 100644 index b2b2a44f6..000000000 --- a/apps/dm/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/dm/src/shared/lib/index.ts b/apps/dm/src/shared/lib/index.ts index a5679f1b5..c9cff1a80 100644 --- a/apps/dm/src/shared/lib/index.ts +++ b/apps/dm/src/shared/lib/index.ts @@ -2,7 +2,7 @@ export { getCookie, setCookie, removeCookie } from "./cookie"; // export { useMutationWrapper } from "./use-mutation-wrapper"; // export { useAlertStore } from "./use-alert-store"; // export { useDataTable } from "./use-data-table"; -// export { useMenuStore } from "./use-menu-store"; +export { useMenuStore } from "./use-menu-store"; // export { useUserStore } from "./use-user-store"; // export { usePrompt } from "./use-prompt"; // export { useSelectPrompt } from "./use-select-prompt"; diff --git a/apps/dm_new/src/shared/lib/use-menu-store.ts b/apps/dm/src/shared/lib/use-menu-store.ts similarity index 100% rename from apps/dm_new/src/shared/lib/use-menu-store.ts rename to apps/dm/src/shared/lib/use-menu-store.ts diff --git a/apps/dm_new/src/shared/providers/index.ts b/apps/dm/src/shared/providers/index.ts similarity index 100% rename from apps/dm_new/src/shared/providers/index.ts rename to apps/dm/src/shared/providers/index.ts diff --git a/apps/dm_new/src/shared/providers/prompt-provider.tsx b/apps/dm/src/shared/providers/prompt-provider.tsx similarity index 100% rename from apps/dm_new/src/shared/providers/prompt-provider.tsx rename to apps/dm/src/shared/providers/prompt-provider.tsx diff --git a/apps/dm_new/src/shared/providers/store-provider.tsx b/apps/dm/src/shared/providers/store-provider.tsx similarity index 100% rename from apps/dm_new/src/shared/providers/store-provider.tsx rename to apps/dm/src/shared/providers/store-provider.tsx diff --git a/apps/dm_new/src/shared/store/alert-store.ts b/apps/dm/src/shared/store/alert-store.ts similarity index 100% rename from apps/dm_new/src/shared/store/alert-store.ts rename to apps/dm/src/shared/store/alert-store.ts diff --git a/apps/dm_new/src/shared/store/attachment-store.ts b/apps/dm/src/shared/store/attachment-store.ts similarity index 100% rename from apps/dm_new/src/shared/store/attachment-store.ts rename to apps/dm/src/shared/store/attachment-store.ts diff --git a/apps/dm_new/src/shared/store/index.ts b/apps/dm/src/shared/store/index.ts similarity index 100% rename from apps/dm_new/src/shared/store/index.ts rename to apps/dm/src/shared/store/index.ts diff --git a/apps/dm_new/src/shared/store/menu-store.ts b/apps/dm/src/shared/store/menu-store.ts similarity index 100% rename from apps/dm_new/src/shared/store/menu-store.ts rename to apps/dm/src/shared/store/menu-store.ts diff --git a/apps/dm_new/src/shared/store/user-store.ts b/apps/dm/src/shared/store/user-store.ts similarity index 100% rename from apps/dm_new/src/shared/store/user-store.ts rename to apps/dm/src/shared/store/user-store.ts diff --git a/apps/dm/tsconfig.json b/apps/dm/tsconfig.json index 493584782..ba8cd2c2f 100644 --- a/apps/dm/tsconfig.json +++ b/apps/dm/tsconfig.json @@ -40,6 +40,7 @@ "include": [ ".eslintrc.*", "next-env.d.ts", + "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.mts", diff --git a/apps/dm_new/types/style.d.ts b/apps/dm/types/style.d.ts similarity index 100% rename from apps/dm_new/types/style.d.ts rename to apps/dm/types/style.d.ts diff --git a/apps/dm_new/.env.local b/apps/dm_new/.env.local deleted file mode 100644 index 58001643c..000000000 --- a/apps/dm_new/.env.local +++ /dev/null @@ -1,26 +0,0 @@ -NEXT_PUBLIC_SITE_ID = DN -NEXT_PUBLIC_PORT = 3015 -NEXT_PUBLIC_CLAIM_NAME = Authorization -NEXT_PUBLIC_AUTH_USER_ID = token-id -NEXT_PUBLIC_ACCESS_TOKEN = access-token -NEXT_PUBLIC_REFRESH_TOKEN = refresh-token -NEXT_PUBLIC_PROXY_HOST = http://localhost:3005 -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login -NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -# NEXT_PUBLIC_FO_SERVER_API_URL = http://192.168.1.222:8883/operation-service/api/v1 -NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver -NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload -NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr -NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API -#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 -#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 - - -NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do -NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm_new/.env.mindone b/apps/dm_new/.env.mindone deleted file mode 100644 index 9a5005d2b..000000000 --- a/apps/dm_new/.env.mindone +++ /dev/null @@ -1,26 +0,0 @@ -NEXT_PUBLIC_SITE_ID = DN -NEXT_PUBLIC_PORT = 11315 -NEXT_PUBLIC_CLAIM_NAME = Authorization -NEXT_PUBLIC_AUTH_USER_ID = token-id -NEXT_PUBLIC_ACCESS_TOKEN = access-token -NEXT_PUBLIC_REFRESH_TOKEN = refresh-token -NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11315 -NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login -NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 -NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver -#NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload -NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr -NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API -NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API -# NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 -#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 - - -NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do -NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM= diff --git a/apps/dm_new/.env.production b/apps/dm_new/.env.production deleted file mode 100644 index b413d1312..000000000 --- a/apps/dm_new/.env.production +++ /dev/null @@ -1,24 +0,0 @@ -# 공단 운영 -NEXT_PUBLIC_SITE_ID = DN -NEXT_PUBLIC_PORT = 3000 -NEXT_PUBLIC_CLAIM_NAME = Authorization -NEXT_PUBLIC_AUTH_USER_ID = token-id -NEXT_PUBLIC_ACCESS_TOKEN = access-token -NEXT_PUBLIC_REFRESH_TOKEN = refresh-token -#NEXT_PUBLIC_PROXY_HOST = https://devreuse.hasuon.or.kr -#NEXT_PUBLIC_CM_LOGIN_PAGE_URL = https://devqueen.hasuon.or.kr/auth/login -NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 -NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 -NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service/api/v1 -NEXT_PUBLIC_WR_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1 -NEXT_PUBLIC_GEOSERVER_URL = http://172.29.143.87:8080/geoserver -NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl -NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload -NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service -NEXT_PUBLIC_NGII_URL = http://172.29.143.87/poiact -NEXT_PUBLIC_NGII_PARAM = BDE2BEF39DAFEC293AB6730150F222AC&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={y}&tilerow={x} -NEXT_PUBLIC_GATEWAY_URL = http://apigateway.iipbs-was.svc.cluster.local:8000 - - -NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do -NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA= diff --git a/apps/dm_new/AGENTS.md b/apps/dm_new/AGENTS.md deleted file mode 100644 index 7eefb1676..000000000 --- a/apps/dm_new/AGENTS.md +++ /dev/null @@ -1,38 +0,0 @@ -## Project -This repository contains a Next.js web application. - -Main technologies: -- Next.js -- TypeScript -- pnpm workspace -- turborepo - -이 저장소는 pnpm workspace + Turborepo 기반 monorepo이다. - -- 워크스페이스 범위는 `apps/*`, `apps/common/*`, `packages/*`, `packages/common/*` -- 루트 스크립트는 `turbo run build`, `turbo run dev` 를 사용한다 -- `build` 작업은 의존 패키지의 `build` 를 먼저 수행한다 (`dependsOn: ["^build"]`) -- `dev` 는 캐시하지 않는 지속 실행 작업이다 -- 변경 범위만 선택 실행할 수 있으므로, 전체 빌드보다 `--filter` 사용을 우선한다 - -## Encoding -- UTF-8 -- 한글이 깨지지 않도록 주의할 것 - -## 주석 comment -- 한글 주석을 삭제하지 말 것 - -## Monorepo structure -apps/ - _template - as - cm - cs - dc - dm - dm_new - fo - sn - st - wr - diff --git a/apps/dm_new/app/(with-layout)/apply/register/page.tsx b/apps/dm_new/app/(with-layout)/apply/register/page.tsx deleted file mode 100644 index 3c084bf73..000000000 --- a/apps/dm_new/app/(with-layout)/apply/register/page.tsx +++ /dev/null @@ -1,500 +0,0 @@ -// app/(with-layout)/apply/register/page.tsx -/** - * 수요정보등록(수정) 화면 - * - * 🔗 사용 API - * - 수요정보등록: POST /reuseFc/request/demandInfo - * - 수요정보조회: GET /reuseFc/request/' + dmdInfoId - getSdmInfoStatusList - */ -"use client"; - -import { useMemo, useState, useRef } from "react"; -import SubPageHeader from "@/components/SubPageHeader"; -import WaterQualityRefModal from "../modal/WaterQualityRefModal"; -import CandidateSiteModal, { type CandidateSite } from "@/components/CandidateSiteModal"; - -type PHRange = { min: string; max: string }; - -export default function DemandRegisterPage() { - const today = useMemo(() => { - const d = new Date(); - const yyyy = d.getFullYear(); - const mm = String(d.getMonth() + 1).padStart(2, "0"); - const dd = String(d.getDate()).padStart(2, "0"); - return `${yyyy}-${mm}-${dd}`; - }, []); - - const [openRef, setOpenRef] = useState(false); - - const [openCand, setOpenCand] = useState(false); - const [selectedCands, setSelectedCands] = useState([]); - - // 기본정보 - const [reqDate, setReqDate] = useState(today); - const [reqQty, setReqQty] = useState(""); - const [usePurpose, setUsePurpose] = useState(""); - const [hopePrice, setHopePrice] = useState(""); - - // 요구수질 - const [coliform, setColiform] = useState(""); - const [noDetect, setNoDetect] = useState(false); - - const [tn, setTn] = useState(""); - const [residualCl, setResidualCl] = useState(""); - const [tp, setTp] = useState(""); - const [turbidity, setTurbidity] = useState(""); - const [ph, setPh] = useState({ min: "", max: "" }); - const [bod, setBod] = useState(""); - const [chloride, setChloride] = useState(""); - const [odor, setOdor] = useState("불쾌하지 않을 것"); - const [ec, setEc] = useState(""); - const [color, setColor] = useState(""); - const [remark, setRemark] = useState(""); - - // 공급처 후보지 선택(필수아님) - const [cand1, setCand1] = useState(""); - const [cand2, setCand2] = useState(""); - const [cand3, setCand3] = useState(""); - - return ( - <> - -
- - {/* 서브 타이틀 + 브레드크럼 */} - - - {/* 컨텐츠 영역 */} -
- - {/* 기본정보 테이블 */} -
- - - - - - - - - - - - - - - - - - - - - -
- - {/* 요구수질 섹션 타이틀 바 */} -
-
- 요구수질 - - {/* 수질기준참고 모달 */} - setOpenRef(false)} - imageSrc="/images/landing/water_quality_ref_1.png" - /> -
-
- - {/* 요구수질 테이블 */} -
- {/* 1행 */} - - - -
- } - rightLabel="총질소(T-N)(mg/L)" - rightControl={ - - } - /> - - - {/* 2행 */} - } - rightLabel="총인(T-P)(mg/L)" - rightControl={ - - } - /> - - - {/* 3행 */} - - } - rightLabel="수소이온농도(pH)" - rightControl={ -
- setPh((p) => ({ ...p, min: v }))} - className="w-32" - /> - ~ - setPh((p) => ({ ...p, max: v }))} - className="w-32" - /> -
- } - /> - - - {/* 4행 */} - - 생물화학적산소요구량 -
- (BOD)(mg/L) - - } - leftRequired - leftControl={} - rightLabel="염화물(mgCL/L)" - rightControl={} - /> - - - {/* 5행 */} - setOdor(e.target.value)} - className="h-11 w-full max-w-[200px] rounded-md border border-neutral-300 bg-white px-3 text-sm outline-none focus:border-neutral-500" - > - - - - - } - rightLabel="전기전도도(µs/cm)" - rightControl={} - /> - - - {/* 6행 */} - } - rightLabel="비고" - rightControl={ -