diff --git a/apps/wr/app/base.tsx b/apps/wr/app/base.tsx new file mode 100644 index 000000000..26b709348 --- /dev/null +++ b/apps/wr/app/base.tsx @@ -0,0 +1,26 @@ +"use client" +import { BaseLayout } from "@common/components/ui"; +import { useWrStore } from "./store"; +import { MainDiv } from "@common/business_components/ui"; +import type { MenuItem } from "./store/common/type/menu"; + +export default function Base({ + children, + changeMenu, +}: Readonly<{ + children: React.ReactNode; + changeMenu: (path: string) => void; +}>) { + const { getMenuAuthrts } = useWrStore((state) => state); + + return ( + { + getMenuAutpnhrts(e.menuCd); + changeMenu(e.urlPath); + }} + > + {children} + + ); +} diff --git a/apps/wr/app/layout.tsx b/apps/wr/app/layout.tsx index 38ce0ec4b..25934500b 100644 --- a/apps/wr/app/layout.tsx +++ b/apps/wr/app/layout.tsx @@ -1,12 +1,11 @@ -'use client' -import { BaseLayout } from "@common/components"; +"use client"; import "./globals.css"; -import { StoreProvider } from "./store"; -import { MainDiv } from "@common/business_components"; -import koBase from '../public/locales/ko/message.json'; +import { StoreProvider} from "./store"; +import koBase from "../public/locales/ko/message.json"; import i18next from "i18next"; import { I18nextProvider } from "react-i18next"; -import { useRouter } from "next/navigation"; +import Base from "./base"; +import { useRouter } from "next/navigation"; // ✅ App Router 전용 export default function RootLayout({ children, @@ -14,34 +13,35 @@ export default function RootLayout({ children: React.ReactNode; }>) { i18next.init({ - lng: 'ko', - fallbackLng: 'ko', + lng: "ko", + fallbackLng: "ko", resources: { ko: { base: koBase, }, - } + }, }); + const router = useRouter(); +const changeMenu = (path: string) => { + router.push(path); +}; + return ( - { - router.push(e.urlPath) - }} + - - {children} - - + {children} + - + ); } diff --git a/apps/wr/app/store/common/auth/index.tsx b/apps/wr/app/store/common/auth/index.tsx new file mode 100644 index 000000000..4e0b08d1b --- /dev/null +++ b/apps/wr/app/store/common/auth/index.tsx @@ -0,0 +1,75 @@ +import { getFetch, getFetchWithCount } from "@/components/lib/api"; +import toast from "react-hot-toast"; + +export const getMenuAuthrts = async (menuCd: number) => { + const response = await getFetch( + "LOCAL", + `/cms-service/api/v1/menu-authrts/pageinfo/${menuCd}` + ); + + authStateExport.auth = { + mngAuthrtYn: response[0].mngAuthrtYn, + inqAuthrtYn: response[0].mngAuthrtYn, + regAuthrtYn: response[0].mngAuthrtYn, + mdfcnAuthrtYn: response[0].mngAuthrtYn, + delAuthrtYn: response[0].mngAuthrtYn, + }; +}; + +export type authState = { + auth: { + mngAuthrtYn: string; + inqAuthrtYn: string; + regAuthrtYn: string; + mdfcnAuthrtYn: string; + delAuthrtYn: string; + }; +}; + +export type authType = authState & authActions; + +export const authStateExport: authState = { + auth: { + mngAuthrtYn: "Y", + inqAuthrtYn: "Y", + regAuthrtYn: "Y", + mdfcnAuthrtYn: "Y", + delAuthrtYn: "Y", + }, +}; + +export type authActions = { + getMenuAuthrts: (menuCd: number) => void; +}; + +export const authActionsExport: (set: any) => authActions = (set: any) => { + return { + getMenuAuthrts: async (menuCd: number) => { + try{ + + const response = await getFetchWithCount( 'LOCAL', + + `/cms-service/api/v1/menu-authrts/pageinfo/${menuCd}` + ); + set((state: authState) => { + return { + ...state, + auth: { + mngAuthrtYn: response[0].mngAuthrtYn, + inqAuthrtYn: response[0].mngAuthrtYn, + regAuthrtYn: response[0].mngAuthrtYn, + mdfcnAuthrtYn: response[0].mngAuthrtYn, + delAuthrtYn: response[0].mngAuthrtYn, + }, + }; + }); + + }catch(e){ + + toast.error('권한 정보를 가져오는 중 오류가 발생했습니다. 관리자에게 문의하세요.'); + console.error('Error fetching menu authorities:', e); + + } + }, + }; +}; diff --git a/apps/wr/app/store/common/auth/type.tsx b/apps/wr/app/store/common/auth/type.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/apps/wr/app/store/common/type/menu/index.tsx b/apps/wr/app/store/common/type/menu/index.tsx new file mode 100644 index 000000000..46df1cf51 --- /dev/null +++ b/apps/wr/app/store/common/type/menu/index.tsx @@ -0,0 +1,19 @@ +export interface MenuItem { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: MenuItem[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; + } \ No newline at end of file diff --git a/apps/wr/app/store/store.tsx b/apps/wr/app/store/store.tsx index b61477862..3ab1439ff 100644 --- a/apps/wr/app/store/store.tsx +++ b/apps/wr/app/store/store.tsx @@ -98,6 +98,7 @@ import { rainStatActionsExport, rainStatExport, type rainStatState, type rainSta import { rswtStatActionsExport, rswtStatExport, type rswtStatState, type rswtStatType } from "./stats/rswtStat"; //import { isLogin } from "./common/login"; import { LoginApi } from "@/src/entities/auth"; +import { authActionsExport, authStateExport, type authState, type authType } from "./common/auth"; export type WrStore = comCodeType & pubFacType & @@ -124,7 +125,9 @@ export type WrStore = comCodeType & ruseCaseType& rainStatType& rswtStatType& - comFcltType + comFcltType & + authType + ; export const StoreZus = ( @@ -153,7 +156,8 @@ export const StoreZus = ( drgtState& ruseCaseState& rainStatState& - rswtStatState + rswtStatState& + authState = { ...comCodeStateExport, ...pubFacStateExport, @@ -180,7 +184,8 @@ export const StoreZus = ( ...ruseCaseStateExport, ...rainStatExport, ...rswtStatExport, - ...comFcltStateExport + ...comFcltStateExport, + ...authStateExport } ) => { return createStore()((set) => { @@ -218,7 +223,8 @@ export const StoreZus = ( ...drgtActionsExport(set), ...ruseCaseActionsExport(set), ...rainStatActionsExport(set), - ...rswtStatActionsExport(set) + ...rswtStatActionsExport(set), + ...authActionsExport(set) }; }); }; diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index 2f3df1699..4902ccf3d 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -116,6 +116,17 @@ export const handleCommaNumberChange = ( onChange(raw === "" ? "" : Number(raw)); }; +export const handleOnlyNumberChange = ( + e: React.ChangeEvent, + onChange: (value: number | "") => void +) => { + // 콤마 제거 후 숫자와 소수점만 남기기 + const cleaned = e.target.value.replace(/,/g, "").replace(/[^0-9.]/g, ""); + // 숫자 및 소수점 외 입력 차단 + if (!/^\d*\.?\d*$/.test(cleaned)) return; + onChange(cleaned === "" ? "" : Number(cleaned)); +}; + // onchange 텍스트를 숫자로 and ,제거 export const handleMinusNumberChange = ( e: React.ChangeEvent, diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index b3928e4ae..fe9e45ab1 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -4,7 +4,7 @@ import { CLAIM_NAME, AUTH_USER_ID } from "./src/shared/config"; // const CLAIM_NAME = "Authorization"; // const AUTH_USER_ID = "token-id"; - const serverApiUrl = CS_URL; +const serverApiUrl = CS_URL; 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 || ""; @@ -25,28 +25,43 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/server")) { const decodedParams = Array.from(searchParams.entries()) - .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) - .join("&"); + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); - console.log(' sesesesesesevereveraserwereqwrewbrtkehjwrtg server') const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ decodedParams ? `?${decodedParams}` : "" }`; return NextResponse.rewrite(new URL(newUrl)); } + if (pathname === "/") { + console.log(" maim 화면이동 "); + return NextResponse.redirect(new URL("/map/mainMap", req.url)); + } + + if (pathname.startsWith("/cms-service")) { + const requestHeaders = new Headers(req.headers); + const Authorization = req.cookies.get(CLAIM_NAME)?.value; + const newUrl = `${serverApiUrl}${pathname}`; + + console.log("Authorization cms-service ", Authorization); + if (Authorization) requestHeaders.set(CLAIM_NAME, Authorization); + //if (userId) requestHeaders.set(AUTH_USER_ID, userId); + + console.log( + `[Middleware] Rewriting from '/cms-service' path to: ${newUrl}` + ); + return NextResponse.rewrite(new URL(newUrl), { + headers: requestHeaders, + }); + } if (pathname.startsWith("/attach-service")) { const requestHeaders = new Headers(req.headers); - // const token = - // "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhMGVjN2EwNy03NTJhLTRmN2YtODlhNi04NTRlNjNkMmUzYTAiLCJhdXRob3JpdGllcyI6IlJPTEVfQURNSU4sUk9MRV9BTk9OWU1PVVMiLCJtbmdyWW4iOiJZIiwiZXhwIjoxNzUyMTM1OTk3fQ.itGY6M6mC2iwKvEpXw2KS6354T_B_OFz4hD26ResIgwJhpwiDJCMuSXD-rZ3AarmAIKd0udpCuJR7UlvFMN2ww"; - // const userId = "a0ec7a07-752a-4f7f-89a6-854e63d2e3a0"; const token = req.cookies.get(CLAIM_NAME)?.value; const userId = req.cookies.get(AUTH_USER_ID)?.value; const newUrl = `${serverApiUrl}${pathname}`; - //wr:dev: pathname /attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad - //http://dev.onasys.co.kr:8000/attach-service/api/v1/tus/upload/5e60b70a-956e-40c3-be5a-cdd3100bf8ad if (token) requestHeaders.set(CLAIM_NAME, token); if (userId) requestHeaders.set(AUTH_USER_ID, userId); @@ -66,7 +81,7 @@ export function middleware(req: NextRequest) { `[Middleware] Rewriting from '/user-service' path to: ${newUrl}` ); return NextResponse.rewrite(new URL(newUrl), { - headers: requestHeaders + headers: requestHeaders, }); } @@ -78,33 +93,31 @@ export function middleware(req: NextRequest) { 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" //도시침수지도 + 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" //우수토실 + } 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" //잠재수요처 - ){ + } 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:","")); + url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); queryParams = url.searchParams.toString(); newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; } @@ -115,8 +128,9 @@ export function middleware(req: NextRequest) { if (pathname.startsWith("/sewerage-service")) { const queryParams = searchParams.toString(); - const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${queryParams ? `?${queryParams}` : "" - }`; + const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${ + queryParams ? `?${queryParams}` : "" + }`; console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`); return NextResponse.rewrite(new URL(newUrl)); } @@ -125,5 +139,12 @@ export function middleware(req: NextRequest) { } export const config = { - matcher: ["/server/:path*", "/user-service/:path*", "/attach-service/:path*", "/geoserver/:path*", "/sewerage-service/:path*"], + matcher: [ + "/cms-service/:path*", + "/server/:path*", + "/user-service/:path*", + "/attach-service/:path*", + "/geoserver/:path*", + "/sewerage-service/:path*", + ], }; diff --git a/apps/wr/src/entities/auth/api/login-service.ts b/apps/wr/src/entities/auth/api/login-service.ts index 3cd387507..1808f7de8 100644 --- a/apps/wr/src/entities/auth/api/login-service.ts +++ b/apps/wr/src/entities/auth/api/login-service.ts @@ -29,23 +29,18 @@ export class LoginApiService implements LoginService { const accessToken = response.headers.get(ACCESS_TOKEN); const tokenId = response.headers.get(AUTH_USER_ID); - console.log('accessToken ', accessToken) - console.log('tokenId ', tokenId) - if (!accessToken || !tokenId) { throw new Error("Missing required authentication tokens"); } - - tokenManager.setToken(CLAIM_NAME, accessToken); - tokenManager.setToken(AUTH_USER_ID, tokenId); - tokenManager.setToken("site-id-key", SITE_ID); + tokenManager.setToken(CLAIM_NAME, accessToken) + tokenManager.setToken(AUTH_USER_ID, tokenId) + tokenManager.setToken("site-id-key", SITE_ID) tokenManager.setupRefreshTimer( () => this.silentRefresh(), JWT_EXPIRATION_TIME, ); } catch (error) { - console.log(' login err ' , error) throw new Error(`Login failed: ${error}`); } } @@ -67,7 +62,7 @@ export class LoginApiService implements LoginService { if (!accessToken || !tokenId) { throw new Error("Missing required authentication tokens"); } - + tokenManager.setToken(CLAIM_NAME, accessToken); tokenManager.setToken(AUTH_USER_ID, tokenId); } catch (error) { diff --git a/apps/wr/src/entities/auth/lib/auth-wrapper.tsx b/apps/wr/src/entities/auth/lib/auth-wrapper.tsx deleted file mode 100644 index 678ff50d5..000000000 --- a/apps/wr/src/entities/auth/lib/auth-wrapper.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// "use client"; - -// import { useEffect } from "react"; -// import { useRouter } from "next/navigation"; -// import { LoginApi } from "../api/login-service"; - -// const AuthWrapper = ({ children }: { children: React.ReactNode }) => { -// const router = useRouter(); -// const { userInfo, setUserInfo } = useUserStore((state) => state); - -// async function authCheck() { -// const userId = getCookie(AUTH_USER_ID); -// if (!userId) { -// throw new Error("Missing required authentication tokens"); -// } - -// const response = await client.get( -// `${ENDPOINT.USER_SERVICE.USERS}/info/me`, -// ); -// if (!userInfo) setUserInfo(response.data); -// } - -// useEffect(() => { -// (async () => { -// try { -// 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"); -// } -// } -// })(); -// }, []); - -// return <>{children}; -// }; - -// export { AuthWrapper }; diff --git a/apps/wr/src/shared/config/api.ts b/apps/wr/src/shared/config/api.ts index 3eba9fa91..b0d1a5be2 100644 --- a/apps/wr/src/shared/config/api.ts +++ b/apps/wr/src/shared/config/api.ts @@ -1,6 +1,6 @@ import { PROXY_HOST } from "./env"; -export const BASE_URL = `${PROXY_HOST}/server`; +export const BASE_URL = `${PROXY_HOST}`; export const ENDPOINT = { ATTACH_SERVICE: {