+
+ {/* 배너 영역 */}
+
- {/* 세번째 라인 */}
-
-
- {/* 네번째 라인 */}
-
@@ -139,4 +300,4 @@ function DashboardPage() {
);
}
-export { DashboardPage };
\ No newline at end of file
+export { DashboardPage };
diff --git a/apps/cm/src/shared/config/api.ts b/apps/cm/src/shared/config/api.ts
index 36dfc69a3..25c8b88c9 100644
--- a/apps/cm/src/shared/config/api.ts
+++ b/apps/cm/src/shared/config/api.ts
@@ -69,6 +69,17 @@ export const ENDPOINT = {
POSTS_COMMENTS: "/board-service/api/v1/posts-cmnts",
POST_EVALUATION: "/board-service/api/v1/pstEvl",
},
+ SEWERAGE_SERVICE: {
+ PPL_COUNTS: "/sewerage-service/api/v1/ms/pplCounts",
+ OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts",
+ },
+ IC_SERVICE: {
+ GROUPING_STATISTICS: "/ic-service/api/v1/nti/grouping-statistics",
+ },
+ DC_SERVICE: {
+ DATA_STATUS: "/dc-service/api/v1/dc/dataStatus",
+ },
+
};
export const JWT_EXPIRATION_TIME = 1800000;
diff --git a/apps/dm/.env.development b/apps/dm/.env.development
index 7a7dc3f52..833207ed0 100644
--- a/apps/dm/.env.development
+++ b/apps/dm/.env.development
@@ -1,22 +1,22 @@
+# 공단 개발
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:11080/api/v1
-NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
-NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
+NEXT_PUBLIC_PORT = 3000
+NEXT_PUBLIC_PROXY_HOST = https://devreuse.hasuon.or.kr
+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_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
+NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
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_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1
-NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
-NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 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_REUSE_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
+NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
+NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
NEXT_PUBLIC_FINDADRESS_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
-NEXT_PRIVATE_WR_ADDRESS_KEY = devU01TX0FVVEgyMDI1MDgyNTEwMzkwNDExNjEwNTM=
+NEXT_PRIVATE_WR_ADDRESS_KEY = U01TXOFVVEgyMDI1MDgyODE0MTAxNzExNjEyMjA=
diff --git a/apps/dm/.env.local b/apps/dm/.env.local
index 3c390c8bd..c43f5af55 100644
--- a/apps/dm/.env.local
+++ b/apps/dm/.env.local
@@ -5,7 +5,7 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_PORT = 3005
#NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11035
-NEXT_PUBLIC_PROXY_HOST = http://localhost:3005/
+NEXT_PUBLIC_PROXY_HOST = http://localhost:3005
NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
diff --git a/apps/dm/.env.mindone b/apps/dm/.env.mindone
new file mode 100644
index 000000000..7a7dc3f52
--- /dev/null
+++ b/apps/dm/.env.mindone
@@ -0,0 +1,22 @@
+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:11080/api/v1
+NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11082/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_CLAIM_NAME = Authorization
+NEXT_PUBLIC_AUTH_USER_ID = token-id
+NEXT_PUBLIC_ACCESS_TOKEN = access-token
+NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
+NEXT_PUBLIC_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1
+NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
+NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 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/.env.production b/apps/dm/.env.production
index b9d81ccc4..e627195cb 100644
--- a/apps/dm/.env.production
+++ b/apps/dm/.env.production
@@ -1,9 +1,10 @@
+# 공단 운영
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://apigateway.iipbs-dev.svc.cluster.local:8000/api/v1
-NEXT_PUBLIC_FO_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/operation-service/api/v1
-NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000
+NEXT_PUBLIC_PORT = 3000
+NEXT_PUBLIC_PROXY_HOST = https://reuse.hasuon.or.kr
+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_GEOSERVER_URL = http://172.29.143.87:8080/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://172.29.143.87:8080/geoserver_swl
@@ -11,9 +12,9 @@ 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_REUSE_API_URL=http://con.mind-one.co.kr:11080/api/v1
+NEXT_PUBLIC_REUSE_API_URL= http://apigateway.iipbs-was.svc.cluster.local:8000/reuse-service/api/v1
NEXT_PUBLIC_NGII_URL = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
-NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000/sewerage-service/api
+NEXT_PUBLIC_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
diff --git a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx
index f5acfdb97..ce38a1226 100644
--- a/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx
+++ b/apps/dm/app/(base-layout)/pages/wrStatus/page.tsx
@@ -5,7 +5,9 @@ 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;
diff --git a/apps/dm/app/(popup-layout)/map/service/map.ts b/apps/dm/app/(popup-layout)/map/service/map.ts
index aa98a0bc2..9c10ca190 100644
--- a/apps/dm/app/(popup-layout)/map/service/map.ts
+++ b/apps/dm/app/(popup-layout)/map/service/map.ts
@@ -1,9 +1,7 @@
-
/**
* 관망도 API
*/
- const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
- const REUSE_API_URL = `${process.env.NEXT_PUBLIC_REUSE_API_URL}`
+import { DEV_URL_WR, DEV_URL_SN } from "@/components/ui/config";
export type LyrStngProps = {
lyr_sn: string
@@ -31,7 +29,7 @@ export type LyrStngProps = {
export const mapService = {
//레이어 설정 조회
getLyrStngs: async () => {
- const response = await fetch(`${API_URL}/lyrStngs/DM_NETWORK`, {
+ const response = await fetch(`${DEV_URL_SN}/app/v1/map/lyrStngs/DM_NETWORK`, {
method: 'GET',
headers: {
'Accept': 'application/json',
@@ -45,7 +43,7 @@ export const mapService = {
return response.json();
},
getPbnstRuseFclts: async (ruseFcltPk:any) => {
- const response = await fetch(`${REUSE_API_URL}/reuseFc/pubFc/${ruseFcltPk}`, {
+ const response = await fetch(`${DEV_URL_WR}/api/v1/reuseFc/pubFc/${ruseFcltPk}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
diff --git a/apps/dm/app/middleware.ts b/apps/dm/app/middleware.ts
deleted file mode 100644
index ad35dfba7..000000000
--- a/apps/dm/app/middleware.ts
+++ /dev/null
@@ -1,129 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-
-const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
-const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
-const SERVER_API_URL = process.env.NEXT_PUBLIC_DM_SERVER_API_URL || "";
-const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
-const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
-const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
-const DC_URL = process.env.NEXT_PUBLIC_DC_URL || "";
-
-export function middleware(req: NextRequest) {
- console.log("Middleware 진입:", req.nextUrl.pathname);
- const pathname = req.nextUrl.pathname;
- const searchParams = req.nextUrl.searchParams;
-
- if (pathname.startsWith("/server")) {
-
- const decodedParams = Array.from(searchParams.entries())
- .map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
- .join("&");
-
- const newUrl = `${SERVER_API_URL}${pathname.replace("/server", "")}${
- decodedParams ? `?${decodedParams}` : ""
- }`;
- return NextResponse.rewrite(new URL(newUrl));
- }
-
- if (pathname.startsWith("/geoserver")) {
- let queryParams = searchParams.toString();
- const typename = searchParams.get("TYPENAME");
-
- let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
-
- if (
- typename == "hasudo:tb_il_swage_prcs_fclt" //하수처리시설
- || typename == "hasudo:tb_ms_swage_prcs_zone" //하수처리구역
- || typename == "hasudo:tb_ms_sbmrg_dngr_map" //침수위험지도
- || typename == "hasudo:tb_ms_flood_dngr_map" //도시침수지도
- ) {
- newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
- }
- else if (
- typename == "hasudo:tb_ms_swage_ppl" //관로
- || typename == "hasudo:tb_ms_swage_mhl" //맨홀
- || typename == "hasudo:tb_ms_swage_pmstn" //펌프장
- || typename == "hasudo:tb_ms_stofcbr" //우수토실
- ) {
- newUrl = `${LX_GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`;
- }
- else if (
- typename == "hasudo:om_dt_untrt_swg_rain" //중점우수토실
- || typename == "hasudo:om_wqlt_station" //관측소
- || typename == "hasudo:tb_wr_bdst_ruse_fclt" //재이용시설
- || typename == "hasudo:tb_wr_rnwtr_utztn_fclt" //빗물이용시설
- || typename == "hasudo:tb_wr_rswtr_fclt" //중수도시설
- || typename == "hasudo:tb_wr_htdfl_fclt" //온배수시설
- || typename == "hasudo:tb_wr_ruse_fclt_dmd_co" //수요처
- || typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처
- ){
- const url = req.nextUrl.clone();
- url.searchParams.set('TYPENAME', typename.replace("hasudo:",""));
- queryParams = url.searchParams.toString();
- newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`;
- }
-
- console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
- return NextResponse.rewrite(new URL(newUrl));
- }
-
- if (
- pathname.startsWith("/attach-service") ||
- pathname.startsWith("/cms-service")
- ) {
- const requestHeaders = new Headers(req.headers);
- const token = req.cookies.get(CLAIM_NAME)?.value;
- const userId = req.cookies.get(AUTH_USER_ID)?.value;
- const newUrl = `${SERVER_API_URL}${pathname}`;
-
- if (token) requestHeaders.set(CLAIM_NAME, token);
- if (userId) requestHeaders.set(AUTH_USER_ID, userId);
-
- console.log(
- `[Middleware] Rewriting from '/attach-service' path to: ${newUrl}`,
- );
- return NextResponse.rewrite(new URL(newUrl), {
- headers: requestHeaders,
- });
- }
-
- if (pathname.startsWith("/user-service")) {
- const requestHeaders = new Headers(req.headers);
- const newUrl = `${SERVER_API_URL}${pathname}`;
-
- console.log(
- `[Middleware] Rewriting from '/user-service' path to: ${newUrl}`,
- );
- return NextResponse.rewrite(new URL(newUrl), {
- headers: requestHeaders,
- });
- }
-
- if (pathname.startsWith("/dc-service")) {
- const requestHeaders = new Headers(req.headers);
- const decodedParams = Array.from(searchParams.entries())
- .map(([key, value]) => `${key}=${decodeURIComponent(value)}`)
- .join("&");
-
- const newUrl = `${DC_URL}${pathname}${decodedParams ? `?${decodedParams}` : ""}`;
- console.log(
- `[Middleware] Rewriting from '/dc-service' path to: ${newUrl}`,
- );
- return NextResponse.rewrite(new URL(newUrl), {
- headers: requestHeaders,
- });
- }
- return NextResponse.next();
-}
-
-export const config = {
- matcher: [
- "/server/:path*",
- "/geoserver/:path*",
- "/attach-service/:path*",
- "/user-service/:path*",
- "/cms-service/:path*",
- "/dc-service/:path*",
- ],
-
-};
diff --git a/apps/dm/app/store/map/index.tsx b/apps/dm/app/store/map/index.tsx
index 5ed066f73..f5b44c891 100644
--- a/apps/dm/app/store/map/index.tsx
+++ b/apps/dm/app/store/map/index.tsx
@@ -1,7 +1,12 @@
+import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config";
import { fetchType, getFetch } from "@/components/ui/api";
import { DEV_URL_FO } from "@/components/ui/config";
+import { getCookie } from "@common/components/lib/cookie";
import type { FacilityInfo, SubsInfo } from "./type";
+const tokenId = getCookie(AUTH_USER_ID);
+const Authorization = getCookie(CLAIM_NAME);
+
const checkWrRegList = async () => {
const wrRegList = await getFetch(fetchType.WR, `/reuseFc/pubFc`);
return wrRegList;
@@ -26,6 +31,8 @@ const getRuseFclt = async (
method: "POST",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(ruseList),
});
diff --git a/apps/dm/components/ui/api/index.ts b/apps/dm/components/ui/api/index.ts
index 28644b00e..7ba66c4ed 100644
--- a/apps/dm/components/ui/api/index.ts
+++ b/apps/dm/components/ui/api/index.ts
@@ -1,5 +1,10 @@
//8880
+import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config";
import { DEV_URL_FO, DEV_URL_WR } from "../config";
+import { getCookie } from "@common/components/lib/cookie";
+
+const tokenId = getCookie(AUTH_USER_ID);
+const Authorization = getCookie(CLAIM_NAME);
export const fetchType = {
COMMON: "common",
@@ -17,6 +22,8 @@ export const getFetch = async (type: string, url: string) => {
method: "GET",
headers: {
accept: "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
});
@@ -32,6 +39,8 @@ export const postFetch = async (url: string, params: RequestParams) => {
method: "POST",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
@@ -47,6 +56,8 @@ export const putFetch = async (url: string, params: RequestParams) => {
method: "PUT",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
@@ -62,6 +73,8 @@ export const deleteFetch = async (url: string, params: RequestParams) => {
method: "DELETE",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
diff --git a/apps/dm/components/ui/config/index.ts b/apps/dm/components/ui/config/index.ts
index 2ed4ef03d..1585bb767 100644
--- a/apps/dm/components/ui/config/index.ts
+++ b/apps/dm/components/ui/config/index.ts
@@ -1,5 +1,6 @@
-import { PROXY_HOST, SERVER_API_FO_URL, SERVER_API_WR_URL } from "@/src/shared/config/env";
+import { PROXY_HOST } from "@/src/shared/config/env";
export const LOCAL_URL = PROXY_HOST;
-export const DEV_URL_FO = SERVER_API_FO_URL;
-export const DEV_URL_WR = SERVER_API_WR_URL;
+export const DEV_URL_FO = "/operation-service";
+export const DEV_URL_WR = "/reuse-service";
+export const DEV_URL_SN = "/sewerage-service";
diff --git a/apps/dm/middleware.ts b/apps/dm/middleware.ts
index 4ac2e3d99..8616a6e80 100644
--- a/apps/dm/middleware.ts
+++ b/apps/dm/middleware.ts
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config";
+import { SERVER_API_FO_URL, SERVER_API_WR_URL } from "./src/shared/config/env";
// const CLAIM_NAME = "Authorization";
// const AUTH_USER_ID = "token-id";
@@ -115,12 +116,30 @@ export function middleware(req: NextRequest) {
return NextResponse.rewrite(new URL(newUrl));
}
- if (pathname.startsWith("/sewerage-service")) {
+ if (pathname.startsWith("/reuse-service")) {
const queryParams = searchParams.toString();
- const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${
+ const newUrl = `${SERVER_API_WR_URL}${pathname.replace("/reuse-service", "")}${
queryParams ? `?${queryParams}` : ""
}`;
- console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
+ console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`);
+ return NextResponse.rewrite(new URL(newUrl));
+ }
+
+ if (pathname.startsWith("/operation-service")) {
+ const queryParams = searchParams.toString();
+ const newUrl = `${SERVER_API_FO_URL}${pathname.replace("/operation-service", "")}${
+ queryParams ? `?${queryParams}` : ""
+ }`;
+ console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`);
+ return NextResponse.rewrite(new URL(newUrl));
+ }
+
+ if (pathname.startsWith("/sewerage-service")) {
+ const queryParams = searchParams.toString();
+ const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service", "")}${
+ queryParams ? `?${queryParams}` : ""
+ }`;
+ console.log(`[Middleware] Rewriting from '/sewerage-service' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
if (pathname.startsWith("/bussiness-juso-service")) {
@@ -150,6 +169,8 @@ export const config = {
"/user-service/:path*",
"/attach-service/:path*",
"/geoserver/:path*",
+ "/operation-service/:path*",
+ "/reuse-service/:path*",
"/sewerage-service/:path*",
"/bussiness-juso-service/:path*",
"/",
diff --git a/apps/dm/next.config.ts b/apps/dm/next.config.ts
index 48fc8dacb..bc21e16a4 100644
--- a/apps/dm/next.config.ts
+++ b/apps/dm/next.config.ts
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const subDomain =
- process.env.NEXT_PUBLIC_SUB_DOMAIN || "http://localhost:3000";
+ process.env.NEXT_PUBLIC_PROXY_HOST || "http://localhost:3000";
const nextConfig: NextConfig = {
output: "standalone",
@@ -22,8 +22,16 @@ const nextConfig: NextConfig = {
],
afterFiles: [
{
- source: "/pst/:path*",
- destination: `${subDomain}/pst/:path*`,
+ source: "/sewerage-service/:path*",
+ destination: `${subDomain}/sewerage-service/:path*`,
+ },
+ {
+ source: "/reuse-service/:path*",
+ destination: `${subDomain}/reuse-service/:path*`,
+ },
+ {
+ source: "/operation-service/:path*",
+ destination: `${subDomain}/operation-service/:path*`,
},
],
fallback: [],
diff --git a/apps/dm/src/shared/api/client.ts b/apps/dm/src/shared/api/client.ts
new file mode 100644
index 000000000..cf37e6671
--- /dev/null
+++ b/apps/dm/src/shared/api/client.ts
@@ -0,0 +1,11 @@
+import { Client } from "@common/network";
+import { BASE_URL } from "../config/api";
+import { SITE_ID } from "../config/env";
+import { setCookie } from "../lib";
+
+setCookie("site-id-key", SITE_ID);
+
+export const client = Client.getInstance({
+ baseURL: BASE_URL,
+ siteId: SITE_ID,
+});
diff --git a/apps/dm/src/shared/api/index.ts b/apps/dm/src/shared/api/index.ts
new file mode 100644
index 000000000..b7b9d6121
--- /dev/null
+++ b/apps/dm/src/shared/api/index.ts
@@ -0,0 +1 @@
+export { client } from "./client";
diff --git a/apps/dm/src/shared/lib/index.ts b/apps/dm/src/shared/lib/index.ts
new file mode 100644
index 000000000..a5679f1b5
--- /dev/null
+++ b/apps/dm/src/shared/lib/index.ts
@@ -0,0 +1,8 @@
+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 { useUserStore } from "./use-user-store";
+// export { usePrompt } from "./use-prompt";
+// export { useSelectPrompt } from "./use-select-prompt";
diff --git a/apps/fo/.env.development b/apps/fo/.env.development
index de81dde66..ffaf99feb 100644
--- a/apps/fo/.env.development
+++ b/apps/fo/.env.development
@@ -1,18 +1,17 @@
+# 공단 개발
NEXT_PUBLIC_SITE_ID = FO
-NEXT_PUBLIC_PORT = 11300
-NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
-NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
-#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
-NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
-NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
-#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
-NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
-NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
+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_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
-NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
-
-
+NEXT_PUBLIC_PROXY_HOST = https://devlarva.hasuon.or.kr
+NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service
+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_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_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
+NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
\ No newline at end of file
diff --git a/apps/fo/.env.local b/apps/fo/.env.local
index 2e937af0c..1838377df 100644
--- a/apps/fo/.env.local
+++ b/apps/fo/.env.local
@@ -1,18 +1,19 @@
NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 3000
-NEXT_PUBLIC_PROXY_HOST = http://localhost:3000
-NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
-#NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
-NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
-NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
-#NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
-NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
-NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
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_DEV_CM_LOGIN_PAGE_URL = http://localhost:3001/auth/login
+NEXT_PUBLIC_VDI_DEV_CM_LOGIN_PAGE_URL = http://localhost:3001/auth/login
+NEXT_PUBLIC_VDI_DEV_CM_LOGIN_PAGE_URL = http://localhost:3001/auth/login
+NEXT_PUBLIC_PROXY_HOST = http://localhost:3000
+NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082
+NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
+NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
+NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
+NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
-
-
+NEXT_PUBLIC_DC_URL = https://apigateway.apps.rsonesoft.com/dc-service
+REACT_EDITOR=atom
\ No newline at end of file
diff --git a/apps/fo/.env.mindone b/apps/fo/.env.mindone
new file mode 100644
index 000000000..74d765a48
--- /dev/null
+++ b/apps/fo/.env.mindone
@@ -0,0 +1,14 @@
+NEXT_PUBLIC_SITE_ID = FO
+NEXT_PUBLIC_PORT = 11300
+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:11300
+NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
+NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
+NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
+NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
+NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
+NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g
+NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api
\ No newline at end of file
diff --git a/apps/fo/.env.production b/apps/fo/.env.production
index a1da344b8..3f5cf8bea 100644
--- a/apps/fo/.env.production
+++ b/apps/fo/.env.production
@@ -1,32 +1,17 @@
-# NEXT_PUBLIC_SITE_ID = FO
-# NEXT_PUBLIC_PORT = 11300
-# NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11300
-# NEXT_PUBLIC_SERVER_API_URL = http://con.mind-one.co.kr:11082/api/v1
-# #NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000
-# NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11080/api/v1
-# NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000
-# 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_CLAIM_NAME = Authorization
-# NEXT_PUBLIC_AUTH_USER_ID = token-id
-# NEXT_PUBLIC_ACCESS_TOKEN = access-token
-# NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
-# NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
-
+# 공단 운영
NEXT_PUBLIC_SITE_ID = FO
NEXT_PUBLIC_PORT = 3000
-NEXT_PUBLIC_PROXY_HOST = https://fo.hasuon.or.kr
-# NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000
-NEXT_PUBLIC_SERVER_API_URL = https://fo.hasuon.or.kr/operation-service/api/v1
-NEXT_PUBLIC_FO_SERVER_API_URL = https://apigateway.hasuon.or.kr/operation-service/api/v1
-NEXT_PUBLIC_CS_SERVER_API_URL = http://apigateway.iipbs-dev.svc.cluster.local:8000
-NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload
-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_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
-NEXT_PUBLIC_WR_SERVER_API_URL = https://fo.hasuon.or.kr/reuse-service/api/v1
-
-NEXT_PUBLIC_SEWERAGE_API_URL = https://fo.hasuon.or.kr/sewerage-service/api
\ No newline at end of file
+NEXT_PUBLIC_PROXY_HOST = https://larva.hasuon.or.kr
+NEXT_PUBLIC_SERVER_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/operation-service
+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_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_SEWERAGE_API_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/sewerage-service/api
+NEXT_PUBLIC_DC_URL = http://apigateway.iipbs-was.svc.cluster.local:8000/dc-service
\ No newline at end of file
diff --git a/apps/fo/app/base.tsx b/apps/fo/app/base.tsx
deleted file mode 100644
index a01ce8643..000000000
--- a/apps/fo/app/base.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-"use client";
-
-import { BaseLayout } from "@common/components";
-import type { MenuItem } from "./store/common";
-import { useFoStore } from "./store";
-import { usePathname, useRouter } from "next/navigation";
-
-export default function Base({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
- const { getMenuAuth } = useFoStore((state) => state);
- const pathname = usePathname();
- const router = useRouter();
-
- const handleClick = (id: MenuItem) => {
- getMenuAuth(id.menuCd);
- router.push(id.urlPath);
- };
-
- return (
-
- {children}
-
- );
-}
diff --git a/apps/fo/app/components/api/config/env.ts b/apps/fo/app/components/api/config/env.ts
index d419bfd1c..1c0ba8b0e 100644
--- a/apps/fo/app/components/api/config/env.ts
+++ b/apps/fo/app/components/api/config/env.ts
@@ -1,5 +1,5 @@
-import { PROXY_HOST, SERVER_API_URL, SERVER_API_WR_URL } from "@/src/shared/config/env";
+import { PROXY_HOST } from "@/src/shared/config/env";
-export const DEV_URL_FO = SERVER_API_URL;
+export const DEV_URL_FO = "/operation-service/api/v1";
export const LOCAL_URL = PROXY_HOST;
-export const DEV_URL_WR = SERVER_API_WR_URL;
\ No newline at end of file
+export const DEV_URL_WR = "/reuse-service/api/v1";
\ No newline at end of file
diff --git a/apps/fo/app/components/api/index.ts b/apps/fo/app/components/api/index.ts
index 3bba85af2..ba4e9863e 100644
--- a/apps/fo/app/components/api/index.ts
+++ b/apps/fo/app/components/api/index.ts
@@ -1,12 +1,22 @@
-import { DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "./config/env";
+import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config";
+import { DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "./config/env";
+import { getCookie } from "@common/components/lib/cookie";
+
+const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || "";
+
+const tokenId = getCookie(AUTH_USER_ID);
+const Authorization = getCookie(CLAIM_NAME);
export const getFetch = async (part : string, url: string) => {
let reqUrl = "";
reqUrl = part === "fo" ? DEV_URL_FO+url : part === 'local' ? LOCAL_URL+url : DEV_URL_WR+url;
+ reqUrl = PROXY_HOST + reqUrl;
const response = await fetch(reqUrl , {
method: "GET",
headers: {
- accept: "application/json",
+ accept: "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
}
});
if(!response.ok) {
@@ -17,10 +27,12 @@ export const getFetch = async (part : string, url: string) => {
}
export const getFetchAreaList = async (url: string) => {
- const response = await fetch(url , {
+ const response = await fetch(PROXY_HOST+url , {
method: "GET",
headers: {
- accept: "application/json",
+ accept: "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
}
});
if(!response.ok) {
@@ -33,10 +45,13 @@ export const getFetchAreaList = async (url: string) => {
export const postFetch = async (part : string, url: string, params: unknown) => {
let reqUrl = "";
reqUrl = part === "fo" ? DEV_URL_FO+url : DEV_URL_WR+url;
+ reqUrl = PROXY_HOST + reqUrl;
const response = await fetch(reqUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
@@ -49,10 +64,13 @@ export const postFetch = async (part : string, url: string, params: unknown) =>
export const putFetch = async (part : string, url: string, params: unknown) => {
let reqUrl = "";
reqUrl = part === "fo" ? DEV_URL_FO+url : DEV_URL_WR+url;
+ reqUrl = PROXY_HOST + reqUrl;
const response = await fetch(reqUrl, {
method: "PUT",
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
@@ -65,10 +83,13 @@ export const putFetch = async (part : string, url: string, params: unknown) => {
export const deleteFetch = async (part : string, url: string, params: unknown) => {
let reqUrl = "";
reqUrl = part === "fo" ? DEV_URL_FO+url : DEV_URL_WR+url;
- const response = await fetch(reqUrl, {
+ reqUrl = PROXY_HOST + reqUrl;
+ const response = await fetch(reqUrl, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
+ [AUTH_USER_ID]: tokenId ?? "",
+ [CLAIM_NAME]: Authorization ?? "",
},
body: JSON.stringify(params),
});
diff --git a/apps/fo/app/layout.tsx b/apps/fo/app/layout.tsx
index 842ec5ed1..683e6d0ec 100644
--- a/apps/fo/app/layout.tsx
+++ b/apps/fo/app/layout.tsx
@@ -1,29 +1,14 @@
-"use client"
-import "./globals.css";
-import { StoreProvider } from "./store";
-import { LoginApi } from "@/src/entities/auth";
-import { useEffect } from "react";
+import type { Metadata } from "next";
+import { App } from "@/src/app";
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
+export const metadata: Metadata = {
+ title: "디지털 하수관로",
+ description: "디지털 하수관로 시스템입니다",
+ keywords: ["하수관로"],
+ openGraph: {
+ title: "디지털 하수관로",
+ description: "디지털 하수관로 시스템입니다"
+ }
+};
- useEffect(() => {
- // 렌더 중이 아니라 클라이언트에서만 실행 + 에러 캐치
- void LoginApi.login({ userId: "CLEANWATER", password: "gktneh12!@" })
- .catch((e) => {
- console.error("Auto login failed:", e);
- });
- }, []);
- return (
-
-
-
- {children}
-
-
-
- );
-}
+export default App;
diff --git a/apps/fo/app/page.tsx b/apps/fo/app/page.tsx
index 823e5a408..b1a103bca 100644
--- a/apps/fo/app/page.tsx
+++ b/apps/fo/app/page.tsx
@@ -1,44 +1,28 @@
-"use client"
+"use client";
+import { LoginApi } from "@/src/entities/auth";
+import { useEffect } from "react";
+import { AUTH_USER_ID } from "@/src/shared/config";
+import { getCookie } from "@/src/shared/lib/cookie";
+import { useRouter } from "next/navigation";
-import Link from "next/link";
-import { Button } from "@common/components/ui";
export default function Home() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
+ const router = useRouter();
+ const checkToken = getCookie(AUTH_USER_ID);
+
+ useEffect(() => {
+ if (!checkToken) {
+ async function doLogin() {
+ try {
+ await LoginApi.login({ userId: "CLEANWATER", password: "gktneh12!@" });
+ router.push("/pages/dashboard");
+ } catch (err) {
+ console.error("로그인 실패", err);
+ }
+ }
+ doLogin();
+ } else {
+ router.push("/pages/dashboard");
+ }
+ }, []);
+}
\ No newline at end of file
diff --git a/apps/fo/app/pages/(with-layout)/layout.tsx b/apps/fo/app/pages/(with-layout)/layout.tsx
index f7c0e62ba..851d79497 100644
--- a/apps/fo/app/pages/(with-layout)/layout.tsx
+++ b/apps/fo/app/pages/(with-layout)/layout.tsx
@@ -1,13 +1,30 @@
"use client"
-import Base from "@/app/base";
+import { BaseLayout } from "@common/components";
+import type { MenuItem } from "@/app/store/common";
+import { useFoStore } from "@/app/store";
+import { usePathname, useRouter } from "next/navigation";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
+ const { getMenuAuth } = useFoStore((state) => state);
+ const pathname = usePathname();
+ const router = useRouter();
+
+ const handleClick = (id: MenuItem) => {
+ getMenuAuth(id.menuCd);
+ router.push(id.urlPath);
+ };
+
return (
-
{children}
+
+ {children}
+
);
}
diff --git a/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx b/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx
index fbda7a140..7e3f7c52f 100644
--- a/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx
+++ b/apps/fo/app/pages/(with-layout)/processFacility/modal/page.tsx
@@ -117,7 +117,7 @@ export default function ProcessFacilityInfoModal(props: ModalType) {
{ value: 'common_info', label: '기본정보' },
{ value: 'history_info', label: '이력정보' },
];
- const fctCpc = props.fctCpc ? props.fctCpc.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : '';
+ const fctCpc = props.fctCpc ? Number(props.fctCpc).toLocaleString() : '';
const upHeadListPlanInfoChange = headMakeList(upHeadListPlanInfo, 2);
const upHeadListFcltyHistInfoChange = headMakeList(upHeadListFcltyHistInfo, 3);
const upHeadListFcltyWaterQltyInfoChange = headMakeList(upHeadListFcltyWaterQltyInfo, 4);
@@ -222,9 +222,9 @@ export default function ProcessFacilityInfoModal(props: ModalType) {
{'시설명'}
- {props.fcltyName}
- {'시설구분'}
- {props.bscFctClsNm}
+ {props.fcltyName}
+ {/* {'시설구분'}
+ {props.bscFctClsNm} */}
{'시설용량(m³/일)'}
{fctCpc}
diff --git a/apps/fo/app/pages/(with-layout)/processFacility/page.tsx b/apps/fo/app/pages/(with-layout)/processFacility/page.tsx
index 0e9085cb4..775dd1a45 100644
--- a/apps/fo/app/pages/(with-layout)/processFacility/page.tsx
+++ b/apps/fo/app/pages/(with-layout)/processFacility/page.tsx
@@ -210,12 +210,13 @@ export default function ProcessFacilitySearch() {
const cellClick = (index: number) => {
const facility = processFacilityList[index];
if (facility) {
+ const fctCpc = Number(String(facility.fctCpc).replace(/,/g, "").trim());
setModalFcltyNm(facility.bscFctNm);
setModalFcltyCd(facility.bscFctCd);
setModalSido(facility.sido);
setModalGugun(facility.gugun);
setModalAddr(facility.daddr);
- setModalFctCpc(Number(facility.fctCpc));
+ setModalFctCpc(fctCpc);
setModalTrtFrfNm(facility.trtFrfNm);
setModalRnDt(facility.rnDt);
setModalCmplDt(facility.cmplDt);
diff --git a/apps/fo/app/store/common/index.tsx b/apps/fo/app/store/common/index.tsx
index e75492d52..e4aebc982 100644
--- a/apps/fo/app/store/common/index.tsx
+++ b/apps/fo/app/store/common/index.tsx
@@ -232,7 +232,7 @@ export const COD_YEAR = 2021;
*/
// 처리시설 계획정보 호출 (알에스원)
-export const planInfoUrl = 'https://apigateway.apps.rsonesoft.com/dc-service/api/v1/nowDmgMtncGnsvy';
+export const planInfoUrl = '/dc-service/api/v1/nowDmgMtncGnsvy';
export const fetchPlanInfo = async (targetYear : string | undefined, ctpvNm : string | undefined, sggNm : string | undefined, fcltyCd : string | undefined) => {
try {
diff --git a/apps/fo/middleware.ts b/apps/fo/middleware.ts
index 32257341d..bb9c9effa 100644
--- a/apps/fo/middleware.ts
+++ b/apps/fo/middleware.ts
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
-import { SERVER_API_CS_URL } from "./src/shared/config/env";
+import { SERVER_API_CS_URL, SERVER_API_FO_URL, SERVER_API_WR_URL } from "./src/shared/config/env";
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
@@ -7,6 +7,8 @@ const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
const serverApiUrl = SERVER_API_CS_URL;
+const DC_URL = process.env.NEXT_PUBLIC_DC_URL || "";
+
export function middleware(req: NextRequest) {
console.log("Middleware 진입:", req.nextUrl.pathname);
const pathname = req.nextUrl.pathname;
@@ -63,12 +65,12 @@ export function middleware(req: NextRequest) {
return NextResponse.rewrite(new URL(newUrl));
}
- if (pathname.startsWith("/sewerage-service/api/v1/map/geometry")) {
+ if (pathname.startsWith("/sewerage-service")) {
const queryParams = searchParams.toString();
- const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${
+ const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service", "")}${
queryParams ? `?${queryParams}` : ""
}`;
- console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
+ console.log(`[Middleware] Rewriting from '/sewerage-service' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
@@ -89,40 +91,42 @@ export function middleware(req: NextRequest) {
});
}
- if (pathname.startsWith("/operation-service")) {
- const requestHeaders = new Headers(req.headers);
- const token = req.cookies.get(CLAIM_NAME)?.value;
- const userId = req.cookies.get(AUTH_USER_ID)?.value;
- const newUrl = `${serverApiUrl}${pathname}`;
-
- if (token) requestHeaders.set(CLAIM_NAME, token);
- if (userId) requestHeaders.set(AUTH_USER_ID, userId);
-
- console.log(
- `[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`
- );
- return NextResponse.rewrite(new URL(newUrl), {
- headers: requestHeaders,
- });
- }
-
if (pathname.startsWith("/reuse-service")) {
+ const queryParams = searchParams.toString();
+ const newUrl = `${SERVER_API_WR_URL}${pathname.replace("/reuse-service", "")}${
+ queryParams ? `?${queryParams}` : ""
+ }`;
+ console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`);
+ return NextResponse.rewrite(new URL(newUrl));
+ }
+
+ if (pathname.startsWith("/operation-service")) {
+ const queryParams = searchParams.toString();
+ const newUrl = `${SERVER_API_FO_URL}${pathname.replace("/operation-service", "")}${
+ queryParams ? `?${queryParams}` : ""
+ }`;
+ console.log(`[Middleware] Rewriting from '/operation-service' path to: ${newUrl}`);
+ return NextResponse.rewrite(new URL(newUrl));
+ }
+
+ if (pathname.startsWith("/dc-service")) {
const requestHeaders = new Headers(req.headers);
+ const queryParams = searchParams.toString();
+ const newUrl = `${DC_URL}${pathname.replace("/dc-service", "")}${
+ queryParams ? `?${queryParams}` : ""
+ }`;
+ console.log(`[Middleware] Rewriting from '/dc-service' path to: ${newUrl}`);
+
const token = req.cookies.get(CLAIM_NAME)?.value;
const userId = req.cookies.get(AUTH_USER_ID)?.value;
- const newUrl = `${serverApiUrl}${pathname}`;
-
if (token) requestHeaders.set(CLAIM_NAME, token);
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
- console.log(
- `[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`
- );
return NextResponse.rewrite(new URL(newUrl), {
headers: requestHeaders,
});
}
-
+
if (pathname.startsWith("/user-service")) {
const requestHeaders = new Headers(req.headers);
const newUrl = `${SERVER_API_CS_URL}${pathname}`;
@@ -135,21 +139,23 @@ export function middleware(req: NextRequest) {
});
}
- if (pathname === "/") {
- return NextResponse.redirect(new URL("/pages/dashboard", req.url));
- }
- return NextResponse.next();
+ // if (pathname === "/") {
+ // return NextResponse.redirect(new URL("/pages/dashboard", req.url));
+ // }
+ // return NextResponse.next();
}
export const config = {
matcher: [
- "/",
- "/server/:path*",
- "/geoserver/:path*",
- "/attach-service/:path*",
- "/user-service/:path*",
- "/cms-service/:path*",
- "/operation-service/:path*",
- "/reuse-service/:path*",
+ "/",
+ "/server/:path*",
+ "/geoserver/:path*",
+ "/sewerage-service/:path*",
+ "/attach-service/:path*",
+ "/user-service/:path*",
+ "/cms-service/:path*",
+ "/operation-service/:path*",
+ "/reuse-service/:path*",
+ "/dc-service/:path*",
],
};
diff --git a/apps/fo/package.json b/apps/fo/package.json
index 8697a116a..53423decf 100644
--- a/apps/fo/package.json
+++ b/apps/fo/package.json
@@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
+ "@common/assets": "workspace:*",
"@common/business_components": "workspace:*",
"@common/components": "workspace:*",
"@tailwindcss/vite": "^4.0.10",
diff --git a/apps/fo/src/app/index.tsx b/apps/fo/src/app/index.tsx
new file mode 100644
index 000000000..692b55ef0
--- /dev/null
+++ b/apps/fo/src/app/index.tsx
@@ -0,0 +1,20 @@
+import "./styles/base.css";
+import "./styles/components.css";
+import "@fontsource-variable/noto-sans-kr";
+
+import { AppProvider } from "./providers";
+import { cookies } from "next/headers";
+
+const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
+ const theme = (await cookies()).get("site-theme-key");
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export { App };
diff --git a/apps/fo/src/app/providers/app-provider.tsx b/apps/fo/src/app/providers/app-provider.tsx
new file mode 100644
index 000000000..edce6071e
--- /dev/null
+++ b/apps/fo/src/app/providers/app-provider.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import { DndProvider } from "react-dnd";
+import { MultiBackend, getBackendOptions } from "@minoru/react-dnd-treeview";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { ConfirmProvider } from "@frontend-opensource/use-react-hooks";
+import { AuthWrapper } from "@/src/entities/auth/lib/auth-wrapper";
+import { MenuWrapper } from "@/src/entities/menu";
+import { PromptProvider, StoreProvider } from "@/src/shared/providers";
+import { AlertDialog } from "@/src/shared/ui/alert-dialog";
+import { PromptDialog } from "@/src/shared/ui/prompt-dialog";
+import { ConfirmDialog } from "@/src/shared/ui/confirm-dialog";
+import { StoreProvider as FoStoreProvider } from "@/app/store";
+
+
+const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {
+ const queryClient = new QueryClient();
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export { AppProvider };
diff --git a/apps/fo/src/app/providers/index.ts b/apps/fo/src/app/providers/index.ts
new file mode 100644
index 000000000..15f99b7fd
--- /dev/null
+++ b/apps/fo/src/app/providers/index.ts
@@ -0,0 +1 @@
+export { AppProvider } from "./app-provider";
diff --git a/apps/fo/src/app/styles/base.css b/apps/fo/src/app/styles/base.css
new file mode 100644
index 000000000..8453ff9e8
--- /dev/null
+++ b/apps/fo/src/app/styles/base.css
@@ -0,0 +1,12 @@
+@import "@common/assets/styles/base.css";
+@import "@common/assets/styles/diveicon.css";
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+html,
+body {
+ font-family: "Noto Sans KR", sans-serif;
+ font-size: 10px;
+}
diff --git a/apps/fo/src/app/styles/components.css b/apps/fo/src/app/styles/components.css
new file mode 100644
index 000000000..4dc30bafd
--- /dev/null
+++ b/apps/fo/src/app/styles/components.css
@@ -0,0 +1,29 @@
+@tailwind components;
+
+@layer components {
+ .card {
+ @apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
+ }
+ .card-border {
+ @apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
+ }
+ .card-header {
+ @apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
+ }
+ .card-footer {
+ @apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
+ }
+
+ [data-theme="black"] {
+ .card-border {
+ @apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
+ }
+ .card-header {
+ @apply border-b-[#3f4042];
+ }
+ }
+
+ input[type="file"] {
+ @apply w-[200px] h-[40px] px-3 py-2 border border-[#d1d5db] rounded-md bg-white text-sm cursor-pointer;
+ }
+}
diff --git a/apps/fo/src/entities/auth/lib/auth-wrapper.tsx b/apps/fo/src/entities/auth/lib/auth-wrapper.tsx
index 678ff50d5..8f77aa8af 100644
--- a/apps/fo/src/entities/auth/lib/auth-wrapper.tsx
+++ b/apps/fo/src/entities/auth/lib/auth-wrapper.tsx
@@ -1,45 +1,54 @@
-// "use client";
+"use client";
-// import { useEffect } from "react";
-// import { useRouter } from "next/navigation";
-// import { LoginApi } from "../api/login-service";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+import { client } from "@/src/shared/api";
+import type { IUserInfo } from "@/src/shared/store";
+import { useUserStore, getCookie } from "@/src/shared/lib";
+import { ENDPOINT, AUTH_USER_ID } from "@/src/shared/config";
+import { LoginApi } from "../api/login-service";
-// const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
-// const router = useRouter();
-// const { userInfo, setUserInfo } = useUserStore((state) => state);
+//const LOGIN_PAGE_URL = process.env.NEXT_PUBLIC_DEV_CM_LOGIN_PAGE_URL || "http://localhost:3001/auth/login"; // 개발 환경 URL
-// async function authCheck() {
-// const userId = getCookie(AUTH_USER_ID);
-// if (!userId) {
-// throw new Error("Missing required authentication tokens");
-// }
+const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
+ const router = useRouter();
+ const { userInfo, setUserInfo } = useUserStore((state) => state);
-// const response = await client.get(
-// `${ENDPOINT.USER_SERVICE.USERS}/info/me`,
-// );
-// if (!userInfo) setUserInfo(response.data);
-// }
+ async function authCheck() {
+ const userId = getCookie(AUTH_USER_ID);
+ if (!userId) {
+ throw new Error("Missing required authentication tokens");
+ }
-// useEffect(() => {
-// (async () => {
-// try {
-// await authCheck();
-// } catch (error) {
-// console.error(error);
+ const response = await client.get(
+ `${ENDPOINT.USER_SERVICE.USERS}/info/me`,
+ );
+ if (!userInfo) setUserInfo(response.data);
+ }
-// try {
-// await LoginApi.silentRefresh();
-// await authCheck();
-// } catch (refreshError) {
-// console.error("Token refresh failed:", refreshError);
-// setUserInfo(null);
-// router.push("/auth/login");
-// }
-// }
-// })();
-// }, []);
+ useEffect(() => {
+ (async () => {
+ try {
+ await authCheck();
+ } catch (error) {
+ console.error(error);
-// return <>{children}>;
-// };
+ try {
+ await LoginApi.silentRefresh();
+ await authCheck();
+ } catch (refreshError) {
+ console.error("Token refresh failed:", refreshError);
+ setUserInfo(null);
+ router.push("/"); // 개발시 홈으로 돌아가기
-// export { AuthWrapper };
+ // window.location.href = LOGIN_PAGE_URL; // 세션 만료 시 CM 로그인 페이지로 리다이렉트
+ // router.push는 같은 Next.js 앱 내에서만 작동하므로 다른 앱으로 이동할 때는 window.location.href 사용
+ }
+ }
+ })();
+ }, []);
+
+ return <>{children}>;
+};
+
+export { AuthWrapper };
diff --git a/apps/fo/src/entities/menu-authority/@x/menu.ts b/apps/fo/src/entities/menu-authority/@x/menu.ts
new file mode 100644
index 000000000..86479f859
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/@x/menu.ts
@@ -0,0 +1 @@
+export { MenuAuthorityApi } from "../api/menu-authority-service";
diff --git a/apps/fo/src/entities/menu-authority/@x/posting.ts b/apps/fo/src/entities/menu-authority/@x/posting.ts
new file mode 100644
index 000000000..bfe84fa0d
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/@x/posting.ts
@@ -0,0 +1 @@
+export { AuthorityButton } from "../ui/authority-button";
diff --git a/apps/fo/src/entities/menu-authority/api/menu-authority-service.ts b/apps/fo/src/entities/menu-authority/api/menu-authority-service.ts
new file mode 100644
index 000000000..c7ac056f4
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/api/menu-authority-service.ts
@@ -0,0 +1,40 @@
+import { client } from "@/src/shared/api";
+import { ENDPOINT } from "@/src/shared/config";
+import type {
+ MenuAuthorityList,
+ MenuAuthorityParams,
+ MenuAuthorityPayload,
+ MenuAuthorityService,
+ PageMenuAuthority,
+} from "../model/menu-authority-interface";
+
+export class MenuAuthorityApiService implements MenuAuthorityService {
+ public async getMenuAuthorityList(
+ params: MenuAuthorityParams,
+ ): Promise {
+ const response = await client.get(
+ `${ENDPOINT.CMS_SERVICE.MENU_AUTHRTS}`,
+ { params },
+ );
+ return response.data;
+ }
+
+ public async getPageMenuAuthority(
+ menuCd: number,
+ ): Promise {
+ const response = await client.get(
+ `${ENDPOINT.CMS_SERVICE.MENU_AUTHRTS}/pageinfo/${menuCd}`,
+ );
+ return response.data;
+ }
+
+ public async saveMenuAuthority(data: MenuAuthorityPayload[]): Promise {
+ const response = await client.post(
+ `${ENDPOINT.CMS_SERVICE.MENU_AUTHRTS}`,
+ data,
+ );
+ return response.data;
+ }
+}
+
+export const MenuAuthorityApi = new MenuAuthorityApiService();
diff --git a/apps/fo/src/entities/menu-authority/index.ts b/apps/fo/src/entities/menu-authority/index.ts
new file mode 100644
index 000000000..2a555b77e
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/index.ts
@@ -0,0 +1,2 @@
+export { AuthorityButton } from "./ui/authority-button";
+export { ManagementAuthorityDialog } from "./ui/management-authority-dialog";
diff --git a/apps/fo/src/entities/menu-authority/model/menu-authority-interface.ts b/apps/fo/src/entities/menu-authority/model/menu-authority-interface.ts
new file mode 100644
index 000000000..f659f58af
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/model/menu-authority-interface.ts
@@ -0,0 +1,48 @@
+import { z } from "zod";
+import type { PageableList, PageableParams } from "@/src/shared/model";
+
+export interface MenuAuthorityParams extends PageableParams {
+ groupId?: string;
+ menuCd?: number;
+ useYn?: string;
+}
+
+export interface PageMenuAuthority {
+ mngAuthrtYn: string;
+ inqAuthrtYn: string;
+ regAuthrtYn: string;
+ mdfcnAuthrtYn: string;
+ delAuthrtYn: string;
+}
+
+export const menuAuthoritySchema = z.object({
+ groupId: z.string(),
+ groupNm: z.string(),
+ menuCd: z.number(),
+ mngAuthrtYn: z.string(),
+ inqAuthrtYn: z.string(),
+ regAuthrtYn: z.string(),
+ mdfcnAuthrtYn: z.string(),
+ delAuthrtYn: z.string(),
+ useYn: z.string(),
+ mode: z.string(),
+});
+
+export const menuAuthorityPayloadSchema = menuAuthoritySchema.omit({
+ groupNm: true,
+});
+
+export const menuAuthorityFormSchema = z.object({
+ menuAuthority: z.array(menuAuthorityPayloadSchema),
+});
+
+export type MenuAuthority = z.infer;
+export type MenuAuthorityList = PageableList;
+export type MenuAuthorityPayload = z.infer;
+export type MenuAuthorityFormData = z.infer;
+
+export interface MenuAuthorityService {
+ getMenuAuthorityList(params: MenuAuthorityParams): Promise;
+ getPageMenuAuthority(menuCd: number): Promise;
+ saveMenuAuthority(data: MenuAuthorityPayload[]): Promise;
+}
diff --git a/apps/fo/src/entities/menu-authority/model/menu-authority-table-columns.tsx b/apps/fo/src/entities/menu-authority/model/menu-authority-table-columns.tsx
new file mode 100644
index 000000000..6969a85e9
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/model/menu-authority-table-columns.tsx
@@ -0,0 +1,117 @@
+import type { ColumnDef } from "@tanstack/react-table";
+import type { UseFormReturn } from "react-hook-form";
+import { Checkbox } from "@common/components";
+import type {
+ MenuAuthority,
+ MenuAuthorityFormData,
+ MenuAuthorityPayload,
+} from "./menu-authority-interface";
+
+export const createColumns = (
+ form: UseFormReturn,
+): ColumnDef[] => {
+ const getCurrentRow = (i: number) => form.watch("menuAuthority")[i];
+
+ const handleCheckedChange =
+ (i: number, key: keyof MenuAuthorityPayload) => (checked: boolean) => {
+ if (getCurrentRow(i)?.mode === "R") {
+ form.setValue(`menuAuthority.${i}.mode`, "U");
+ }
+ form.setValue(`menuAuthority.${i}.${key}`, checked ? "Y" : "N");
+ };
+
+ return [
+ {
+ accessorKey: "groupNm",
+ header: "그룹명",
+ },
+ {
+ accessorKey: "mngAuthrtYn",
+ header: "관리권한",
+ cell: ({ row }) => {
+ return (
+ {
+ if (checked) {
+ handleCheckedChange(
+ row.index,
+ "regAuthrtYn",
+ )(checked as boolean);
+ handleCheckedChange(
+ row.index,
+ "mdfcnAuthrtYn",
+ )(checked as boolean);
+ handleCheckedChange(
+ row.index,
+ "delAuthrtYn",
+ )(checked as boolean);
+ handleCheckedChange(
+ row.index,
+ "inqAuthrtYn",
+ )(checked as boolean);
+ }
+ handleCheckedChange(row.index, "mngAuthrtYn")(checked as boolean);
+ }}
+ />
+ );
+ },
+ },
+ {
+ accessorKey: "regAuthrtYn",
+ header: "등록권한",
+ cell: ({ row }) => {
+ return (
+ {
+ if (checked) {
+ handleCheckedChange(
+ row.index,
+ "inqAuthrtYn",
+ )(checked as boolean);
+ }
+ handleCheckedChange(row.index, "regAuthrtYn")(checked as boolean);
+ }}
+ />
+ );
+ },
+ },
+ {
+ accessorKey: "mdfcnAuthrtYn",
+ header: "수정권한 ",
+ cell: ({ row }) => {
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: "delAuthrtYn",
+ header: "삭제권한",
+ cell: ({ row }) => {
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: "inqAuthrtYn",
+ header: "조회권한",
+ cell: ({ row }) => {
+ return (
+
+ );
+ },
+ },
+ ];
+};
diff --git a/apps/fo/src/entities/menu-authority/ui/authority-button.tsx b/apps/fo/src/entities/menu-authority/ui/authority-button.tsx
new file mode 100644
index 000000000..a4cd666d9
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/ui/authority-button.tsx
@@ -0,0 +1,26 @@
+import { useMenuStore } from "@/src/shared/lib";
+import { Button, cn, type ButtonProps } from "@common/components";
+import type { PageMenuAuthority } from "../model/menu-authority-interface";
+
+type AuthorityButtonProps = {
+ authority: keyof PageMenuAuthority;
+} & ButtonProps;
+
+function AuthorityButton({
+ authority,
+ className,
+ children,
+ ...props
+}: AuthorityButtonProps) {
+ const pageMenuAuthority = useMenuStore((state) => state.pageMenuAuthority);
+
+ const isAuthority = pageMenuAuthority?.[authority] === "Y";
+
+ return (
+
+ );
+}
+
+export { AuthorityButton };
diff --git a/apps/fo/src/entities/menu-authority/ui/management-authority-dialog.tsx b/apps/fo/src/entities/menu-authority/ui/management-authority-dialog.tsx
new file mode 100644
index 000000000..839b9a7a4
--- /dev/null
+++ b/apps/fo/src/entities/menu-authority/ui/management-authority-dialog.tsx
@@ -0,0 +1,103 @@
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQuery } from "@tanstack/react-query";
+import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
+import { useConfirm } from "@frontend-opensource/use-react-hooks";
+import {
+ Button,
+ DataTable,
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@common/components";
+import { ENDPOINT } from "@/src/shared/config";
+import { useAlertStore, useMutationWrapper } from "@/src/shared/lib";
+import {
+ menuAuthorityFormSchema,
+ type MenuAuthorityFormData,
+ type MenuAuthorityPayload,
+} from "../model/menu-authority-interface";
+import { createColumns } from "../model/menu-authority-table-columns";
+import { MenuAuthorityApi } from "../api/menu-authority-service";
+
+type ManagementAuthorityDialogProps = {
+ menuCd?: number;
+} & React.ComponentProps;
+
+function ManagementAuthorityDialog({
+ menuCd,
+ ...props
+}: ManagementAuthorityDialogProps) {
+ const { confirm } = useConfirm();
+ const alert = useAlertStore((state) => state.setMessage);
+
+ const { data: menuAuthorityList } = useQuery({
+ queryKey: [ENDPOINT.CMS_SERVICE.MENU_AUTHRTS, menuCd],
+ queryFn: () => MenuAuthorityApi.getMenuAuthorityList({ menuCd }),
+ enabled: !!menuCd,
+ });
+
+ const { mutateAsync: saveMenuAuthority } = useMutationWrapper({
+ mutationFn: (data: MenuAuthorityPayload[]) =>
+ MenuAuthorityApi.saveMenuAuthority(data),
+ onSuccess: () => {
+ alert("저장되었습니다.");
+ },
+ });
+
+ const form = useForm({
+ resolver: zodResolver(menuAuthorityFormSchema),
+ values: {
+ menuAuthority: menuAuthorityList?.content ?? [],
+ },
+ });
+
+ const table = useReactTable({
+ data: menuAuthorityList?.content ?? [],
+ columns: createColumns(form),
+ getCoreRowModel: getCoreRowModel(),
+ });
+
+ const handleSave = async () => {
+ if (await confirm("저장하시겠습니까?")) {
+ const data = form.getValues("menuAuthority");
+ const filteredData = data.filter((item) => item.mode !== "R");
+ await saveMenuAuthority(filteredData);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export { ManagementAuthorityDialog };
diff --git a/apps/fo/src/entities/menu/api/menu-service.ts b/apps/fo/src/entities/menu/api/menu-service.ts
new file mode 100644
index 000000000..8b08d7fa5
--- /dev/null
+++ b/apps/fo/src/entities/menu/api/menu-service.ts
@@ -0,0 +1,14 @@
+import { client } from "@/src/shared/api";
+import { ENDPOINT } from "@/src/shared/config";
+import type { MenuItem, MenuService } from "../model/menu-interface";
+
+export class MenuApiService implements MenuService {
+ async getMenuRoles(siteId: string): Promise