Merge branch 'main' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -37,4 +37,5 @@ yarn-error.log*
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
.env*
|
||||
.env*
|
||||
__ENV__.js
|
||||
@@ -0,0 +1,44 @@
|
||||
import { findUp } from "find-up";
|
||||
import { config } from "dotenv";
|
||||
import fs from "fs";
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
|
||||
async function parseDotenv(nodeEnv) {
|
||||
const envFilePath = await findUp(`.env.${nodeEnv}`);
|
||||
|
||||
const parsedEnv = config({ path: envFilePath }).parsed || {};
|
||||
|
||||
return parsedEnv;
|
||||
}
|
||||
|
||||
function writeEnv(parsedEnv) {
|
||||
const scriptFilePath = `${fs.realpathSync(process.cwd())}/public/__ENV__.js`;
|
||||
|
||||
fs.writeFileSync(
|
||||
scriptFilePath,
|
||||
`window.__ENV__ = ${JSON.stringify(parsedEnv)}`,
|
||||
);
|
||||
}
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command(
|
||||
"next-env",
|
||||
"Create Next.js runtime environment js",
|
||||
function builder(y) {
|
||||
return y.option("env", {
|
||||
alias: "e",
|
||||
type: "string",
|
||||
description: "Environment name(ex: development, production)",
|
||||
});
|
||||
},
|
||||
async function handler(args) {
|
||||
const nodeEnv = args.e || args.env || "development";
|
||||
|
||||
const parsedEnv = await parseDotenv(nodeEnv); // dotenv 파싱
|
||||
writeEnv(parsedEnv); // 환경 변수 스크립트 파일 생성
|
||||
|
||||
return parsedEnv;
|
||||
},
|
||||
)
|
||||
.parse();
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "features",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 13000",
|
||||
"dev": "node cli.js next-env -e ${NODE_ENV:-development} && next dev -p 13000",
|
||||
"build": "next build",
|
||||
"start": "next start -p 13000",
|
||||
"start": "node cli.js next-env -e ${NODE_ENV:-production} && next start -p 13000",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -27,6 +28,8 @@
|
||||
"axios": "^1.8.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^17.2.1",
|
||||
"find-up": "^7.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"next": "15.3.0",
|
||||
"next-themes": "^0.4.6",
|
||||
@@ -44,6 +47,7 @@
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"@types/yargs": "^17.0.33",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.7",
|
||||
"postcss": "^8",
|
||||
|
||||
@@ -4,12 +4,16 @@ import "@fontsource-variable/noto-sans-kr";
|
||||
|
||||
import { AppProvider } from "./providers";
|
||||
import { cookies } from "next/headers";
|
||||
import Script from "next/script";
|
||||
|
||||
const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
const theme = (await cookies()).get("site-theme-key");
|
||||
|
||||
return (
|
||||
<html lang="ko" data-theme={theme?.value}>
|
||||
<head>
|
||||
<Script src="/__ENV__.js" strategy="beforeInteractive" />
|
||||
</head>
|
||||
<body>
|
||||
<AppProvider>{children}</AppProvider>
|
||||
</body>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { PROXY_HOST } from "./env";
|
||||
"use client";
|
||||
|
||||
export const BASE_URL = `${PROXY_HOST}/server`;
|
||||
import { getCookie, getEnv } from "../lib";
|
||||
|
||||
const SITE_ID = getCookie("site-id-key") ?? "";
|
||||
const PROXY_HOST_KEY = `NEXT_PUBLIC_PROXY_HOST_${SITE_ID}`;
|
||||
export const BASE_URL = `${getEnv(PROXY_HOST_KEY)}/server`;
|
||||
|
||||
export const ENDPOINT = {
|
||||
ATTACH_SERVICE: {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
__ENV__?: Record<string, string>;
|
||||
}
|
||||
}
|
||||
|
||||
export function getEnvConfig(): Record<string, string> {
|
||||
if (typeof window === "undefined") return {};
|
||||
return window.__ENV__ ?? {};
|
||||
}
|
||||
|
||||
export function getEnv(key: string): string | undefined {
|
||||
return getEnvConfig()[key];
|
||||
}
|
||||
@@ -3,3 +3,4 @@ export { useAlertStore } from "./use-alert-store";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
export { usePrompt } from "./use-prompt";
|
||||
export { getEnv } from "./env";
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Authorizations {
|
||||
httpMethodCode: string;
|
||||
sortSeq: number;
|
||||
useYn: string;
|
||||
siteId: string;
|
||||
mdfr: string;
|
||||
mdfcnDt: string;
|
||||
}
|
||||
@@ -27,6 +28,7 @@ export interface RoleAuthorizations {
|
||||
urlPatternValue: string;
|
||||
httpMethodCode: string;
|
||||
sortSeq: number;
|
||||
siteId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ export const authorizationFormSchema = z.object({
|
||||
httpMethodCode: z.string(),
|
||||
sortSeq: z.number(),
|
||||
useYn: z.string(),
|
||||
siteId: z.string(),
|
||||
});
|
||||
|
||||
export type AuthorizationsList = PageableList<Authorizations>;
|
||||
|
||||
@@ -161,12 +161,7 @@ export const LinkTableColumnsSchema = z.object({
|
||||
}, {
|
||||
message: '최대 14자리까지 입력 가능합니다.',
|
||||
}),
|
||||
col_seq: z.coerce.number().nullable().optional().refine((val) => {
|
||||
if (val == null) return true;
|
||||
return val > 0;
|
||||
}, {
|
||||
message: "컬럼 매칭 순서는 0 이상이어야 합니다."
|
||||
}),
|
||||
col_seq: z.coerce.number().nullable().optional(),
|
||||
mdfcn_dt: z.string().nullable().optional(),
|
||||
mdfr_id: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface WithRoleIdRoleAuthorizationsParams extends PageableParams {
|
||||
keywordType?: string;
|
||||
keyword?: string;
|
||||
roleId?: string;
|
||||
siteId?: string;
|
||||
}
|
||||
|
||||
export interface RoleAuthorizations {
|
||||
|
||||
@@ -13,21 +13,22 @@ import {
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
import { FormWrapper } from "@/widgets/form-wrapper";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
|
||||
import type {
|
||||
AuthorizationFormData,
|
||||
WithRoleAuthorizations,
|
||||
} from "@/entities/authorization";
|
||||
import { CodeSelect } from "@/entities/code";
|
||||
import { SiteSelect } from "@/entities/site";
|
||||
import { FormControl, FormField, FormItem } from "@/shared/ui/form";
|
||||
|
||||
type BannerFormProps = {
|
||||
type ModuleFormProps = {
|
||||
form: UseFormReturn<AuthorizationFormData>;
|
||||
authorizationNo?: number;
|
||||
handleSave: (data: AuthorizationFormData) => Promise<WithRoleAuthorizations>;
|
||||
handleDelete?: () => Promise<void>;
|
||||
};
|
||||
|
||||
function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
function ModuleForm({ form, authorizationNo, ...props }: ModuleFormProps) {
|
||||
return (
|
||||
<FormWrapper form={form} isModifyMode={!!authorizationNo} {...props}>
|
||||
<div className="card">
|
||||
@@ -99,6 +100,26 @@ function ModuleForm({ form, authorizationNo, ...props }: BannerFormProps) {
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>사이트</TableHead>
|
||||
<TableCell className="border">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="siteId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormControl>
|
||||
<SiteSelect
|
||||
{...field}
|
||||
onValueChange={field.onChange}
|
||||
className="w-[20rem]"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableHead data-required>정렬순서</TableHead>
|
||||
<TableCell className="border">
|
||||
|
||||
@@ -40,9 +40,12 @@ function MappingRoleDialog({
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [keywordType, setKeywordType] = useState("");
|
||||
const [siteId, setSiteId] = useState("");
|
||||
const [searchParams, setSearchParams] = useState({
|
||||
keyword: "",
|
||||
keywordType: "authorizationName",
|
||||
keywordType: "",
|
||||
siteId: "",
|
||||
});
|
||||
|
||||
const query = useQuery({
|
||||
@@ -85,18 +88,20 @@ function MappingRoleDialog({
|
||||
const formData = new FormData(e.target as HTMLFormElement);
|
||||
const keyword = formData.get("keyword") as string;
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword,
|
||||
keywordType,
|
||||
siteId: siteId === "ALL" ? "" : siteId,
|
||||
});
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = "";
|
||||
}
|
||||
if (inputRef.current) inputRef.current.value = "";
|
||||
setKeywordType("");
|
||||
setSiteId("");
|
||||
setSearchParams({
|
||||
...searchParams,
|
||||
keyword: "",
|
||||
keywordType: "",
|
||||
siteId: "",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -119,14 +124,22 @@ function MappingRoleDialog({
|
||||
</DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<form onSubmit={handleSubmit} className="flex items-center gap-2">
|
||||
<SiteSelect enabledAllSite />
|
||||
<Select>
|
||||
<SiteSelect
|
||||
value={siteId}
|
||||
onValueChange={(value) => setSiteId(value)}
|
||||
enabledAllSite
|
||||
/>
|
||||
<Select
|
||||
value={keywordType}
|
||||
onValueChange={(value) => setKeywordType(value)}
|
||||
>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="authorizationName">API명</SelectItem>
|
||||
<SelectItem value="urlPatternValue">API URL</SelectItem>
|
||||
<SelectItem value="urlPatternValue">URL주소</SelectItem>
|
||||
<SelectItem value="httpMethodCode">전송방식</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
|
||||
@@ -11,6 +11,7 @@ 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 # 박이정
|
||||
|
||||
@@ -14,6 +14,7 @@ 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_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 # 박이정
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 = 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
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ const getRuseFclt = async (
|
||||
searchPk: item.bscFctNm,
|
||||
};
|
||||
})
|
||||
.filter((hasu) => ruseList.find((ruse) => hasu.bscFctCd === ruse));
|
||||
.filter((hasu:any) => ruseList.find((ruse:any) => hasu.bscFctCd === ruse));
|
||||
return { data: data, count: res.count };
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ 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
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ 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
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ 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 = http://10.98.25.94:18080/openapi/Gettile.do?apiKey=BDE2BEF39DAFEC293AB6730150F222AC
|
||||
NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"use client"
|
||||
import Base from "./base";
|
||||
import "./globals.css";
|
||||
import { StoreProvider } from "./store";
|
||||
|
||||
@@ -12,7 +11,7 @@ export default function RootLayout({
|
||||
<html lang="en">
|
||||
<body>
|
||||
<StoreProvider>
|
||||
<Base>{children}</Base>
|
||||
{children}
|
||||
</StoreProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client'
|
||||
import { NoFooterLayout } from "@common/components";
|
||||
import type { MenuItem } from "../../store/common";
|
||||
import { useFoStore } from "../../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 (
|
||||
<NoFooterLayout currentPath={pathname ?? ""} handleMenuClick={handleClick}>{children}</NoFooterLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ProcSearchBar } from "@common/business_components/ui/map/searchBar";
|
||||
|
||||
// OpenLayers overlay/extent 유틸
|
||||
import Overlay from "ol/Overlay";
|
||||
import { getCenter as getExtentCenter } from "ol/extent";
|
||||
import type { Coordinate } from "ol/coordinate";
|
||||
|
||||
interface FeatureData {
|
||||
fid?: string;
|
||||
ftrIdn?: string;
|
||||
id?: string;
|
||||
getId?: () => string | number | undefined; // 추가
|
||||
// OpenLayers Feature 비슷한 최소 인터페이스
|
||||
get?: (key: string) => any;
|
||||
getGeometry?: () => {
|
||||
getExtent: () => number[];
|
||||
getType?: () => string;
|
||||
getCoordinates?: () => Coordinate;
|
||||
};
|
||||
setStyle?: () => void;
|
||||
}
|
||||
|
||||
interface MapInstance {
|
||||
addOverlay?: (ov: Overlay) => void;
|
||||
removeOverlay?: (ov: Overlay) => void;
|
||||
getOverlays?: () => { getArray: () => Overlay[] };
|
||||
getLayers: () => {
|
||||
getArray: () => Array<{
|
||||
get: (key: string) => any;
|
||||
getSource?: () => { getFeatureById?: (id: string) => FeatureData | null };
|
||||
}>;
|
||||
};
|
||||
getView: () => { fit: (extent: number[], options: object) => void };
|
||||
}
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]);
|
||||
const gisMaps = useRef<MapInstance | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
|
||||
const [featureData, setFeatureData] = useState<FeatureData | null>(null);
|
||||
|
||||
// === Overlay 관리용 Ref ===
|
||||
const overlayRef = useRef<Overlay | null>(null);
|
||||
const overlayContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const facilityTypes = [
|
||||
{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
|
||||
];
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`;
|
||||
const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`;
|
||||
const response = await fetch(reqUrl, {
|
||||
method: "GET",
|
||||
headers: { accept: "application/json" },
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP 오류! 상태: ${response.status}`);
|
||||
const data = await response.json();
|
||||
setTreeData(data);
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// ===== 오버레이 콘텐츠 빌더 =====
|
||||
const buildOverlayContent = (layerType: string, feature: FeatureData) => {
|
||||
const props: Record<string, any> = {};
|
||||
// 안전하게 몇 개 필드 추출(없으면 건너뜀)
|
||||
const mayGet = (k: string) => (feature.get ? feature.get(k) : undefined);
|
||||
const safe = (v: any) => (v === undefined || v === null ? "-" : String(v));
|
||||
|
||||
console.log(feature);
|
||||
|
||||
// 레이어 유형별 대표 필드 추출(없으면 "-")
|
||||
if (layerType === "tb_il_swage_prcs_fclt") {
|
||||
// 하수처리시설
|
||||
props["하수처리시설명"] = mayGet("swtrmt_fclt_nm");
|
||||
props["시설용량(m³/d)"] = mayGet("fclt_cpct");
|
||||
props["주소"] = mayGet("rdnm_addr");
|
||||
props["가동개시일"] = mayGet("oprtng_strt_ymd");
|
||||
props["공법"] = mayGet("rprs_msrc_cn");
|
||||
} else if (layerType === "tb_ms_swage_prcs_zone") {
|
||||
// 처리구역(존)
|
||||
props["하수처리구역명"] = mayGet("swtrmt_zone_nm");
|
||||
props["하수처리시설명"] = mayGet("swtrmt_fclt_nm");
|
||||
} else if (layerType === "om_wqlt_station") {
|
||||
// 수질관측소
|
||||
props["관측소명"] = mayGet("stn_ko") ?? mayGet("name");
|
||||
props["주소"] = mayGet("addr") ?? mayGet("indicator");
|
||||
}
|
||||
|
||||
const titleMap: Record<string, string> = {
|
||||
tb_il_swage_prcs_fclt: "하수처리시설",
|
||||
tb_ms_swage_prcs_zone: "하수처리구역",
|
||||
om_wqlt_station: "관측소",
|
||||
};
|
||||
const title = titleMap[layerType] || "정보";
|
||||
|
||||
// 컨테이너 재사용(없으면 생성)
|
||||
if (!overlayContainerRef.current) {
|
||||
overlayContainerRef.current = document.createElement("div");
|
||||
}
|
||||
const el = overlayContainerRef.current;
|
||||
el.className =
|
||||
"ol-custom-overlay shadow-xl rounded-2xl bg-white/95 backdrop-blur p-3 border border-gray-200 min-w-[220px] max-w-[350px] ";
|
||||
el.innerHTML = `
|
||||
<div class="font-semibold text-lg text-gray-800 mb-2 text-center bg-green-400">${title}</div>
|
||||
<table class="w-full text-base text-gray-800">
|
||||
${Object.entries(props)
|
||||
.map(
|
||||
([k, v]) => `
|
||||
<tr>
|
||||
<td class="py-1 pr-2 text-gray-500 whitespace-nowrap">${k}</td>
|
||||
<td class="py-1 font-medium">${safe(v)}</td>
|
||||
</tr>`
|
||||
)
|
||||
.join("")}
|
||||
</table>
|
||||
`;
|
||||
return el;
|
||||
};
|
||||
|
||||
// ===== 오버레이 생성/표시 =====
|
||||
const showOverlay = (
|
||||
map: MapInstance,
|
||||
feature: FeatureData,
|
||||
layerType: string
|
||||
) => {
|
||||
// 이전 오버레이 제거
|
||||
if (overlayRef.current) {
|
||||
map.removeOverlay?.(overlayRef.current);
|
||||
overlayRef.current = null;
|
||||
}
|
||||
|
||||
// 콘텐츠 만들기
|
||||
const element = buildOverlayContent(layerType, feature);
|
||||
|
||||
// 앵커 좌표 계산(포인트면 좌표, 아니면 extent 중심)
|
||||
let position: Coordinate | undefined;
|
||||
const geom = feature.getGeometry?.();
|
||||
if (geom?.getType && geom.getType() === "Point" && geom.getCoordinates) {
|
||||
position = geom.getCoordinates();
|
||||
} else if (geom?.getExtent) {
|
||||
position = getExtentCenter(geom.getExtent());
|
||||
}
|
||||
|
||||
if (!position) return;
|
||||
|
||||
// 오버레이 생성 & 지도에 추가
|
||||
const overlay = new Overlay({
|
||||
element,
|
||||
positioning: "bottom-center",
|
||||
stopEvent: false,
|
||||
offset: [0, -12],
|
||||
});
|
||||
overlay.setPosition(position);
|
||||
map.addOverlay?.(overlay);
|
||||
overlayRef.current = overlay;
|
||||
};
|
||||
|
||||
// ===== 피처 선택 시 호출 =====
|
||||
const onFeatureSelect = (data: FeatureData) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
type SelectModule = {
|
||||
resetSelectedFeature: () => void;
|
||||
selectFeature: (feature: FeatureData, selected: boolean) => void;
|
||||
};
|
||||
|
||||
const maybeSelect = GisApp?.Module?.select; // 타입 그대로 둠 (any | unknown | null 가능)
|
||||
|
||||
if (!maybeSelect) {
|
||||
// null/undefined면 일찍 반환
|
||||
return;
|
||||
}
|
||||
|
||||
const select = maybeSelect as SelectModule; // 여기선 null 아님이 보장됨
|
||||
if (!select) return;
|
||||
|
||||
// 1) 기존 선택 초기화
|
||||
select.resetSelectedFeature();
|
||||
|
||||
// 2) 피처 탐색 + 레이어 유형 파악
|
||||
let featureId = String(data.getId?.() ?? "");
|
||||
let foundLayerType = featureId.split(".")[0];
|
||||
let foundFeature: FeatureData | null = null;
|
||||
const isOlFeatureInstance =
|
||||
data &&
|
||||
typeof data.getGeometry === "function" &&
|
||||
typeof data.setStyle === "function";
|
||||
|
||||
if (isOlFeatureInstance) {
|
||||
foundFeature = data;
|
||||
// 어떤 레이어에서 온 건지 모르는 경우가 많아 기본값으로 시도
|
||||
} else {
|
||||
map
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer) => {
|
||||
if (foundFeature) return; // 이미 찾았으면 skip
|
||||
const source = layer.getSource?.();
|
||||
const feature = source?.getFeatureById?.(featureId);
|
||||
if (feature) {
|
||||
foundFeature = feature;
|
||||
// 레이어 이름(유형) 우선순위: name -> lyrTblNm -> id
|
||||
foundLayerType =
|
||||
layer.get("name") || layer.get("lyrTblNm") || layer.get("id");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3) 피처 선택/줌 + 오버레이
|
||||
if (foundFeature && foundFeature.getGeometry) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
select.selectFeature(foundFeature, true);
|
||||
|
||||
// 표출 대상 레이어만 오버레이 표시
|
||||
const normalized = (foundLayerType || "").toString().trim();
|
||||
const TARGETS = [
|
||||
"tb_il_swage_prcs_fclt",
|
||||
"tb_ms_swage_prcs_zone",
|
||||
"om_wqlt_station",
|
||||
];
|
||||
|
||||
if (TARGETS.includes(normalized)) {
|
||||
showOverlay(map, foundFeature, normalized);
|
||||
} else {
|
||||
// 대상 외 레이어 클릭 시 기존 오버레이 제거
|
||||
if (overlayRef.current) {
|
||||
map.removeOverlay?.(overlayRef.current);
|
||||
overlayRef.current = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
|
||||
// 실패 시 기존 오버레이 제거
|
||||
if (overlayRef.current) {
|
||||
map.removeOverlay?.(overlayRef.current);
|
||||
overlayRef.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 우측 패널/검색 모달 상태
|
||||
setFeatureData(data);
|
||||
setIsOpen(false);
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
// (선택) 다른 위치 클릭 시 오버레이 닫고 싶다면 Gis 쪽에서 지도 클릭 이벤트 받아 여기서 제거하는 훅을 연결하면 됨.
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={undefined}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={undefined}
|
||||
imgVisble={false}
|
||||
searchBar={
|
||||
<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />
|
||||
}
|
||||
waterDepth={undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import { useFoStore } from "@/app/store";
|
||||
import { useEffect, useState } from "react";
|
||||
import SearchInput from "../../components/common/SearchInput";
|
||||
import SearchInput from "../../../components/common/SearchInput";
|
||||
|
||||
export default function DashBoardFclty({ onFacilityChange, sidoSigun, facilityNm, }: { onFacilityChange?: (facility: { bscFctCd: string, bscFctNm: string, fcltyCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | '' }) => void, sidoSigun?: string, facilityNm?: string }) {
|
||||
// 입력값 상태 관리
|
||||
+2
-2
@@ -4,8 +4,8 @@ import type { FacilityWaterQltyProps, modalExcelType } from "@/app/store/facilit
|
||||
import { downloadExcel, UITab, UiTable } from "@common/business_components/ui";
|
||||
import { Button, TabsContent } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { ReturnType } from "../../../components/common/FacilityTermModalHeader";
|
||||
import FacilityTermModalHeader from "../../../components/common/FacilityTermModalHeader";
|
||||
import type { ReturnType } from "../../../../components/common/FacilityTermModalHeader";
|
||||
import FacilityTermModalHeader from "../../../../components/common/FacilityTermModalHeader";
|
||||
import { HeadMakeColSpan } from "../HeadMakeColSpan";
|
||||
|
||||
type ModalProps ={
|
||||
+1
-1
@@ -5,7 +5,7 @@ import type { FlowRateModalInterface, modalExcelType } from "@/app/store/flowRat
|
||||
import { downloadExcel, UITab, UiTable } from "@common/business_components/ui";
|
||||
import { Button, TabsContent } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { ReturnType } from "../../../components/common/FacilityTermModalHeader";
|
||||
import type { ReturnType } from "../../../../components/common/FacilityTermModalHeader";
|
||||
|
||||
const tabList = [
|
||||
{ value: 'tab_term_year', label: '연간' },
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import Base from "@/app/base";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<Base>{children}</Base>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { TableUpperProps } from "@/app/store/common";
|
||||
import { TableHead, TableRow } from "@common/components/ui";
|
||||
import type { TableUpperProps } from "../../store/dashboard";
|
||||
|
||||
const HeadMakeColSpan = ({ upHeadList, layoutNum }: { upHeadList: TableUpperProps[], layoutNum: number }) => {
|
||||
return (
|
||||
@@ -1,152 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
|
||||
interface FeatureData {
|
||||
fid?: string;
|
||||
ftrIdn?: string;
|
||||
id?: string;
|
||||
getGeometry?: () => { getExtent: () => number[] };
|
||||
setStyle?: () => void;
|
||||
}
|
||||
|
||||
interface MapInstance {
|
||||
getLayers: () => {
|
||||
getArray: () => Array<{
|
||||
getSource?: () => { getFeatureById?: (id: string) => FeatureData | null };
|
||||
}>;
|
||||
};
|
||||
getView: () => { fit: (extent: number[], options: object) => void };
|
||||
}
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const gisMaps = useRef<MapInstance | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState<FeatureData | null>(null); // 선택된 피쳐 데이터
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`;
|
||||
const reqUrl = `${API_URL}/lyrStngs/FO_NETWORK`;
|
||||
const response = await fetch(reqUrl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP 오류! 상태: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json(); // 여기서 json 파싱
|
||||
setTreeData(data); // 데이터 설정
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
// SearchModal, GisContent 등에서 호출
|
||||
const onFeatureSelect = (data: FeatureData) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 항상 같은 select 객체 사용
|
||||
const select = GisApp.Module.select as unknown as
|
||||
| {
|
||||
resetSelectedFeature: () => void;
|
||||
selectFeature: (feature: FeatureData, selected: boolean) => void;
|
||||
}
|
||||
| undefined;
|
||||
if (!select) return;
|
||||
|
||||
// 1. 기존 선택 초기화
|
||||
select.resetSelectedFeature();
|
||||
|
||||
// 2. data가 OpenLayers Feature 인스턴스인지 확인
|
||||
let foundFeature: FeatureData | null = null;
|
||||
if (
|
||||
data &&
|
||||
typeof data.getGeometry === "function" &&
|
||||
typeof data.setStyle === "function"
|
||||
) {
|
||||
// 이미 Feature 인스턴스라면 그대로 사용
|
||||
foundFeature = data;
|
||||
} else {
|
||||
// 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용
|
||||
const featureId = data.fid || data.ftrIdn || data.id;
|
||||
map
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer) => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) foundFeature = feature;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 피처 선택
|
||||
if (foundFeature && foundFeature.getGeometry) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
select.selectFeature(foundFeature, true);
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
|
||||
}
|
||||
|
||||
setFeatureData(data);
|
||||
setIsOpen(true);
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={undefined}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={undefined}
|
||||
imgVisble={false}
|
||||
searchBar={() => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />}
|
||||
waterDepth={undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -17,7 +17,10 @@
|
||||
"next": "15.1.7",
|
||||
"select": "^1.1.2",
|
||||
"webpack": "^5.98.0",
|
||||
"zustand": "^5.0.3"
|
||||
"zustand": "^5.0.3",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"proj4": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
||||
+2
-1
@@ -13,4 +13,5 @@ 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
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.222:8883/sewerage-service/api
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.222:8883/sewerage-service/api
|
||||
#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api
|
||||
@@ -3,7 +3,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { BaseLayout } from "@common/components";
|
||||
import { NoFooterLayout } from "@common/components";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import useStore from '../store/useStore';
|
||||
@@ -33,12 +33,12 @@ export default function MapLayout({
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
<NoFooterLayout
|
||||
queryClient={queryClient}
|
||||
currentPath={pathname ?? ""}
|
||||
handleMenuClick={handleMenuClick}
|
||||
>
|
||||
{children}
|
||||
</BaseLayout>
|
||||
</NoFooterLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ export const MsSwageMhlCol = [
|
||||
// { id: 'usg_cd', title: '용도코드' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'mhl_knd_cd', title: '맨홀종류코드' },
|
||||
{ id: 'swage_mh_diam', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'swage_mhL_diam_vl', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
@@ -396,7 +396,7 @@ export const MsSwageMhlCol2 = [
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
// { id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
// { id: 'mhlKndCd', title: '맨홀종류코드' },
|
||||
{ id: 'swageMhDiam', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'swageMhlDiamVl', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'mqltCd', title: '재질코드' },
|
||||
{ id: 'wdthLen', title: '가로길이(m)' },
|
||||
{ id: 'vrtcLen', title: '세로길이(m)' },
|
||||
@@ -487,7 +487,7 @@ export const ExclnSewagePumpCol = [
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt', title: '우수양수능력(m²/d)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const ExclnSewagePumpCol2 = [
|
||||
@@ -505,10 +505,10 @@ export const ExclnSewagePumpCol2 = [
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAblt', title: '우수양수능력(m²/d)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
// 오수펌프장
|
||||
@@ -529,7 +529,7 @@ export const DrtwSewagePumpCol = [
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt', title: '오수양수능력(m²/d)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const DrtwSewagePumpCol2 = [
|
||||
@@ -547,10 +547,10 @@ export const DrtwSewagePumpCol2 = [
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAblt', title: '오수양수능력(m²/d)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
// 펌프장 용도 코드표
|
||||
|
||||
@@ -5,13 +5,9 @@ import moment from "moment";
|
||||
export type TInftwtAnls = {
|
||||
inftwtAnlsSn:number;
|
||||
aplcnYm: string;
|
||||
enoCd: string;
|
||||
ctpvCd: string;
|
||||
sggCd: string;
|
||||
swageTmplntCd: string;
|
||||
swageTmplntNm: string;
|
||||
swtrmtZoneCd: string;
|
||||
swtrmtZoneNm: string;
|
||||
swtrmtFcltNm: string;
|
||||
atflCd: string;
|
||||
atflId: string;
|
||||
atflInpNm: string;
|
||||
@@ -32,13 +28,9 @@ export type TInftwtAnlsRslt = {
|
||||
export type TRdiiAnls = {
|
||||
rainInfwtAnlsSn:number;
|
||||
aplcnYm: string;
|
||||
enoCd: string;
|
||||
ctpvCd: string;
|
||||
sggCd: string;
|
||||
swageTmplntCd: string;
|
||||
swageTmplntNm: string;
|
||||
swtrmtZoneCd: string;
|
||||
swtrmtZoneNm: string;
|
||||
swtrmtFcltNm: string;
|
||||
atflCd: string;
|
||||
atflId: string;
|
||||
atflInpNm: string;
|
||||
|
||||
@@ -52,6 +52,7 @@ type BaseLayout = {
|
||||
setCtpvCd: (ctpv: number) => void;
|
||||
setSggCd: (sgg: number) => void;
|
||||
setSggName?: (sggName: string) => void;
|
||||
setSwtrmtFcltNm?: (swtrmtFcltNm: string) => void;
|
||||
setIsLoading?: (isLoading: boolean) => void;
|
||||
onExcelDownload?: () => void;
|
||||
onDeleteFile?: () => void;
|
||||
@@ -81,6 +82,7 @@ const BaseLayout = ({
|
||||
setCtpvCd,
|
||||
setSggCd,
|
||||
setSggName,
|
||||
setSwtrmtFcltNm,
|
||||
setIsLoading,
|
||||
onExcelDownload,
|
||||
onDeleteFile,
|
||||
@@ -184,6 +186,7 @@ const BaseLayout = ({
|
||||
setSggName={setSggName}
|
||||
selectedSwagePrcsFclt={selectedSwagePrcsFclt}
|
||||
onSwagePrcsFcltChange={onSwagePrcsFcltChange}
|
||||
setSwtrmtFcltNm={setSwtrmtFcltNm}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
{selectBox?.searchChk && (
|
||||
|
||||
@@ -18,6 +18,7 @@ import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
import LoadingOverlay from '@common/business_components/ui/loadingBar';
|
||||
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -55,6 +56,7 @@ const IrSetting = () => {
|
||||
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
|
||||
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
|
||||
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
|
||||
const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>(''); // 하수처리시설명 상태
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
@@ -115,6 +117,7 @@ const IrSetting = () => {
|
||||
setInput1(''); // 입력 필드 초기화
|
||||
setInput2(''); // 입력 필드 초기화
|
||||
setSelect1(0);
|
||||
setSwtrmtFcltNm('');
|
||||
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
|
||||
};
|
||||
|
||||
@@ -156,6 +159,7 @@ const IrSetting = () => {
|
||||
|
||||
// 침입수 분석 실행 버튼 클릭 핸들러
|
||||
const handleAnalysisClick = async () => {
|
||||
console.log('분석 시작');
|
||||
setIsLoadingResult(true); // 로딩 상태 시작
|
||||
|
||||
// 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
|
||||
@@ -296,9 +300,7 @@ const IrSetting = () => {
|
||||
const combinedAnalysisData = {
|
||||
ctpvCd: (ctpvCd ? String(ctpvCd) : null),
|
||||
sggCd: (sggCd ? String(sggCd) : null), // 시군구코드 (선택값)
|
||||
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
|
||||
? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
|
||||
: null, // 하수처리장코드 (선택값)
|
||||
swtrmtFcltNm: swtrmtFcltNm ?? null,
|
||||
aplcnYm: selectedDate, // 적용연월
|
||||
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
|
||||
anlsInfo: input2, // 분석정보
|
||||
@@ -330,6 +332,7 @@ const IrSetting = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LoadingOverlay isLoading={isLoadingResult} />
|
||||
<BaseLayout
|
||||
titleName={{ mainTitle: '침입수 분석', firstCardTitle: '침입수 분석 자료 입력' }}
|
||||
form={form}
|
||||
@@ -345,6 +348,7 @@ const IrSetting = () => {
|
||||
setCtpvCd={setCtpvCd}
|
||||
setSggCd={setSggCd}
|
||||
setSggName={setSggName}
|
||||
setSwtrmtFcltNm={setSwtrmtFcltNm}
|
||||
setIsLoading={setIsLoadingResult}
|
||||
onAnalysis={handleAnalysisClick}
|
||||
>
|
||||
|
||||
@@ -20,15 +20,15 @@ import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
import LoadingOverlay from '@common/business_components/ui/loadingBar';
|
||||
|
||||
|
||||
|
||||
// zod 스키마 정의
|
||||
const formSchema = z.object({
|
||||
enoCd: z.string(), // 환경청코드
|
||||
ctpvCd: z.string(), // 시도코드
|
||||
sggCd: z.string(), // 시군구코드
|
||||
swageTmplntCd: z.string(), // 하수처리장코드
|
||||
swtrmtZoneCd: z.string(), // 하수처리구역코드
|
||||
swtrmtFcltNm: z.string(), // 하수처리시설명
|
||||
rtkSource: z.string(), // RTK계수 출처
|
||||
rdiiArea: z.string(), // RDII 기여면적
|
||||
unitFactors: z.array(z.object({ // 단위도 계수
|
||||
@@ -56,11 +56,9 @@ const formSchema = z.object({
|
||||
type FormSchemaType = z.infer<typeof formSchema>;
|
||||
// 기본 폼 값 정의 (테스트 편의를 위해 초기값 설정, 추후 삭제 필요)
|
||||
const defaultFormValues: FormSchemaType = {
|
||||
enoCd: "",
|
||||
ctpvCd: "",
|
||||
sggCd: "",
|
||||
swageTmplntCd: "",
|
||||
swtrmtZoneCd: "",
|
||||
swtrmtFcltNm: "",
|
||||
rtkSource: "",
|
||||
rdiiArea: "", // 문자열로 기본값 설정
|
||||
unitFactors: [
|
||||
@@ -114,10 +112,11 @@ export default function RTKForm() {
|
||||
initialFileInfo, // 2: 강우자료
|
||||
]);
|
||||
|
||||
const [select1, setSelect1] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
|
||||
const [select1, setSelect1] = useState<string | number>('0'); // 하수처리장 (swtrmtFcltCd)
|
||||
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
|
||||
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
|
||||
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
|
||||
const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>(''); // 하수처리시설명 상태
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
@@ -293,9 +292,7 @@ export default function RTKForm() {
|
||||
aplcnYm: formData.date, // 적용연월 (YYYYMM 형식)
|
||||
ctpvCd: ctpvCd ? String(ctpvCd) : null, // 시도코드 (선택값)
|
||||
sggCd: sggCd ? String(sggCd) : null, // 시군구코드 (선택값)
|
||||
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
|
||||
? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
|
||||
: null, // 하수처리장코드 (선택값)
|
||||
swtrmtFcltNm: swtrmtFcltNm ?? null,
|
||||
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
|
||||
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
|
||||
atflInpNm: file1?.file?.name || "", // 오수관망도 파일명
|
||||
@@ -324,9 +321,9 @@ export default function RTKForm() {
|
||||
mdPrdRcvryRt: parseFloat(formData.initialDepths[1]?.Drec || "0"),
|
||||
mdPrdBgngDpth: parseFloat(formData.initialDepths[1]?.Do || "0"),
|
||||
|
||||
longTermMaxDpth: parseFloat(formData.initialDepths[2]?.Dmax || "0"),
|
||||
longTermRcvryRt: parseFloat(formData.initialDepths[2]?.Drec || "0"),
|
||||
longTermBgngDpth: parseFloat(formData.initialDepths[2]?.Do || "0"),
|
||||
longPrdMaxDpth: parseFloat(formData.initialDepths[2]?.Dmax || "0"),
|
||||
longPrdRcvryRt: parseFloat(formData.initialDepths[2]?.Drec || "0"),
|
||||
longPrdBgngDpth: parseFloat(formData.initialDepths[2]?.Do || "0"),
|
||||
|
||||
drtwSwtcRt: parseFloat(formData.rate || "0"), // 오수전환율
|
||||
anlsInfo: formData.analysisInfo, // 분석정보
|
||||
@@ -386,140 +383,144 @@ export default function RTKForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
selectBox={{ title: '분석 대상지역 선택' }}
|
||||
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
|
||||
form={form} // react-hook-form 인스턴스 전달
|
||||
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
|
||||
graphBtn={false}
|
||||
analysisBtn={'RDII분석 실행'}
|
||||
ctpvCd={ctpvCd}
|
||||
sggCd={sggCd}
|
||||
sggName={sggName}
|
||||
selectedSwagePrcsFclt={select1}
|
||||
onSwagePrcsFcltChange={setSelect1}
|
||||
setCtpvCd={setCtpvCd}
|
||||
setSggCd={setSggCd}
|
||||
setSggName={setSggName}
|
||||
setIsLoading={setIsLoadingResult}
|
||||
onAnalysis={handleAnalysisClick}
|
||||
>
|
||||
{[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처">
|
||||
<Input {...form.register("rtkSource")} className="h-12 text-center text-xl w-[225px]" />
|
||||
</FormSection>
|
||||
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)">
|
||||
<Input {...form.register("rdiiArea")} className="h-12 text-center text-xl w-[225px]" />
|
||||
</FormSection></div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 text-xl font-medium">○ 단위도 계수</Label>
|
||||
<EditableFormTable
|
||||
form={form}
|
||||
name="unitFactors"
|
||||
fields={unitFactorFields}
|
||||
headlist={unitFactorHeadlist}
|
||||
tableClassName="w-[400px]"
|
||||
/>
|
||||
<>
|
||||
<LoadingOverlay isLoading={isLoadingResult} />
|
||||
<BaseLayout
|
||||
selectBox={{ title: '분석 대상지역 선택' }}
|
||||
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
|
||||
form={form} // react-hook-form 인스턴스 전달
|
||||
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
|
||||
graphBtn={false}
|
||||
analysisBtn={'RDII분석 실행'}
|
||||
ctpvCd={ctpvCd}
|
||||
sggCd={sggCd}
|
||||
sggName={sggName}
|
||||
selectedSwagePrcsFclt={select1}
|
||||
onSwagePrcsFcltChange={setSelect1}
|
||||
setCtpvCd={setCtpvCd}
|
||||
setSggCd={setSggCd}
|
||||
setSggName={setSggName}
|
||||
setSwtrmtFcltNm={setSwtrmtFcltNm}
|
||||
setIsLoading={setIsLoadingResult}
|
||||
onAnalysis={handleAnalysisClick}
|
||||
>
|
||||
{[<div className="space-y-[1.2rem] overflow-x-auto pt-4 pb-4 px-2" key="form-section">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="rtkSource" form={form} titleNm="○ RTK계수 출처">
|
||||
<Input {...form.register("rtkSource")} className="h-12 text-center text-xl w-[225px]" />
|
||||
</FormSection>
|
||||
<FormSection name="rdiiArea" form={form} titleNm="○ RDII 기여면적(ha)">
|
||||
<Input {...form.register("rdiiArea")} className="h-12 text-center text-xl w-[225px]" />
|
||||
</FormSection></div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<Label className="mb-2 text-xl font-medium">○ 단위도 계수</Label>
|
||||
<EditableFormTable
|
||||
form={form}
|
||||
name="unitFactors"
|
||||
fields={unitFactorFields}
|
||||
headlist={unitFactorHeadlist}
|
||||
tableClassName="w-[400px]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-[1.2rem] text-xl font-medium">○ 초기 차단 깊이</Label>
|
||||
<EditableFormTable
|
||||
form={form}
|
||||
name="initialDepths"
|
||||
fields={initialDepthFields}
|
||||
headlist={initialDepthHeadlist}
|
||||
tableClassName="w-[400px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="mb-[1.2rem] text-xl font-medium">○ 초기 차단 깊이</Label>
|
||||
<EditableFormTable
|
||||
form={form}
|
||||
name="initialDepths"
|
||||
fields={initialDepthFields}
|
||||
headlist={initialDepthHeadlist}
|
||||
tableClassName="w-[400px]"
|
||||
/>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".inp"}
|
||||
fileIdx={0} />
|
||||
</div></FormSection>
|
||||
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".xls, .xlsx, .xlsm, .xltx"}
|
||||
fileIdx={1} />
|
||||
</div></FormSection>
|
||||
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".xls, .xlsx, .xlsm, .xltx"}
|
||||
fileIdx={2} />
|
||||
</div></FormSection>
|
||||
<FormSection name='rate' form={form} titleNm='○ 오수전환율'>
|
||||
<Input
|
||||
value={form.watch("rate")} // form의 rate 값을 가져옴
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
if (newValue === "") {
|
||||
form.setValue("rate", newValue); // form의 rate 필드에 설정
|
||||
return;
|
||||
}
|
||||
|
||||
// 숫자와 '.'만 허용하고, '.'은 최대 하나인지 확인
|
||||
const isValidChars = /^[0-9]*\.?[0-9]*$/.test(newValue);
|
||||
if (!isValidChars) return; // 유효하지 않은 문자 또는 여러 개의 '.' 포함
|
||||
|
||||
// '.' 기호가 처음에 오면 안 됨
|
||||
if (newValue.startsWith('.')) return;
|
||||
|
||||
// 완성된 숫자인 경우에만 범위 체크 (입력 중인 경우 허용)
|
||||
if (!newValue.endsWith('.')) {
|
||||
const numericValue = parseFloat(newValue);
|
||||
if (isNaN(numericValue) || numericValue < 0 || numericValue > 1) return;
|
||||
}
|
||||
|
||||
form.setValue("rate", newValue, { shouldValidate: true }); // form의 rate 필드에 설정
|
||||
}}
|
||||
className="h-12 text-center text-xl w-[225px] ml-5"
|
||||
placeholder="0 ~ 1 사이의 값 입력"
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용월'>
|
||||
<DateMonthPicker
|
||||
field={{
|
||||
value: form.watch("date"), // form의 date 값을 직접 사용
|
||||
onChange: (value: string) => {
|
||||
form.setValue("date", value, { shouldValidate: true }); // form의 date 필드에 직접 설정
|
||||
}
|
||||
}}
|
||||
selectClass="ml-5 w-[225px]"
|
||||
startYear={1900} // 시작 연도 설정
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection name='rate' form={form} titleNm='○ 분석 정보'>
|
||||
<Input
|
||||
{...form.register("analysisInfo")}
|
||||
className="h-12 text-center text-xl w-[225px] ml-5"
|
||||
placeholder='세부 분석정보 입력'
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault(); // Enter 키 기본 동작 방지
|
||||
handleAnalysisClick();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormSection>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormSection name="suyeonga" form={form} titleNm="○ 오수관망도"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-0-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(0, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".inp"}
|
||||
fileIdx={0} />
|
||||
</div></FormSection>
|
||||
<FormSection name="regopm" form={form} titleNm="○ 수용가-맨홀 매칭결과"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-1-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(1, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".xls, .xlsx, .xlsm, .xltx"}
|
||||
fileIdx={1} />
|
||||
</div></FormSection>
|
||||
<FormSection name="rain" form={form} titleNm="○ 강우자료"><div className="w-[350px]">
|
||||
<FileUploadForm
|
||||
key={`file-upload-2-${formResetKey}`} // 폼 초기화를 위한 key
|
||||
onFileSelect={(file, cd, id) => handleFileSelection(2, file, cd, id)}
|
||||
showButton={false}
|
||||
showPreview={true}
|
||||
accept={".xls, .xlsx, .xlsm, .xltx"}
|
||||
fileIdx={2} />
|
||||
</div></FormSection>
|
||||
<FormSection name='rate' form={form} titleNm='○ 오수전환율'>
|
||||
<Input
|
||||
value={form.watch("rate")} // form의 rate 값을 가져옴
|
||||
onChange={(e) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
if (newValue === "") {
|
||||
form.setValue("rate", newValue); // form의 rate 필드에 설정
|
||||
return;
|
||||
}
|
||||
|
||||
// 숫자와 '.'만 허용하고, '.'은 최대 하나인지 확인
|
||||
const isValidChars = /^[0-9]*\.?[0-9]*$/.test(newValue);
|
||||
if (!isValidChars) return; // 유효하지 않은 문자 또는 여러 개의 '.' 포함
|
||||
|
||||
// '.' 기호가 처음에 오면 안 됨
|
||||
if (newValue.startsWith('.')) return;
|
||||
|
||||
// 완성된 숫자인 경우에만 범위 체크 (입력 중인 경우 허용)
|
||||
if (!newValue.endsWith('.')) {
|
||||
const numericValue = parseFloat(newValue);
|
||||
if (isNaN(numericValue) || numericValue < 0 || numericValue > 1) return;
|
||||
}
|
||||
|
||||
form.setValue("rate", newValue, { shouldValidate: true }); // form의 rate 필드에 설정
|
||||
}}
|
||||
className="h-12 text-center text-xl w-[225px] ml-5"
|
||||
placeholder="0 ~ 1 사이의 값 입력"
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection name='date' form={form} titleNm='○ 상수사용량 적용월'>
|
||||
<DateMonthPicker
|
||||
field={{
|
||||
value: form.watch("date"), // form의 date 값을 직접 사용
|
||||
onChange: (value: string) => {
|
||||
form.setValue("date", value, { shouldValidate: true }); // form의 date 필드에 직접 설정
|
||||
}
|
||||
}}
|
||||
selectClass="ml-5 w-[225px]"
|
||||
startYear={1900} // 시작 연도 설정
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection name='rate' form={form} titleNm='○ 분석 정보'>
|
||||
<Input
|
||||
{...form.register("analysisInfo")}
|
||||
className="h-12 text-center text-xl w-[225px] ml-5"
|
||||
placeholder='세부 분석정보 입력'
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault(); // Enter 키 기본 동작 방지
|
||||
handleAnalysisClick();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormSection>
|
||||
</div>
|
||||
</div>
|
||||
]}
|
||||
</BaseLayout >
|
||||
]}
|
||||
</BaseLayout >
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ import { z } from 'zod'
|
||||
import BaseLayout from '../BaseLayout'
|
||||
import { handleDelete } from '@/components/lib/utils'
|
||||
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
|
||||
import useStore from '../../../store/useStore';
|
||||
|
||||
|
||||
const formSchema = z.object({
|
||||
swageTmplntCd: z.string().default('0'), // 하수처리장
|
||||
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
|
||||
swtrmtFcltCd: z.string().default('0'), // 하수처리시설코드
|
||||
swtrmtFcltNm: z.string().default(''), // 하수처리시설명
|
||||
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -32,11 +33,9 @@ type ResultFormValues = z.infer<typeof formSchema>
|
||||
|
||||
const tableColumn = [
|
||||
{ id: 'rowNumber', title: '일련번호' },
|
||||
{ id: 'enoNm', title: '환경청' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'swageTmplntNm', title: '하수처리장' },
|
||||
// { id: 'swtrmtZoneNm', title: '처리구역' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설' },
|
||||
{ id: 'aplcnYm', title: '상수사용연월' },
|
||||
{ id: 'drtwSwtcRt', title: '오수 전환율' },
|
||||
{ id: 'srcNm', title: 'RTK 계수 출처' },
|
||||
@@ -53,8 +52,7 @@ const Result = () => {
|
||||
const form = useForm<ResultFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
swageTmplntCd: '0',
|
||||
swtrmtZoneCd: '0',
|
||||
swtrmtFcltCd: '0',
|
||||
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -83,6 +81,8 @@ const Result = () => {
|
||||
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
|
||||
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
|
||||
|
||||
const {ctpv, sgg} = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
// setCurPage(1);
|
||||
handleSearchButtonClick();
|
||||
@@ -109,22 +109,13 @@ const Result = () => {
|
||||
const formValues = form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
console.log("formValues", formValues);
|
||||
|
||||
// swageTmplntCd 처리 로직 추가
|
||||
const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0'
|
||||
? (() => {
|
||||
const valString = String(formValues.swageTmplntCd);
|
||||
const parts = valString.split('.');
|
||||
return parts.length > 1 ? parts[1] : valString;
|
||||
})()
|
||||
: null;
|
||||
|
||||
let inftwtAnlsSearchDto = {
|
||||
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
|
||||
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
|
||||
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
|
||||
"sggCd": sggCd === 0 ? null : sggCd,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
"auth": AUTH,
|
||||
const inftwtAnlsSearchDto = {
|
||||
aplcnYmFrom: formValues.aplcnYmFrom?.trim() ? formValues.aplcnYmFrom : null,
|
||||
aplcnYmTo: formValues.aplcnYmTo?.trim() ? formValues.aplcnYmTo : null,
|
||||
ctpvCd: ctpvCd || null,
|
||||
sggCd: sggCd || null,
|
||||
swtrmtFcltNm: formValues.swtrmtFcltNm?.trim() ? formValues.swtrmtFcltNm : null,
|
||||
auth: AUTH,
|
||||
};
|
||||
|
||||
console.log("Search DTO:", inftwtAnlsSearchDto);
|
||||
@@ -149,12 +140,15 @@ const Result = () => {
|
||||
const dataWithRowNumber = (data.content || []).map((item: any, index: number) => {
|
||||
// 🎯 먼저 각 아이템을 포맷팅
|
||||
const formattedItem = formatDisplayData(item);
|
||||
|
||||
const ctpvOne = ctpv.find(code => code.id === item.ctpvCd);
|
||||
const sggOne = sgg.find(code => code.id === item.sggCd);
|
||||
// 추가 처리 및 일련번호 추가
|
||||
return {
|
||||
...formattedItem,
|
||||
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
|
||||
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
|
||||
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn, // scsYn이 null이면 'N'으로 변경
|
||||
ctpvNm: ctpvOne ? ctpvOne.name : item.ctpvCd,
|
||||
sggNm: sggOne ? sggOne.name : item.sggCd,
|
||||
};
|
||||
})
|
||||
.filter((item: any) => item.scsYn === 'Y');
|
||||
@@ -211,7 +205,7 @@ const Result = () => {
|
||||
};
|
||||
|
||||
// 파일명 생성 (분석번호 기반)
|
||||
const fileName = `RDII분석결과_${selectedRowData.swageTmplntNm}_${selectedRowData.aplcnYm}_${selectedRowData.rainInfwtAnlsSn}`;
|
||||
const fileName = `RDII분석결과_${selectedRowData.swtrmtFcltNm}_${selectedRowData.aplcnYm}_${selectedRowData.rainInfwtAnlsSn}`;
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelData as JSON[],
|
||||
@@ -286,8 +280,8 @@ const Result = () => {
|
||||
ctpvCd={ctpvCd}
|
||||
sggCd={sggCd}
|
||||
sggName={sggName}
|
||||
selectedSwagePrcsFclt={form.watch("swageTmplntCd")}
|
||||
onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))}
|
||||
selectedSwagePrcsFclt={form.watch("swtrmtFcltCd")}
|
||||
onSwagePrcsFcltChange={(val) => form.setValue("swtrmtFcltCd", String(val))}
|
||||
aplcnYmFrom={form.watch("aplcnYmFrom")}
|
||||
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
|
||||
aplcnYmTo={form.watch("aplcnYmTo")}
|
||||
@@ -295,6 +289,7 @@ const Result = () => {
|
||||
setCtpvCd={setCtpvCd}
|
||||
setSggCd={setSggCd}
|
||||
setSggName={setSggName}
|
||||
setSwtrmtFcltNm={(val: string) => { form.setValue("swtrmtFcltNm", val) }}
|
||||
setIsLoading={setIsLoadingResult}
|
||||
// Excel 다운로드 핸들러 추가
|
||||
onExcelDownload={handleExcelDownload}
|
||||
|
||||
@@ -8,11 +8,11 @@ import { z } from 'zod'
|
||||
import BaseLayout from '../BaseLayout'
|
||||
import { handleDelete } from '@/components/lib/utils'
|
||||
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
|
||||
|
||||
import useStore from '../../../store/useStore';
|
||||
|
||||
const formSchema = z.object({
|
||||
swageTmplntCd: z.string().default('0'), // 하수처리장
|
||||
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
|
||||
swtrmtFcltCd: z.string().default('0'), // 하수처리시설코드
|
||||
swtrmtFcltNm: z.string().default(''), // 하수처리시설명
|
||||
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -32,11 +32,9 @@ type ResultFormValues = z.infer<typeof formSchema>
|
||||
|
||||
const tableColumn = [
|
||||
{ id: 'rowNumber', title: '일련번호' },
|
||||
{ id: 'enoNm', title: '환경청' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'swageTmplntNm', title: '하수처리장' },
|
||||
// { id: 'swtrmtZoneNm', title: '처리구역' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설' },
|
||||
{ id: 'aplcnYm', title: '적용연월' },
|
||||
{ id: 'drtwSwtcRt', title: '오수 전환율' },
|
||||
{ id: 'anlsInfo', title: '침입수 분석정보' },
|
||||
@@ -51,8 +49,8 @@ const Result = () => {
|
||||
const form = useForm<ResultFormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
swageTmplntCd: '0',
|
||||
swtrmtZoneCd: '0',
|
||||
swtrmtFcltCd: '0',
|
||||
swtrmtFcltNm: '',
|
||||
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
@@ -79,6 +77,7 @@ const Result = () => {
|
||||
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
|
||||
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
|
||||
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
|
||||
const {ctpv, sgg} = useStore();
|
||||
|
||||
useEffect(() => {
|
||||
// setCurPage(1);
|
||||
@@ -104,23 +103,12 @@ const Result = () => {
|
||||
const formValues = form.getValues(); // react-hook-form에서 현재 값 가져오기
|
||||
console.log("formValues", formValues);
|
||||
|
||||
|
||||
|
||||
// swageTmplntCd 처리 로직 추가
|
||||
const processedSwageTmplntCd = formValues.swageTmplntCd && formValues.swageTmplntCd !== '0'
|
||||
? (() => {
|
||||
const valString = String(formValues.swageTmplntCd);
|
||||
const parts = valString.split('.');
|
||||
return parts.length > 1 ? parts[1] : valString;
|
||||
})()
|
||||
: null;
|
||||
|
||||
let inftwtAnlsSearchDto = {
|
||||
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
|
||||
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
|
||||
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
|
||||
"sggCd": sggCd === 0 ? null : sggCd,
|
||||
"swageTmplntCd": processedSwageTmplntCd,
|
||||
"swtrmtFcltNm": formValues.swtrmtFcltNm?.trim() ? formValues.swtrmtFcltNm : null,
|
||||
"auth": AUTH,
|
||||
};
|
||||
|
||||
@@ -147,12 +135,15 @@ const Result = () => {
|
||||
const dataWithRowNumber = (data.content || []).map((item: any, index: number) => {
|
||||
// 🎯 먼저 각 아이템을 포맷팅
|
||||
const formattedItem = formatDisplayData(item);
|
||||
|
||||
const ctpvOne = ctpv.find(code => code.id === item.ctpvCd);
|
||||
const sggOne = sgg.find(code => code.id === item.sggCd);
|
||||
// 추가 처리 및 일련번호 추가
|
||||
return {
|
||||
...formattedItem,
|
||||
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
|
||||
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
|
||||
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn, // scsYn이 null이면 'N'으로 변경
|
||||
ctpvNm: ctpvOne ? ctpvOne.name : item.ctpvCd,
|
||||
sggNm: sggOne ? sggOne.name : item.sggCd,
|
||||
};
|
||||
})
|
||||
.filter((item: any) => item.scsYn === 'Y');
|
||||
@@ -209,7 +200,7 @@ const Result = () => {
|
||||
};
|
||||
|
||||
// 파일명 생성 (분석번호 기반)
|
||||
const fileName = `침입수분석결과_${selectedRowData.swageTmplntNm}_${selectedRowData.aplcnYm}_${selectedRowData.inftwtAnlsSn}`;
|
||||
const fileName = `침입수분석결과_${selectedRowData.swtrmtFcltNm}_${selectedRowData.aplcnYm}_${selectedRowData.inftwtAnlsSn}`;
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelData as JSON[],
|
||||
@@ -285,8 +276,8 @@ const Result = () => {
|
||||
ctpvCd={ctpvCd}
|
||||
sggCd={sggCd}
|
||||
sggName={sggName}
|
||||
selectedSwagePrcsFclt={form.watch("swageTmplntCd")}
|
||||
onSwagePrcsFcltChange={(val) => form.setValue("swageTmplntCd", String(val))}
|
||||
selectedSwagePrcsFclt={form.watch("swtrmtFcltCd")}
|
||||
onSwagePrcsFcltChange={(val) => form.setValue("swtrmtFcltCd", String(val))}
|
||||
aplcnYmFrom={form.watch("aplcnYmFrom")}
|
||||
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
|
||||
aplcnYmTo={form.watch("aplcnYmTo")}
|
||||
@@ -294,8 +285,8 @@ const Result = () => {
|
||||
setCtpvCd={setCtpvCd}
|
||||
setSggCd={setSggCd}
|
||||
setSggName={setSggName}
|
||||
setSwtrmtFcltNm={(val: string) => { form.setValue("swtrmtFcltNm", val) }}
|
||||
setIsLoading={setIsLoadingResult}
|
||||
// Excel 다운로드 핸들러 추가
|
||||
onExcelDownload={handleExcelDownload}
|
||||
onDeleteFile={handleFileDelete}
|
||||
selectedRowData={selectedRowData}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { deleteData, postData } from '@/components/lib/api'
|
||||
import { deleteData, postData, useCookieAuth } from '@/components/lib/api'
|
||||
import { searchData } from '@/components/lib/common/apiHelpers'
|
||||
import { formatDate, handleDownload } from '@/components/lib/utils'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
@@ -9,9 +9,16 @@ import { Button, DatePicker, Input, Label } from '@common/components/ui'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { tableHeadlist } from '..'
|
||||
import OperFileUploadForm from '../../../components/ui/operFileUploadForm'
|
||||
import { tokenManager } from '@/entities/auth/lib/token-manager';
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
|
||||
|
||||
const Page = () => {
|
||||
const AUTH = useCookieAuth();
|
||||
|
||||
// 조회조건
|
||||
const [fromDate, setFromDate] = useState(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date })
|
||||
const [toDate, setToDate] = useState(new Date())
|
||||
@@ -29,6 +36,12 @@ const Page = () => {
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(false);
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
queryFn: () => getUserInfo(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurPage(1)
|
||||
fetchData()
|
||||
@@ -57,7 +70,7 @@ const Page = () => {
|
||||
fromRegDt: formatDate(fromDate),
|
||||
toRegDt: formatDate(toDate),
|
||||
sbmrgPrevntBizNm: pjNm,
|
||||
rgtrNm: usr,
|
||||
auth: AUTH,
|
||||
page: curPage - 1,
|
||||
size: '10'
|
||||
})
|
||||
@@ -109,14 +122,17 @@ const Page = () => {
|
||||
|
||||
// 현황 등록
|
||||
const registFile = async (fileName: string, year: string, quater: string, cd: string, id: string) => {
|
||||
const data = await postData('v1/sbmrgPrevnt', {
|
||||
const json = {
|
||||
atflNm: fileName,
|
||||
mngYr: year,
|
||||
mngQu: quater,
|
||||
atflCd: cd,
|
||||
atflId: id,
|
||||
sbmrgPrevntBizNm: fileName
|
||||
})
|
||||
sbmrgPrevntBizNm: fileName,
|
||||
rgtr: tokenManager.getToken(AUTH_USER_ID) || null
|
||||
}
|
||||
console.log('request DTO', json);
|
||||
const data = await postData('v1/sbmrgPrevnt', json)
|
||||
if (data) {
|
||||
alert('파일이 등록되었습니다.');
|
||||
setIsModalOpen(false);
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
export const tableHeadlist = [
|
||||
{ id: 'rowNumber', title: '일련번호' },
|
||||
// { id: 'sbmrgPrevntBizSn', title: '일련번호' },
|
||||
{ id: 'sbmrgPrevntBizNm', title: '파일명' },
|
||||
{ id: 'mngYr', title: '년도' },
|
||||
{ id: 'mngQu', title: '분기' },
|
||||
{ id: 'regDt', title: '등록 일자' },
|
||||
{ id: 'rgtr', title: '등록자' }
|
||||
{ id: 'rgtrNm', title: '등록자' }
|
||||
|
||||
]
|
||||
export const tableHeadlistOldPp = [
|
||||
{ id: 'rowNumber', title: '일련번호' },
|
||||
// { id: 'dcrpPplMtncBizSn', title: '일련번호' },
|
||||
{ id: 'dcrpPplMtncBizNm', title: '파일명' },
|
||||
{ id: 'mngYr', title: '년도' },
|
||||
{ id: 'mngQu', title: '분기' },
|
||||
{ id: 'regDt', title: '등록 일자' },
|
||||
{ id: 'rgtr', title: '등록자' }
|
||||
{ id: 'rgtrNm', title: '등록자' }
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { deleteData, postData } from '@/components/lib/api'
|
||||
import { deleteData, postData, useCookieAuth } from '@/components/lib/api'
|
||||
import { searchData } from '@/components/lib/common/apiHelpers'
|
||||
import { formatDate, handleDownload, yearOptions } from '@/components/lib/utils'
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
@@ -9,9 +9,16 @@ import { Button, DatePicker, Input, Label } from '@common/components/ui'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { tableHeadlistOldPp } from '..'
|
||||
import OperFileUploadForm from '../../../components/ui/operFileUploadForm'
|
||||
import { tokenManager } from '@/entities/auth/lib/token-manager';
|
||||
import { AUTH_USER_ID } from "@/shared/config";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ENDPOINT } from "@common/components/config";
|
||||
import { getUserInfo } from "@common/components/api";
|
||||
|
||||
|
||||
const Page = () => {
|
||||
const AUTH = useCookieAuth();
|
||||
|
||||
const [fromDate, setFromDate] = useState<Date>(() => { const date = new Date(); date.setDate(date.getDate() - 7); return date });
|
||||
const [toDate, setToDate] = useState<Date>(new Date());
|
||||
const [usr, setUsr] = useState('')
|
||||
@@ -27,6 +34,12 @@ const Page = () => {
|
||||
const [resetSelection, setResetSelection] = useState<boolean>(false); // 선택 초기화 상태
|
||||
const [isResetPage, setIsResetPage] = useState<boolean>(false);
|
||||
|
||||
const { data: userInfo } = useQuery({
|
||||
queryKey: [ENDPOINT.USER_SERVICE.USERS],
|
||||
queryFn: () => getUserInfo(),
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurPage(1)
|
||||
fetchData()
|
||||
@@ -54,7 +67,7 @@ const Page = () => {
|
||||
fromRegDt: formatDate(fromDate),
|
||||
toRegDt: formatDate(toDate),
|
||||
dcrpPplMtncBizNm: pjNm,
|
||||
rgtrNm: usr,
|
||||
auth: AUTH,
|
||||
page: curPage - 1,
|
||||
size: '10'
|
||||
})
|
||||
@@ -112,7 +125,9 @@ const Page = () => {
|
||||
mngQu: quater,
|
||||
atflCd: cd,
|
||||
atflId: id,
|
||||
dcrpPplMtncBizNm: fileNm
|
||||
dcrpPplMtncBizNm: fileNm,
|
||||
rgtr: tokenManager.getToken(AUTH_USER_ID) || null,
|
||||
rgtrNm: userInfo?.userName,
|
||||
})
|
||||
if (data) {
|
||||
alert('파일이 등록되었습니다.');
|
||||
|
||||
@@ -20,6 +20,7 @@ interface CtpvSggPrcsFcltSelectBoxProps {
|
||||
className?: string;
|
||||
selectedSwagePrcsFclt?: string | number;
|
||||
onSwagePrcsFcltChange?: (value: string | number) => void;
|
||||
setSwtrmtFcltNm?: (name: string) => void;
|
||||
setIsLoading?: (isLoading: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -36,6 +37,7 @@ export function CtpvSggPrcsFcltSelectBox({
|
||||
className = "",
|
||||
selectedSwagePrcsFclt,
|
||||
onSwagePrcsFcltChange,
|
||||
setSwtrmtFcltNm,
|
||||
setIsLoading,
|
||||
}: CtpvSggPrcsFcltSelectBoxProps) {
|
||||
|
||||
@@ -50,6 +52,7 @@ export function CtpvSggPrcsFcltSelectBox({
|
||||
setCtpvName("");
|
||||
setSggName("");
|
||||
}
|
||||
if (setSwtrmtFcltNm) setSwtrmtFcltNm("");
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@@ -118,6 +121,7 @@ export function CtpvSggPrcsFcltSelectBox({
|
||||
onSelectValue={val => {
|
||||
onSidoChange(Number(val));
|
||||
if (setCtpvName) setCtpvName(ctpv.find(opt => Number(opt.id) === Number(val))?.name || "");
|
||||
if (setSwtrmtFcltNm) setSwtrmtFcltNm("");
|
||||
}}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
@@ -137,6 +141,7 @@ export function CtpvSggPrcsFcltSelectBox({
|
||||
onSelectValue={val => {
|
||||
onSigunguChange(Number(val))
|
||||
if (setSggName) setSggName(sgg.find(opt => Number(opt.id) === Number(val))?.name || "");
|
||||
if (setSwtrmtFcltNm) setSwtrmtFcltNm("")
|
||||
}}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
@@ -149,7 +154,10 @@ export function CtpvSggPrcsFcltSelectBox({
|
||||
label=""
|
||||
selectArray={[defaultOption, ...prcsFcltOptions]}
|
||||
value={selectedSwagePrcsFclt}
|
||||
onSelectValue={(val) => onSwagePrcsFcltChange?.(val)}
|
||||
onSelectValue={(val) => {
|
||||
onSwagePrcsFcltChange?.(val)
|
||||
if (setSwtrmtFcltNm) setSwtrmtFcltNm(prcsFcltOptions.find(opt => String(opt.val) === String(val))?.text || "")
|
||||
}}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -24,5 +24,5 @@ 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
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
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_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
|
||||
|
||||
@@ -11,7 +11,7 @@ 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 = 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
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
import type { MenuItem } from "@common/components/api";
|
||||
import { NoFooterLayout } from "@common/components/ui";
|
||||
import { usePathname,useRouter } from "next/navigation";
|
||||
import { useWrStore } from "../store";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const { getMenuAuthrts } = useWrStore((state) => state);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const changeMenu = (path: MenuItem) => {
|
||||
getMenuAuthrts(path.menuCd);
|
||||
router.push(path.urlPath);
|
||||
};
|
||||
return (
|
||||
<NoFooterLayout currentPath={pathname ?? ""} handleMenuClick={changeMenu}>
|
||||
{children}
|
||||
</NoFooterLayout>
|
||||
);
|
||||
}
|
||||
+73
-33
@@ -2,11 +2,12 @@
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { mapService } from "../service";
|
||||
import { mapService } from "../../service";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { SearchBar } from "@common/business_components/ui/map/searchBar/searchBar";
|
||||
import { useWrStore } from "@/app/store";
|
||||
import { getFeatureCd } from "@/components/lib/func/\mapUtil";
|
||||
import { getFeatureCd } from "@/components/lib/func/mapUtil";
|
||||
import toast, { Toaster } from "react-hot-toast";
|
||||
|
||||
const Map = () => {
|
||||
const { comMap } = useWrStore((state) => state);
|
||||
@@ -23,7 +24,7 @@ const Map = () => {
|
||||
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs();
|
||||
const response = await mapService.getLyrStngs('WR_NETWORK');
|
||||
setTreeData(response); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
@@ -173,7 +174,6 @@ const Map = () => {
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
|
||||
const getSelectedLayers = (isFeature: any) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
@@ -206,33 +206,52 @@ const Map = () => {
|
||||
|
||||
const renderDetailView = useCallback(
|
||||
(props: any) => {
|
||||
const isFeature = props.featureData.getProperties();
|
||||
if (
|
||||
Object.keys(isFeature).length === 0 &&
|
||||
isFeature.constructor === Object
|
||||
) {
|
||||
const url = `/mapDetilePopUp?layerNm=${encodeURIComponent(props.seletectedLayerNm)}`;
|
||||
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`;
|
||||
popupRef.current = window.open(url, "지도 레이어 팝업", popupOptions);
|
||||
} else {
|
||||
const layerNm = getSelectedLayers(featureData);
|
||||
const safeProps = featureData.getProperties();
|
||||
const featureCd = getFeatureCd(safeProps, String(layerNm));
|
||||
const url = `/mapFeaturePopup?layerNm=${encodeURIComponent(String(layerNm))}&featureCd=${encodeURIComponent(featureCd)} `;
|
||||
const width = Math.floor(window.innerWidth * 0.4); // 화면 폭의 40%
|
||||
const height = Math.floor(window.innerHeight - 450); // 150px 여백 확보; // calc(100% - 70px)
|
||||
const left = (window.screen.availWidth - width) / 2;
|
||||
const top = (window.screen.availHeight - height) / 2;
|
||||
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
|
||||
popupDetailRef.current = window.open(
|
||||
url,
|
||||
"지도 시설 상세정보 팝업",
|
||||
popupOptions
|
||||
);
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const isFeature = props?.featureData;
|
||||
|
||||
console.log('isFeature 1', isFeature)
|
||||
if( typeof isFeature.getProperties === "function" ){
|
||||
|
||||
if (
|
||||
Object.keys(isFeature?.getProperties()).length === 0 &&
|
||||
isFeature.constructor === Object
|
||||
) {
|
||||
const url = `/mapDetailPopUp?layerNm=${encodeURIComponent(props.seletectedLayerNm)}`;
|
||||
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=no`;
|
||||
popupRef.current = window.open(url, "지도 레이어 팝업", popupOptions);
|
||||
} else {
|
||||
const layerNm = getSelectedLayers(isFeature);
|
||||
const safeProps = isFeature.getProperties();
|
||||
const featureCd = getFeatureCd(safeProps, String(layerNm));
|
||||
const url = `/mapFeaturePopup?layerNm=${encodeURIComponent(String(layerNm))}&featureCd=${encodeURIComponent(featureCd)} `;
|
||||
const width =
|
||||
String(layerNm) === "하수재이용시설"
|
||||
? Math.floor(window.innerWidth * 0.8)
|
||||
: Math.floor(window.innerWidth * 0.4); // 화면 폭의 40%
|
||||
const height =
|
||||
String(layerNm) === "하수재이용시설"
|
||||
? Math.floor(window.innerHeight - 150)
|
||||
: Math.floor(window.innerHeight - 450); // 150px 여백 확보; // calc(100% - 70px)
|
||||
const left = (window.screen.availWidth - width) / 2;
|
||||
const top = (window.screen.availHeight - height) / 2;
|
||||
const popupOptions = `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`;
|
||||
popupDetailRef.current = window.open(
|
||||
url,
|
||||
"지도 시설 상세정보 팝업",
|
||||
popupOptions
|
||||
);
|
||||
}
|
||||
}else{
|
||||
console.log(' data확인 필요')
|
||||
}
|
||||
},
|
||||
[checkedLayers, checkedLayersData, featureData] // 필요 시 의존성 넣기
|
||||
@@ -246,6 +265,23 @@ const Map = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onOpenPopup = (data:any) => {
|
||||
if(data){
|
||||
|
||||
const url = `/mapDetailPopUp?layerNm=${encodeURIComponent(data)}`;
|
||||
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=no`;
|
||||
popupRef.current = window.open(url, "지도 레이어 팝업", popupOptions);
|
||||
}else{
|
||||
toast.error('시설 및 수요처가 선택되지 않았습니다.')
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
@@ -255,7 +291,7 @@ const Map = () => {
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={renderDetailView}
|
||||
onFeatureSelect={onFeatureFocus}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
@@ -280,8 +316,12 @@ const Map = () => {
|
||||
/>
|
||||
}
|
||||
waterDepth={undefined}
|
||||
onOpenPopup={(data:any) =>{
|
||||
onOpenPopup(data)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Toaster position="top-center" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+2
-2
@@ -30,8 +30,8 @@ export type LyrStngProps = {
|
||||
|
||||
export const mapService = {
|
||||
//레이어 설정 조회
|
||||
getLyrStngs: async () => {
|
||||
const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, {
|
||||
getLyrStngs: async (lyrGrp : string) => {
|
||||
const response = await fetch(`${API_URL}/lyrStngs/${lyrGrp}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
+8
-8
@@ -16,7 +16,7 @@ export default function MapDetailPopUp() {
|
||||
const renderConditionalContent = () => {
|
||||
const layerId = searchParams.get("layerNm");
|
||||
switch (layerId) {
|
||||
case "하수재이용시설":
|
||||
case "tb_wr_bdst_ruse_fclt":
|
||||
return (
|
||||
<RuseFacility
|
||||
rtnFeature={(feature) => {
|
||||
@@ -24,7 +24,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "하수처리시설":
|
||||
case "tb_il_swage_prcs_fclt":
|
||||
return (
|
||||
<HasuFacility
|
||||
rtnFeature={(feature) => {
|
||||
@@ -32,7 +32,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "빗물이용시설":
|
||||
case "tb_wr_rnwtr_utztn_fclt":
|
||||
return (
|
||||
<RainFacility
|
||||
rtnFeature={(feature) => {
|
||||
@@ -40,7 +40,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "중수도시설":
|
||||
case "tb_wr_rswtr_fclt":
|
||||
return (
|
||||
<RswtFacility
|
||||
rtnFeature={(feature) => {
|
||||
@@ -48,7 +48,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "온배수시설":
|
||||
case "tb_wr_htdfl_fclt":
|
||||
return (
|
||||
<HtdflFacility
|
||||
rtnFeature={(feature) => {
|
||||
@@ -56,7 +56,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "수요처":
|
||||
case "tb_wr_ruse_fclt_dmd_co":
|
||||
return (
|
||||
<CoSttnMap
|
||||
rtnFeature={(feature) => {
|
||||
@@ -64,7 +64,7 @@ export default function MapDetailPopUp() {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "잠재수요처":
|
||||
case "tb_wr_tmpr_dmd_co":
|
||||
return (
|
||||
<LdrmntCoMap
|
||||
rtnFeature={(feature) => {
|
||||
@@ -78,7 +78,7 @@ export default function MapDetailPopUp() {
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div>{renderConditionalContent()}</div>
|
||||
<div className="h-full overflow-auto">{renderConditionalContent()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Label } from "@common/components/ui/label";
|
||||
|
||||
export const DRGT_LAYER_TYPES: string[] = [
|
||||
// 수정 필요
|
||||
'drought1',
|
||||
'drought2',
|
||||
'drought3',
|
||||
'drought4',
|
||||
'drought5',
|
||||
'drought6',
|
||||
];
|
||||
// 지정된 순서에 따른 lyr_sn 배열
|
||||
export const EXPRS_ORDERED_LAYERS: string[] = [
|
||||
// 수정 필요
|
||||
'106', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도
|
||||
'107', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도
|
||||
'108', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도
|
||||
'109', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도
|
||||
'110', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도
|
||||
'111', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도
|
||||
];
|
||||
|
||||
export function DrgtLegend() {
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-white rounded-lg shadow-lg border border-gray-200 w-[160px] px-4 py-3">
|
||||
<div className="flex items-center justify-between mx-2 my-4">
|
||||
<Label className="text-3xl text-gray-900 font-bold">가뭄정보 범례</Label>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<div className="w-full bg-gray-200 rounded-full h-1">
|
||||
<div className="bg-blue-900 h-1 rounded-full transition-all duration-300 ease-in-out" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[140px] mx-auto flex flex-col items-center">
|
||||
<div className="bg-blue-900 text-white text-2xl text-center py-1 mb-2 rounded w-full">가 뭄 정 보</div>
|
||||
<div className="space-y-1 w-full">
|
||||
<Label className="text-xl text-gray-900 font-bold">관심</Label>
|
||||
<LegendItem color="rgba(222,235,247, 1)" label="1회 미만" /> {/* 관심 - 1회 미만 */}
|
||||
<LegendItem color="rgba(46, 117, 182, 1)" label="1회 이상" /> {/* 관심 - 1회 이상 */}
|
||||
<Label className="text-xl text-gray-900 font-bold">주의</Label>
|
||||
<LegendItem color="rgba(255, 242, 204, 1)" label="1회 미만" /> {/* 주의 - 1회 미만 */}
|
||||
<LegendItem color="rgba(255, 217, 102, 1)" label="1회 이상" /> {/* 주의 - 1회 이상 */}
|
||||
<Label className="text-xl text-gray-900 font-bold">관심 + 주의</Label>
|
||||
<LegendItem color="rgba(251, 229, 214, 1)" label="1회 미만" /> {/* 관심+주의 - 1회 미만 */}
|
||||
<LegendItem color="rgba(237, 125, 49, 1)" label="1회 이상" /> {/* 관심+주의 - 1회 이상 */}
|
||||
</div>
|
||||
<div className="border-t-2 border-blue-900 mt-1 mx-auto w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function LegendItem({ color, label }: { color: string; label: string }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<span
|
||||
className="inline-block w-16 h-6"
|
||||
style={{ backgroundColor: color, border: "1px solid #ccc" }}
|
||||
/>
|
||||
<span className="text-xl text-gray-900 font-bold">{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { boundingExtent, getCenter } from 'ol/extent';
|
||||
import Feature from 'ol/Feature';
|
||||
import Geometry from 'ol/geom/Geometry';
|
||||
|
||||
export function medianCoordinate2D(feature: Feature<Geometry>) {
|
||||
// Feature를 입력 받아 중심 좌표를 계산하는 함수
|
||||
if (!feature) {
|
||||
console.warn("유효한 피처가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(feature)
|
||||
|
||||
const geometry = feature.getGeometry();
|
||||
if (!geometry) {
|
||||
console.warn("피처에 geometry가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const extent = geometry.getExtent();
|
||||
const center = getCenter(extent);
|
||||
const centerExtent = boundingExtent([center]);
|
||||
return centerExtent;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { mapService } from "../../(map-layout)/service";
|
||||
import { DrgtLegend, DRGT_LAYER_TYPES, EXPRS_ORDERED_LAYERS } from "./drgtLegend";
|
||||
import { SearchBar } from "./searchBar";
|
||||
|
||||
|
||||
export default function MapDrgtPopUp() {
|
||||
const searchParams = useSearchParams();
|
||||
const ctpvCd = searchParams.get('ctpvCd') || '';
|
||||
const sggCd = searchParams.get('sggCd') || '';
|
||||
const fromDate = searchParams.get('fromDate') || '';
|
||||
const toDate = searchParams.get('toDate') || '';
|
||||
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [showDrgtLegend, setShowDrgtLegend] = useState(false); // DrgtLegend 표시 여부
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
function fetchData() {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs('WR_DROUGHT');
|
||||
setTreeData(response); // API 응답 저장
|
||||
console.log("Drought layers fetched:", response);
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// WaterDepth 표시 여부를 결정하는 함수
|
||||
const handleDrgtLegndVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => {
|
||||
const drgtLegndLayers = checkedNodesData.filter(node =>
|
||||
DRGT_LAYER_TYPES.includes(node.data?.lyrTyNm)
|
||||
);
|
||||
|
||||
const hasDrgtLegndLayer = drgtLegndLayers.length > 0;
|
||||
setShowDrgtLegend(hasDrgtLegndLayer);
|
||||
}, []);
|
||||
|
||||
// 체크된 레이어 변경 처리 함수
|
||||
const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => {
|
||||
handleDrgtLegndVisibility(checkedIds, checkedNodesData);
|
||||
}, [handleDrgtLegndVisibility]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={undefined}
|
||||
onFeatureSelect={undefined}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={undefined}
|
||||
featureData={undefined}
|
||||
setIsSearchModalOpen={undefined}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={undefined}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
imgVisble={true}
|
||||
searchBar={<SearchBar gisMaps={gisMaps} ctpvCode={ctpvCd} sggCode={sggCd} fromDate={fromDate} toDate={toDate} />}
|
||||
waterDepth={showDrgtLegend ? DrgtLegend : undefined}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying as any}
|
||||
targetLayerTypes={DRGT_LAYER_TYPES as any}
|
||||
orderedLayers={EXPRS_ORDERED_LAYERS as any}
|
||||
checkInterval={2500}
|
||||
height={`h-[100vh]`}
|
||||
droughtDates={{ startYmd: fromDate, endYmd: toDate }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { SelectBox } from '@common/business_components/ui'
|
||||
import { Label, Button } from '@common/components';
|
||||
import { type SelectDataType } from "@common/business_components/ui";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import GeoJSON from "ol/format/GeoJSON";
|
||||
import Feature from "ol/Feature";
|
||||
import Geometry from "ol/geom/Geometry";
|
||||
import { medianCoordinate2D } from "./median";
|
||||
|
||||
interface CtpvSggPrcsFcltSelectBoxProps {
|
||||
gisMaps: React.RefObject<any>;
|
||||
ctpvCode: string;
|
||||
sggCode: string;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
}
|
||||
|
||||
const DEFAULT_OPTION: SelectDataType = { val: '0', text: '전체' };
|
||||
|
||||
export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectBoxProps) {
|
||||
const [ctpvCd, setCtpvCd] = useState<string>(ctpvCode);
|
||||
const [sggCd, setSggCd] = useState<string>(sggCode);
|
||||
const [ctpv, setCtpv] = useState<SelectDataType[]>([]);
|
||||
const [sgg, setSgg] = useState<SelectDataType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await fetchSido();
|
||||
await fetchSigungu();
|
||||
setCtpvCd(ctpvCode);
|
||||
setSggCd(sggCode);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
function onViewMapClick() {
|
||||
if (sggCd && sggCd !== '0') {
|
||||
fetchSggData();
|
||||
return;
|
||||
}
|
||||
if (ctpvCd && ctpvCd !== '0') fetchCtpvData();
|
||||
}
|
||||
|
||||
async function fetchCtpvData() {
|
||||
try {
|
||||
const ctpvData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson&cql_filter=mctpv_cd=%27${ctpvCd.trim()}%27&srsName=EPSG:5179`)
|
||||
.then(res => res.json());
|
||||
console.log('시도 데이터', ctpvData.features[0]);
|
||||
|
||||
const format = new GeoJSON();
|
||||
const feature: Feature<Geometry> = format.readFeature(ctpvData.features[0], {
|
||||
dataProjection: "EPSG:5179", // GeoJSON 좌표계
|
||||
featureProjection: GisApp.TO_PROJ // 지도 좌표계
|
||||
});
|
||||
|
||||
console.log('feature', feature);
|
||||
|
||||
const extent = medianCoordinate2D(feature);
|
||||
console.log('GisApp.TO_PROJ', GisApp.TO_PROJ);
|
||||
console.log('extent', extent)
|
||||
|
||||
handleFeatureFocus(extent);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("시도 data fetching Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSggData() {
|
||||
try {
|
||||
const sggData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson&cql_filter=sgg_cd=%27${sggCd}%27&srsName=EPSG:5179`)
|
||||
.then(res => res.json());
|
||||
console.log('시군구 데이터', sggData.features[0]);
|
||||
|
||||
|
||||
const format = new GeoJSON();
|
||||
const feature: Feature<Geometry> = format.readFeature(sggData.features[0], {
|
||||
dataProjection: "EPSG:5179", // GeoJSON 좌표계
|
||||
featureProjection: GisApp.TO_PROJ // 지도 좌표계
|
||||
});
|
||||
|
||||
const extent = medianCoordinate2D(feature);
|
||||
|
||||
handleFeatureFocus(extent);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("시군구 data fetching Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFeatureFocus(extent: any) {
|
||||
console.log('ctpv', ctpv);
|
||||
console.log('sgg', sgg);
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 11
|
||||
});
|
||||
};
|
||||
|
||||
const fetchSido = async () => {
|
||||
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`;
|
||||
const res = await fetch(url).then(res => res.json());
|
||||
|
||||
const list = res.features.map((f: any) => ({
|
||||
text: f.properties.mctpv_korn_nm,
|
||||
val: f.properties.mctpv_cd,
|
||||
}));
|
||||
|
||||
console.log('시도 데이터', list);
|
||||
setCtpv(list);
|
||||
};
|
||||
|
||||
const fetchSigungu = async () => {
|
||||
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`;
|
||||
const res = await fetch(url).then(res => res.json());
|
||||
|
||||
const list = res.features.map((f: any) => ({
|
||||
text: f.properties.sgg_korn_nm,
|
||||
val: f.properties.sgg_cd,
|
||||
}));
|
||||
|
||||
console.log('시군구 데이터', list);
|
||||
setSgg(list);
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4 bg-white min-h-[48px] min-w-[430px] px-5 py-3 rounded-lg shadow">
|
||||
<div className={`flex items-center justify-center mr-8`}>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시도</Label>
|
||||
<SelectBox
|
||||
className="w-60 mr-8"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={[DEFAULT_OPTION, ...ctpv]}
|
||||
value={ctpvCd}
|
||||
onSelectValue={val => {
|
||||
setCtpvCd(String(val));
|
||||
setSggCd(String(0));
|
||||
}}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-2">시군구</Label>
|
||||
<SelectBox
|
||||
className="w-48"
|
||||
selectClass="w-48 rounded-lg bg-white truncate"
|
||||
label=""
|
||||
selectArray={
|
||||
[DEFAULT_OPTION,
|
||||
...sgg.filter(opt => String(opt.val).slice(0, 2) === String(ctpvCd).trim())
|
||||
]
|
||||
}
|
||||
value={sggCd}
|
||||
onSelectValue={val => { setSggCd(String(val)) }}
|
||||
selectContentClass="z-[101]"
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-1 justify-end '>
|
||||
<Button size="sm" onClick={() => onViewMapClick()}>조회</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import { useWrStore } from "@/app/store";
|
||||
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
|
||||
import { RuseDetailPopup } from "@/components/ui/map/detailPopup/ruseDetailPopup";
|
||||
import CoDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/coDatailModal";
|
||||
import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal";
|
||||
import { Title, UiTable } from "@common/business_components/ui";
|
||||
@@ -11,7 +11,6 @@ export default function MapFeaturePopup() {
|
||||
const [isFeatureCd, setIsFeatureCd] = useState<any>(null);
|
||||
const {
|
||||
pbnMap,
|
||||
ruseMapDetail,
|
||||
rainMapDetile,
|
||||
rswtMapDetile,
|
||||
htdfMapDetile,
|
||||
@@ -46,10 +45,8 @@ export default function MapFeaturePopup() {
|
||||
|
||||
useEffect(() => {
|
||||
if (isFeatureCd && layerNm) {
|
||||
let detail = {};
|
||||
if (layerNm === "하수재이용시설") {
|
||||
detail = ruseMapDetail(isFeatureCd);
|
||||
setIsDetail(detail);
|
||||
setIsDetail(isFeatureCd);
|
||||
} else if (layerNm === "하수처리시설") {
|
||||
pbnMapDetaile(isFeatureCd, isFeatureCd).then((res) => {
|
||||
setIsDetail(res);
|
||||
@@ -78,9 +75,9 @@ export default function MapFeaturePopup() {
|
||||
return (
|
||||
<>
|
||||
{isDetail ? (
|
||||
<div className="fixed inset-0 w-screen h-screen m-0 p-0 overflow-auto bg-white">
|
||||
<div className="fixed inset-0 w-screen h-screen m-0 p-0 bg-white">
|
||||
{layerNm === "하수재이용시설" ? (
|
||||
<PbnstSttnModal data={isDetail} />
|
||||
<RuseDetailPopup bscFctCd={isDetail} />
|
||||
) : layerNm === "하수처리시설" ? (
|
||||
<div style={{ overflowX: "hidden" }}>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
@@ -125,6 +122,7 @@ export default function MapFeaturePopup() {
|
||||
: "HTB"
|
||||
}
|
||||
formObj={isDetail}
|
||||
isMap={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
type SearchType = {
|
||||
bscFctNm: string;
|
||||
bscFctCd: string;
|
||||
};
|
||||
|
||||
type thisProps = {
|
||||
label: string | undefined;
|
||||
initValue: string | undefined;
|
||||
setValue: (searchNm?: string) => void;
|
||||
setRtnData: (rtnData: any) => void;
|
||||
searchList?: SearchType[];
|
||||
};
|
||||
|
||||
export default function SearchInputText({
|
||||
label = undefined,
|
||||
initValue = undefined,
|
||||
setValue = () => {},
|
||||
setRtnData = () => {},
|
||||
searchList = [],
|
||||
}: thisProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [textValue, setTextValue] = useState(initValue || "");
|
||||
const [highlightIndex, setHighlightIndex] = useState<number>(-1);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setTextValue(value);
|
||||
setValue(value);
|
||||
setHighlightIndex(-1);
|
||||
setIsOpen(value !== "");
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const handleSuggestionClick = useCallback(
|
||||
(searchNm: string) => {
|
||||
setValue(searchNm);
|
||||
setTextValue(searchNm);
|
||||
setIsOpen(false);
|
||||
setRtnData(searchList?.find((item) => item.bscFctNm === searchNm) || {});
|
||||
},
|
||||
[searchList, setRtnData, setValue]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (!isOpen || searchList.length === 0) return;
|
||||
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) => (prev + 1) % searchList.length);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setHighlightIndex((prev) =>
|
||||
prev <= 0 ? searchList.length - 1 : prev - 1
|
||||
);
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (searchList.length === 1 && searchList[0]) {
|
||||
handleSuggestionClick(searchList[0].bscFctNm);
|
||||
} else if (highlightIndex >= 0 && highlightIndex < searchList.length && searchList[highlightIndex]) {
|
||||
handleSuggestionClick(searchList[highlightIndex].bscFctNm);
|
||||
}
|
||||
}
|
||||
},
|
||||
[highlightIndex, isOpen, searchList, handleSuggestionClick]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
wrapperRef.current &&
|
||||
!wrapperRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative">
|
||||
<input
|
||||
type="text"
|
||||
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"
|
||||
placeholder={`${label}을(를) 입력하세요`}
|
||||
onClick={() => {
|
||||
setTextValue("");
|
||||
setValue("");
|
||||
setIsOpen(false);
|
||||
}}
|
||||
/>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 z-50 mb-2 max-h-72 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
|
||||
<div className="max-h-72">
|
||||
{searchList.length > 0 ? (
|
||||
searchList.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`w-full cursor-pointer overflow-auto px-5 py-3 text-left !text-xl ${
|
||||
highlightIndex === index
|
||||
? "bg-blue-100"
|
||||
: "hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => handleSuggestionClick(item.bscFctNm)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
handleSuggestionClick(item.bscFctNm);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="float-left">{item.bscFctNm}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="!text-xl w-full px-5 py-3 hover:bg-gray-100 cursor-pointer overflow-auto">
|
||||
검색 결과가 없습니다.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
"use client";
|
||||
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { mapService } from "./service";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
import SelectedFcltList from "./selectedFcltList";
|
||||
|
||||
export type SubsInfo = {
|
||||
ruseUtztnFcltNm: string; // 재이용시설명
|
||||
ruseUtztnFcltCd: string; // 재이용시설 코드
|
||||
}
|
||||
|
||||
const Map = () => {
|
||||
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
|
||||
const gisMaps = useRef<any | null>(null);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
|
||||
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
|
||||
const [selectDataPk, setSelectDataPk] = useState<Record<string, any>>({});
|
||||
const [selectedFclts, setSelectedFclts] = useState<any[]>([]);
|
||||
|
||||
//적용 버튼 클릭 이벤트
|
||||
const onApply = () => {
|
||||
if (selectedFclts.length > 1) {
|
||||
alert("재이용시설 공급처는 1곳만 선택할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const subsInfoList: SubsInfo[] = selectedFclts.map((fclt) => ({
|
||||
ruseUtztnFcltNm: fclt.ruseUtztnFcltNm,
|
||||
ruseUtztnFcltCd: fclt.pbnstRuseFcltId,
|
||||
}));
|
||||
|
||||
if (!window.opener || typeof window.opener.postMessage !== "function") {
|
||||
alert("부모 창을 찾을 수 없습니다. 팝업이 'noopener'로 열렸거나, 보안 정책으로 차단되었을 수 있어요.");
|
||||
return;
|
||||
}
|
||||
|
||||
window.opener.postMessage(
|
||||
{
|
||||
type: "SET_SUBS_INFO",
|
||||
payload: subsInfoList[0], // 또는 필요한 데이터
|
||||
},
|
||||
"*"
|
||||
);
|
||||
|
||||
window.close();
|
||||
};
|
||||
|
||||
//트리 레이어 데이터 조회
|
||||
const fetchTreeData = async () => {
|
||||
try {
|
||||
const response = await mapService.getLyrStngs();
|
||||
setTreeData(response); // API 응답 저장
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
//하수처리시설코드로 재이용시설 목록 조회
|
||||
const reuseData = async (ruseFcltPk: any) => {
|
||||
try {
|
||||
const response = await mapService.getPbnstRuseFclts(ruseFcltPk);
|
||||
const data = response.data || [];
|
||||
setSelectedFclts((prev) => {
|
||||
const newItems = data.filter(
|
||||
(item: any) =>
|
||||
item &&
|
||||
Object.keys(item).length > 0 &&
|
||||
!prev.some((f) => f.pbnstRuseFcltId === item.pbnstRuseFcltId)
|
||||
);
|
||||
console.log(newItems);
|
||||
return [...prev, ...newItems];
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("데이터 불러오기 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 1회 실행
|
||||
useEffect(() => {
|
||||
fetchTreeData();
|
||||
}, []);
|
||||
|
||||
// 팝업 Map.tsx useEffect 안
|
||||
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") {
|
||||
// payload: [{ ruseUtztnFcltNm, ruseUtztnFcltCd }, ...]
|
||||
const list = Array.isArray(data.payload) ? data.payload : [];
|
||||
|
||||
// selectedFclts 형태에 맞게 변환해서 반영
|
||||
// (지금 onApply에서 pbnstRuseFcltId를 쓰므로 매핑 맞춰줍니다)
|
||||
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");
|
||||
|
||||
//수요처 위치로 화면 이동
|
||||
const maps = gisMaps.current;
|
||||
const layer = GisApp.Module.layer as any;
|
||||
const layerNm = "tb_wr_ruse_fclt_dmd_co";
|
||||
const featureId = `${layerNm}.${dmdInfoId}`;
|
||||
let foundFeature: any = null;
|
||||
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") == layerNm) {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) {
|
||||
foundFeature = feature;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
if (geometry.getType() === "Point") {
|
||||
maps.getView().setCenter(geometry.getCoordinates());
|
||||
maps.getView().setZoom(11);
|
||||
} else {
|
||||
maps.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [30, 30, 30, 30],
|
||||
maxZoom: 16,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
layer.highLightFeature(
|
||||
[foundFeature],
|
||||
{
|
||||
id: GisApp.hlLayerId,
|
||||
zIndex: 10,
|
||||
opacity: 1,
|
||||
selectable: true,
|
||||
},
|
||||
{ width: 30 }
|
||||
);
|
||||
}
|
||||
|
||||
const formatNumber = (val: any) => {
|
||||
const num = Number(val);
|
||||
return isNaN(num) ? "-" : num.toLocaleString();
|
||||
};
|
||||
|
||||
// 재이용시설 오버레이
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") === "tb_wr_bdst_ruse_fclt") {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
|
||||
features.forEach((feature: any) => {
|
||||
// 필요한 고유 ID 또는 속성 가져오기 (예: ftrIdn 또는 feature.getId())
|
||||
const featureId =
|
||||
feature.getId() || feature.get("ftrIdn") || Math.random();
|
||||
|
||||
// HTML 또는 React 오버레이 컴포넌트 구성
|
||||
const overlayContent = `
|
||||
<div class="rounded-md border border-gray-300 bg-white shadow-md text-sm w-[120px]">
|
||||
<!-- 헤더 -->
|
||||
<div class="bg-cyan-700 text-white px-3 py-2 text-[11px] font-semibold text-center rounded-t-md">
|
||||
${feature.get("swtrmt_fclt_nm") || "-"}
|
||||
</div>
|
||||
|
||||
<!-- 테이블 -->
|
||||
<table class="w-full text-sm text-gray-800 border-t border-gray-200">
|
||||
<tbody>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="bg-gray-100 text-gray-600 font-semibold text-[10px] text-left">용량(m³/일)</th>
|
||||
<td class="text-right text-[10px] font-bold">${formatNumber(feature.get("fclt_cpct"))}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
feature.set("html", overlayContent);
|
||||
feature.set("overLayId", `overLay.${featureId}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 검색해서 재이용시설 선택하면 해당 시설로 화면 이동 처리
|
||||
useEffect(() => {
|
||||
if (!selectDataPk || Object.keys(selectDataPk).length === 0) return;
|
||||
const maps = gisMaps.current;
|
||||
let foundFeature: any = null;
|
||||
const layerNm = "tb_wr_bdst_ruse_fclt";
|
||||
const featureId = `${layerNm}.${selectDataPk.bscFctCd}`;
|
||||
maps
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.forEach((layer: any) => {
|
||||
if (layer.get("lyrTyNm") == layerNm) {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatureById) {
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) {
|
||||
foundFeature = feature;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
|
||||
maps.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.warn("OpenLayers Feature를 찾을 수 없습니다:", selectDataPk);
|
||||
}
|
||||
}, [selectDataPk]);
|
||||
|
||||
// 재이용시설 클릭 시 선택 시설목록에 추가
|
||||
const onFeatureSelect = (data: any) => {
|
||||
// 처리시설코드
|
||||
const swtrmtFcltCd = data.get("swtrmt_fclt_cd");
|
||||
reuseData(swtrmtFcltCd);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
height='h-screen'
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={undefined}
|
||||
detailView={undefined}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={onCheckedLayersChange}
|
||||
imgVisble={false}
|
||||
searchBar={
|
||||
<SearchBar
|
||||
setHasuPk={(hasuPk: any) => {
|
||||
if (hasuPk) setSelectDataPk(hasuPk);
|
||||
}}
|
||||
onApply={onApply}
|
||||
/>
|
||||
}
|
||||
waterDepth={undefined}
|
||||
/>
|
||||
</div>
|
||||
<SelectedFcltList
|
||||
data={selectedFclts}
|
||||
onRemove={(updatedList) => setSelectedFclts(updatedList)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client";
|
||||
import { useWrStore } from "@/app/store";
|
||||
import {
|
||||
SelectBox,
|
||||
type SelectDataType,
|
||||
} from "@common/business_components/ui";
|
||||
import { Button, Label } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import SearchInputText from "../SearchInputText/SearchInputText";
|
||||
|
||||
export function SearchBar({
|
||||
setHasuPk,
|
||||
onApply,
|
||||
}: {
|
||||
setHasuPk: (hasuPk: any) => void;
|
||||
onApply: () => void;
|
||||
}) {
|
||||
const { map, code, codeInit, findRuseFclt } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
const [sidoValue, setSidoValue] = useState<string>("");
|
||||
const [sigunValue, setSigunValue] = useState<string>("");
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([
|
||||
{ text: "전체", val: "0" },
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
//codeInit();
|
||||
}, [codeInit]);
|
||||
|
||||
const initializeSigunData = useCallback(
|
||||
(sidoCd: string) => {
|
||||
if (sidoCd === "0") {
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData([{ text: "전체", val: "0" }]);
|
||||
} else {
|
||||
const newFilteredData = [
|
||||
{ text: "전체", val: "0" },
|
||||
...code.sggCd.filter(
|
||||
(item: SelectDataType) => item.val.substring(0, 2) === sidoCd
|
||||
),
|
||||
];
|
||||
setSigunValue("0");
|
||||
setFilteredSigunData(newFilteredData);
|
||||
}
|
||||
},
|
||||
[code.sggCd]
|
||||
);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
switch (type) {
|
||||
case "ctpvCd":
|
||||
initializeSigunData(val);
|
||||
setSidoValue(val);
|
||||
break;
|
||||
case "sggCd":
|
||||
setSigunValue(val);
|
||||
}
|
||||
},
|
||||
[initializeSigunData]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center w-full px-4 py-3">
|
||||
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="w-[200px]"
|
||||
label="시도"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={code.cityCd || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="w-[200px]"
|
||||
label="시군구"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={filteredSigunData || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<Label className="text-gray-600">하수재이용시설</Label>
|
||||
<SearchInputText
|
||||
label="시설명"
|
||||
initValue={undefined}
|
||||
setValue={(searchNm?: string) => {
|
||||
let sggOrCity = "";
|
||||
if (searchNm) {
|
||||
if (sigunValue === "0" || sigunValue === "") {
|
||||
sggOrCity = sidoValue;
|
||||
} else {
|
||||
sggOrCity = sigunValue;
|
||||
}
|
||||
findRuseFclt(sggOrCity, searchNm);
|
||||
}
|
||||
}}
|
||||
setRtnData={(rtnData: any) => {
|
||||
setHasuPk(rtnData);
|
||||
}}
|
||||
searchList={map.ruseFcltList || []}
|
||||
/>
|
||||
<Button
|
||||
color="sky"
|
||||
className="ml-2 text-white"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
onApply();
|
||||
}}
|
||||
>
|
||||
적용
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from "react";
|
||||
|
||||
type FcltItem = {
|
||||
pbnstRuseFcltId: string;
|
||||
ruseUtztnFcltNm: string;
|
||||
splyExtraCpct?: number;
|
||||
ruseCpct?: number;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export default function SelectedFcltList({
|
||||
data = [],
|
||||
onRemove,
|
||||
}: {
|
||||
data: FcltItem[];
|
||||
onRemove: (updatedList: FcltItem[]) => void;
|
||||
}) {
|
||||
const handleDelete = (code: string) => {
|
||||
const updatedList = data.filter((item) => item.pbnstRuseFcltId !== code);
|
||||
onRemove(updatedList);
|
||||
};
|
||||
|
||||
const TableContent = (
|
||||
<>
|
||||
<thead className="bg-gray-100 text-gray-700 text-[13px]">
|
||||
<tr>
|
||||
<th className="px-3 py-2 text-left border-b">시설명</th>
|
||||
<th className="px-3 py-2 text-right border-b">
|
||||
하수처리량
|
||||
<br />
|
||||
(㎥/일)
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right border-b">
|
||||
재이용량
|
||||
<br />
|
||||
(㎥/일)
|
||||
</th>
|
||||
<th className="px-3 py-2 border-b">삭제</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]">
|
||||
{Number(item.splyExtraCpct || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right text-[12px]">
|
||||
{Number(item.ruseCpct || 0).toLocaleString()}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<button
|
||||
onClick={() => handleDelete(item.pbnstRuseFcltId)}
|
||||
className="text-red-500 hover:text-red-700 text-sm font-bold"
|
||||
aria-label="삭제"
|
||||
title="삭제"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
);
|
||||
|
||||
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">
|
||||
선택된 시설
|
||||
</h3>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-sm text-gray-800 border border-gray-200">
|
||||
{TableContent}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './map'
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
/**
|
||||
* 관망도 API
|
||||
*/
|
||||
import { DEV_URL_WR, DEV_URL_SN } from "@/components/lib/config";
|
||||
|
||||
export type LyrStngProps = {
|
||||
lyr_sn: string
|
||||
lyr_nm: string
|
||||
upper_lyr_sn: string
|
||||
drawOrdr: string
|
||||
flterSql: string
|
||||
gobjTy: string
|
||||
lyrTpNm: string
|
||||
useYn: string
|
||||
minViewSize: string
|
||||
iconNm: string
|
||||
lblAtrbNm: string
|
||||
fontNm: string
|
||||
fontIntnColor: string
|
||||
fontExtnColor: string
|
||||
fontExtnWdth: string
|
||||
lnWdth: string
|
||||
lnColor: string
|
||||
polgnColor: string
|
||||
initEprssYn: string
|
||||
clickPsblYn: string
|
||||
}
|
||||
|
||||
export const mapService = {
|
||||
//레이어 설정 조회
|
||||
getLyrStngs: async () => {
|
||||
const response = await fetch(`${DEV_URL_SN}/v1/map/lyrStngs/DM_NETWORK`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
},
|
||||
getPbnstRuseFclts: async (ruseFcltPk:any) => {
|
||||
const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/b
|
||||
import { ContentsDiv, DateMonthPicker, downloadExcel, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui";
|
||||
import ListDiv from "@common/business_components/ui/list_div";
|
||||
import { Button, Label, ScrollArea } from "@common/components/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { toast, Toaster } from "react-hot-toast";
|
||||
|
||||
export default function DrgtSttn() {
|
||||
@@ -26,90 +26,91 @@ export default function DrgtSttn() {
|
||||
isOpen: boolean;
|
||||
data: customDrgt[] | undefined;
|
||||
}>({ data: undefined, isOpen: false });
|
||||
const popupRef = useRef<Window | null>(null);
|
||||
|
||||
const cellClick = (index: number) => {
|
||||
if (fromDate && toDate && drgtRes[index]) {
|
||||
drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm ?? '');
|
||||
} else {
|
||||
toast.error("조회기간을 선택해주세요.");
|
||||
}
|
||||
const cellClick = (index: number) => {
|
||||
if (fromDate && toDate && drgtRes[index]) {
|
||||
drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm ?? '');
|
||||
} else {
|
||||
toast.error("조회기간을 선택해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
const excelClick = () => {
|
||||
downloadExcel({
|
||||
data: excelData ?? [],
|
||||
fileName: "우리동네가뭄 현황",
|
||||
sheetNameArray: ["Sheet1"],
|
||||
headerRange: "A1:G1"
|
||||
})
|
||||
const excelClick = () => {
|
||||
downloadExcel({
|
||||
data: excelData ?? [],
|
||||
fileName: "우리동네가뭄 현황",
|
||||
sheetNameArray: ["Sheet1"],
|
||||
headerRange: "A1:G1"
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!drgt.isInit) {
|
||||
drgtInit();
|
||||
}
|
||||
}, [drgt.isInit, drgtInit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!drgt.isInit) {
|
||||
drgtInit();
|
||||
useEffect(() => {
|
||||
let resData = Array.isArray(drgtRes) ? drgtRes : [drgtRes];
|
||||
resData = resData.map((item) => {
|
||||
if (item.sggNm === '세종시') {
|
||||
return {
|
||||
...item,
|
||||
ctpvCd: '세종특별자치시',
|
||||
sggNm: ''
|
||||
}
|
||||
}, [drgt.isInit, drgtInit]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
let resData = Array.isArray(drgtRes) ? drgtRes : [drgtRes];
|
||||
resData = resData.map((item) => {
|
||||
if (item.sggNm === '세종시') {
|
||||
return {
|
||||
...item,
|
||||
ctpvCd: '세종특별자치시',
|
||||
sggNm: ''
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
ctpvCd: code.cityCd.find(ct => (ct.text == item.sggNm || (code.sggCd.find(sgg => sgg.text == item.sggNm)?.val.substring(0, 2) == ct.val)))?.text,
|
||||
sggNm: code.sggCd.find(sgg => sgg.text == item.sggNm)?.text
|
||||
}
|
||||
})
|
||||
const data = Array.isArray(resData)
|
||||
? resData.map((item: customDrgt) => {
|
||||
return {
|
||||
"시도": item.ctpvCd,
|
||||
"시군구": item.sggNm,
|
||||
"정상(횟수)": item.normal,
|
||||
"관심(횟수)": item.attention,
|
||||
"주의(횟수)": item.caution,
|
||||
"관심+주의(횟수)": item.warning,
|
||||
"가뭄순위": item.ranking,
|
||||
}
|
||||
}) : [];
|
||||
setExcelData(data)
|
||||
setTableData(resData)
|
||||
}, [drgtRes])
|
||||
|
||||
useEffect(() => {
|
||||
if (drgtMoRes && drgtMoRes.length > 0) {
|
||||
setDrgtModal({ isOpen: true, data: drgtMoRes });
|
||||
return {
|
||||
...item,
|
||||
ctpvCd: code.cityCd.find(ct => (ct.text == item.sggNm || (code.sggCd.find(sgg => sgg.text == item.sggNm)?.val.substring(0, 2) == ct.val)))?.text,
|
||||
sggNm: code.sggCd.find(sgg => sgg.text == item.sggNm)?.text
|
||||
}
|
||||
})
|
||||
const data = Array.isArray(resData)
|
||||
? resData.map((item: customDrgt) => {
|
||||
return {
|
||||
"시도": item.ctpvCd,
|
||||
"시군구": item.sggNm,
|
||||
"정상(횟수)": item.normal,
|
||||
"관심(횟수)": item.attention,
|
||||
"주의(횟수)": item.caution,
|
||||
"관심+주의(횟수)": item.warning,
|
||||
"가뭄순위": item.ranking,
|
||||
}
|
||||
}, [drgtMoRes]);
|
||||
}) : [];
|
||||
setExcelData(data)
|
||||
setTableData(resData)
|
||||
}, [drgtRes])
|
||||
|
||||
const getSelCity = (val: string | number) => {
|
||||
const cityValue = String(val);
|
||||
useEffect(() => {
|
||||
if (drgtMoRes && drgtMoRes.length > 0) {
|
||||
setDrgtModal({ isOpen: true, data: drgtMoRes });
|
||||
}
|
||||
}, [drgtMoRes]);
|
||||
|
||||
if (cityValue === "0") {
|
||||
setSelectSgg([{ val: '0', text: '전체' }]);
|
||||
setSelectedSgg('0');
|
||||
setObjSearch({ city: cityValue, sgg: '0' });
|
||||
} else {
|
||||
const data = getCity(cityValue, code.sggCd);
|
||||
setSelectSgg(data);
|
||||
const getSelCity = (val: string | number) => {
|
||||
const cityValue = String(val);
|
||||
|
||||
const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? "");
|
||||
setSelectedSgg(firstSggVal);
|
||||
setObjSearch({ city: cityValue, sgg: firstSggVal });
|
||||
}
|
||||
};
|
||||
if (cityValue === "0") {
|
||||
setSelectSgg([{ val: '0', text: '전체' }]);
|
||||
setSelectedSgg('0');
|
||||
setObjSearch({ city: cityValue, sgg: '0' });
|
||||
} else {
|
||||
const data = getCity(cityValue, code.sggCd);
|
||||
setSelectSgg(data);
|
||||
|
||||
const getSgg = (val: string | number) => {
|
||||
setSelectedSgg(String(val));
|
||||
setObjSearch({ ...objSearch, sgg: String(val) });
|
||||
};
|
||||
const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? "");
|
||||
setSelectedSgg(firstSggVal);
|
||||
setObjSearch({ city: cityValue, sgg: firstSggVal });
|
||||
}
|
||||
};
|
||||
|
||||
const getSgg = (val: string | number) => {
|
||||
setSelectedSgg(String(val));
|
||||
setObjSearch({ ...objSearch, sgg: String(val) });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (fromDate === undefined && toDate === undefined) {
|
||||
@@ -121,6 +122,19 @@ export default function DrgtSttn() {
|
||||
setToDate(formatted);
|
||||
}
|
||||
}, [fromDate, toDate])
|
||||
|
||||
function onViewMapClick() {
|
||||
const params = new URLSearchParams({
|
||||
ctpvCd: String(objSearch.city),
|
||||
sggCd: String(objSearch.sgg),
|
||||
fromDate: fromDate || '',
|
||||
toDate: toDate || ''
|
||||
});
|
||||
const popupOptions = `width=${window.screen.availWidth},height=${window.screen.availHeight},left=0,top=0,resizable=yes,scrollbars=yes`;
|
||||
popupRef.current = window.open(`/mapDrgtPopUp?${params.toString()}`, "가뭄 지도 보기 팝업", popupOptions);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentsDiv>
|
||||
@@ -136,7 +150,7 @@ export default function DrgtSttn() {
|
||||
selectClass={""}
|
||||
selectArray={code.cityCd || []}
|
||||
onSelectValue={getSelCity}
|
||||
value={objSearch?.city ||code.cityCd[0]?.val ||""}
|
||||
value={objSearch?.city || code.cityCd[0]?.val || ""}
|
||||
/>
|
||||
<SelectBox
|
||||
className="col-span-2 w-1/6 h-full text-2xl ml-8"
|
||||
@@ -144,7 +158,7 @@ export default function DrgtSttn() {
|
||||
selectClass={""}
|
||||
selectArray={selectSgg || [{ val: '0', text: '전체' }]}
|
||||
onSelectValue={getSgg}
|
||||
value={selectedSgg ||'0'}
|
||||
value={selectedSgg || '0'}
|
||||
/>
|
||||
<div className="col-span-5 w-auto">
|
||||
<div className="flex flex-row items-center">
|
||||
@@ -156,7 +170,7 @@ export default function DrgtSttn() {
|
||||
onChange: (date: string | undefined) => {
|
||||
const year = date ? date.slice(0, 4) : '';
|
||||
let month = date?.slice(4);
|
||||
if (month&&month?.length < 2) {
|
||||
if (month && month?.length < 2) {
|
||||
month = month?.padStart(2, "0");
|
||||
}
|
||||
const formatted = `${year}${month}`;
|
||||
@@ -172,7 +186,7 @@ export default function DrgtSttn() {
|
||||
onChange: (date: string | undefined) => {
|
||||
const year = date ? date.slice(0, 4) : '';
|
||||
let month = date?.slice(4);
|
||||
if (month&&month.length < 2) {
|
||||
if (month && month.length < 2) {
|
||||
month = month?.padStart(2, "0");
|
||||
}
|
||||
const formatted = `${year}${month}`;
|
||||
@@ -195,23 +209,23 @@ export default function DrgtSttn() {
|
||||
let sidoText: string = '';
|
||||
let sggText: string | string[] = '';
|
||||
|
||||
if (objSearch?.city.toString().length > 0) {
|
||||
sidoText = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? '';
|
||||
}
|
||||
if (objSearch?.sgg.toString().length > 1) {
|
||||
sggText = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? '';
|
||||
} else {
|
||||
sggText = code.sggCd.filter((item) => item.val.substring(0, 2) === objSearch?.city).map((item) => item.text);
|
||||
if (objSearch?.city.toString().length > 0) {
|
||||
sidoText = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? '';
|
||||
}
|
||||
if (objSearch?.sgg.toString().length > 1) {
|
||||
sggText = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? '';
|
||||
} else {
|
||||
sggText = code.sggCd.filter((item) => item.val.substring(0, 2) === objSearch?.city).map((item) => item.text);
|
||||
|
||||
}
|
||||
if (sidoText === '전체') { sidoText = '' }
|
||||
}
|
||||
if (sidoText === '전체') { sidoText = '' }
|
||||
|
||||
if (sidoText === '세종특별자치시') {
|
||||
sidoText = '세종시';
|
||||
sggText = sidoText;
|
||||
}
|
||||
if (sidoText === '세종특별자치시') {
|
||||
sidoText = '세종시';
|
||||
sggText = sidoText;
|
||||
}
|
||||
|
||||
const searchArr:string[] = [];
|
||||
const searchArr: string[] = [];
|
||||
if (sidoText) searchArr.push(sidoText);
|
||||
if (Array.isArray(sggText)) {
|
||||
searchArr.push(...sggText);
|
||||
@@ -234,7 +248,7 @@ export default function DrgtSttn() {
|
||||
<Button
|
||||
size={"sm"}
|
||||
className={btnClass}
|
||||
onClick={() => { }}
|
||||
onClick={onViewMapClick}
|
||||
>
|
||||
지도보기
|
||||
</Button>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { cmsPostFectch } from "@/components/lib/api";
|
||||
|
||||
export const isLogin = async () => {
|
||||
const headers = {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"X-Site-Id": "WR",
|
||||
"Access-Control-Expose-Headers" : ["Authorization","refresh-token","token-id"]
|
||||
};
|
||||
const param = { userId: "tuser10003", password: "test1234!" };
|
||||
await cmsPostFectch("/user-service/login",param,headers);
|
||||
};
|
||||
|
||||
// export type LoginState = {
|
||||
// login: {} as any;
|
||||
// };
|
||||
|
||||
// export type loginType = LoginState & LoginActions;
|
||||
|
||||
// export const loginState: LoginState = {
|
||||
// login: {},
|
||||
// };
|
||||
|
||||
// export type LoginActions = {
|
||||
// isLogin: () => void;
|
||||
// };
|
||||
|
||||
// export const loginActionExport: (set: any) => LoginActions = (set: any) => {
|
||||
// return {
|
||||
// isLogin: async () => {},
|
||||
// };
|
||||
// };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchType, getFetch } from "@/components/lib/api";
|
||||
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
|
||||
import type { CommonTableProps } from "../../common/type/header";
|
||||
import type { WaterReuseFacility } from "../../ruseFcltMng/fcltSttn/pbnstSttn/type";
|
||||
import {
|
||||
@@ -16,6 +16,7 @@ export type ruseMapState = {
|
||||
ruseMapCount: number;
|
||||
isInit: boolean;
|
||||
fclts: string[];
|
||||
ruseDetailList: customPbnst[];
|
||||
ruseMapHeadList: CommonTableProps[];
|
||||
ruseMapNodeList: CommonTableProps[];
|
||||
};
|
||||
@@ -31,14 +32,17 @@ type ruseMapActions = {
|
||||
fclts: string[]
|
||||
) => void;
|
||||
ruseMapDetail: (ruseFcltId: string) => Promise<customPbnst | undefined>;
|
||||
getRuseMaplList: (bscFctCd: string) => void;
|
||||
};
|
||||
|
||||
|
||||
export type ruseMapType = ruseMapState & ruseMapActions;
|
||||
|
||||
export const ruseMapStateExport: ruseMapState = {
|
||||
ruseMap: {
|
||||
isInit: false,
|
||||
ruseMapRes: [],
|
||||
ruseDetailList: [],
|
||||
fclts: [],
|
||||
ruseMapCount: 1,
|
||||
ruseMapHeadList: pbnSttHeadListExport,
|
||||
@@ -107,5 +111,42 @@ export const ruseMapActionsExport: (set: any) => ruseMapActions = (
|
||||
throw errorHasu; // 호출부에서 catch 가능하도록
|
||||
}
|
||||
},
|
||||
getRuseMaplList: async (bscFctCd: string) => {
|
||||
try {
|
||||
const ruseDetailList = await getFetch(
|
||||
fetchType.WR,
|
||||
`/reuseFc/pubFc/${bscFctCd}`
|
||||
);
|
||||
const year = new Date().getFullYear();
|
||||
const data = [bscFctCd];
|
||||
const rusePrfList = await postFectch(
|
||||
`/reuseFc/pubDetailList/${year}/0/1/10`,
|
||||
data,
|
||||
fetchType.WR
|
||||
);
|
||||
const dataList = ruseDetailList.map((ruse) => {
|
||||
const matchRuse = rusePrfList?.data.find(
|
||||
(prf) => prf.pbnstRuseFcltId === ruse.pbnstRuseFcltId
|
||||
);
|
||||
return {
|
||||
...ruse,
|
||||
prfList: matchRuse.prfList,
|
||||
};
|
||||
});
|
||||
console.log("/dataList ", dataList);
|
||||
set((state: ruseMapState) => {
|
||||
return {
|
||||
...state,
|
||||
ruseMap: {
|
||||
...state.ruseMap,
|
||||
ruseDetailList: dataList,
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch (errorHasu) {
|
||||
console.log(errorHasu);
|
||||
throw errorHasu; // 호출부에서 catch 가능하도록
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { getFetch } from "@/components/lib/api";
|
||||
import type { FacilityInfo, SubsInfo } from "./type";
|
||||
import { DEV_URL_FO } from "@/components/lib/config";
|
||||
|
||||
const checkWrRegList = async () => {
|
||||
const wrRegList = await getFetch("wr", `/reuseFc/pubFc`);
|
||||
return wrRegList;
|
||||
};
|
||||
|
||||
const getRuseFclt = async (
|
||||
page: string,
|
||||
sggOrCity: string,
|
||||
searchTxt?: string
|
||||
) => {
|
||||
const checkWrRegListRes = await checkWrRegList();
|
||||
|
||||
let url = `/prcsFclt/ruseInfo/${page ? page : 1}/5/A/${sggOrCity ? sggOrCity : "0"}/0/1/SW`;
|
||||
|
||||
const ruseList = checkWrRegListRes.filter((item: string) => item !== "");
|
||||
|
||||
if (searchTxt) {
|
||||
url = url + `/${searchTxt}`;
|
||||
}
|
||||
|
||||
const response = await fetch(DEV_URL_FO + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(ruseList),
|
||||
});
|
||||
|
||||
const hasuList = await response.json().then((res: any) => {
|
||||
const data = res.data
|
||||
.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
fctCpc: Number(item.fctCpc) || 0,
|
||||
searchNm: item.fctNm,
|
||||
searchPk: item.bscFctNm,
|
||||
};
|
||||
})
|
||||
.filter((hasu: any) => ruseList.find((ruse: any) => hasu.bscFctCd === ruse));
|
||||
return { data: data, count: res.count };
|
||||
});
|
||||
|
||||
return hasuList;
|
||||
};
|
||||
|
||||
export type DmMapActions = {
|
||||
findRuseFclt(sggOrCity: string, searchTxt: string): void;
|
||||
setSubsInfo(subsInfo: SubsInfo[]): void;
|
||||
};
|
||||
|
||||
export type DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: FacilityInfo[];
|
||||
subsInfo: SubsInfo[];
|
||||
};
|
||||
};
|
||||
|
||||
export const dmMapState: DmMapType = {
|
||||
map: {
|
||||
ruseFcltList: [],
|
||||
subsInfo: []
|
||||
},
|
||||
};
|
||||
export type DmMapStore = DmMapType & DmMapActions;
|
||||
|
||||
export const DmMapReducer = (set: any): DmMapActions => {
|
||||
return {
|
||||
findRuseFclt: async (sggOrCity: string, searchTxt: string) => {
|
||||
const ruseFcltList = await getRuseFclt("1", sggOrCity, searchTxt);
|
||||
|
||||
set((state: DmMapType) => ({
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
ruseFcltList: ruseFcltList.data,
|
||||
},
|
||||
}));
|
||||
},
|
||||
setSubsInfo: async (subsInfo: SubsInfo[]) => {
|
||||
set((state: DmMapType) => ({
|
||||
...state,
|
||||
map: {
|
||||
...state.map,
|
||||
subsInfo: subsInfo,
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
export type FacilityInfo= {
|
||||
bscFctCd: string; // 기본시설 코드
|
||||
bscFctNm: string; // 기본시설명
|
||||
rvbsnCd: string; // 유역 코드
|
||||
rvbsn: string; // 유역명
|
||||
lgbsn: string; // 대권역명
|
||||
mdmbsn: string; // 중권역명
|
||||
rgnCd: string; // 관할청 코드
|
||||
rgnNm: string; // 관할청 명칭
|
||||
adstDstrCd: string; // 행정구역 코드
|
||||
sido: string; // 시도
|
||||
gugun: string; // 구군
|
||||
addr: string; // 주소
|
||||
fctCpc: string; // 시설용량
|
||||
efflWqltStndrdCd: string; // 방류수 수질기준 코드
|
||||
efflWqltStndrdNm: string; // 방류수 수질기준명
|
||||
trtFrfNm: string; // 처리공법명
|
||||
trtDstr: string; // 처리구역
|
||||
rnDt: string; // 사용개시일 (예: YYYYMMDD)
|
||||
cmplDt: string; // 준공일자 (예: YYYYMMDD)
|
||||
trstOprEtpNm: string; // 위탁운영기관명
|
||||
bscFctClsNm: string; // 기본시설 구분명
|
||||
sitArea: string; // 부지면적
|
||||
trtDstrArea: string; // 처리구역 면적
|
||||
swrTrtPopl: string; // 하수처리 인구
|
||||
mnstr: string; // 총괄 유역
|
||||
efflWtrrgn: string; // 방류수역
|
||||
rvNm: string; // 하천명
|
||||
bscFctClsCd: string; // 기본시설 구분 코드
|
||||
rvbsnNm: string; // 유역명
|
||||
tel: string; // 전화번호
|
||||
rswtrYn: string; // 재이용수 여부 ("Y" 또는 "N")
|
||||
ruse: boolean; // 재이용 여부 (boolean)
|
||||
}
|
||||
|
||||
export type SubsInfo={
|
||||
ruseUtztnFcltNm: string; // 재이용시설명
|
||||
ruseUtztnFcltCd: string; // 재이용시설 코드
|
||||
}
|
||||
@@ -231,6 +231,30 @@ export const fcBizSchema = z.object({
|
||||
mdfr: z.string().nullish(),
|
||||
});
|
||||
|
||||
const PrfSchema = z.object({
|
||||
pbnstRuseFcltId: z.string(),
|
||||
ym: z.string(), // 예: "202310"
|
||||
wshwtr: z.number(),
|
||||
coolUswtr: z.number(),
|
||||
cleanUswtr: z.number(),
|
||||
drwtUswtr: z.number(),
|
||||
dltnUswtr: z.number(),
|
||||
ithlRswtr: z.number(),
|
||||
ithlEtcUswtr: z.number(),
|
||||
cleanToilUswtr: z.number(),
|
||||
resn: z.number(),
|
||||
lndwtr: z.number(),
|
||||
hydUswtr: z.number(),
|
||||
rvrKeepUswtr: z.number(),
|
||||
farmUswtr: z.number(),
|
||||
ugwtChr: z.number(),
|
||||
indstUswtr: z.number(),
|
||||
regDt: z.date().default(new Date()).nullish(),
|
||||
rgtr: z.string().nullish(),
|
||||
mdfcnDt: z.date().default(new Date()).nullish(),
|
||||
mdfr: z.string().nullish(),
|
||||
});
|
||||
|
||||
export const pbnstRegZod = z.object({
|
||||
pbnstRuseFcltId: z.string().default(""),
|
||||
swtrmtFcltId: z.string().default(""),
|
||||
@@ -258,11 +282,14 @@ export const pbnstRegZod = z.object({
|
||||
fcBizs: z.array(fcBizSchema).default([]),
|
||||
fcOpers://z.array(fcOperSchema).default([]),
|
||||
z .array(fcOperSchema)
|
||||
.min(1, { message: "재이용시설의 운영정보를 최소 하나를 등록해주세요" })
|
||||
.min(1, { message: "재이용시설의 운영정보를 최소 하나를 등록해주세요" }),
|
||||
prfList: z.array(PrfSchema).nullish(),
|
||||
});
|
||||
|
||||
export const pbnstRegArrayZod = z.array(pbnstRegZod);
|
||||
|
||||
|
||||
|
||||
export const pbnstRegArrayZod = z.array(pbnstRegZod);
|
||||
export type PbnstRegArrrayType = z.infer<typeof pbnstRegArrayZod>;
|
||||
|
||||
export type fcBizType = z.infer<typeof fcBizSchema>;
|
||||
|
||||
@@ -161,7 +161,6 @@ import {
|
||||
type rswtStatState,
|
||||
type rswtStatType,
|
||||
} from "./stats/rswtStat";
|
||||
//import { isLogin } from "./common/login";
|
||||
import { LoginApi } from "@/src/entities/auth";
|
||||
import {
|
||||
authActionsExport,
|
||||
@@ -217,6 +216,12 @@ import {
|
||||
type ldrmntMapState,
|
||||
type ldrmntMapType,
|
||||
} from "./map/ldrmntCoMap";
|
||||
import {
|
||||
DmMapReducer,
|
||||
dmMapState,
|
||||
type DmMapType,
|
||||
type DmMapStore,
|
||||
} from "./map/splyPop";
|
||||
|
||||
export type WrStore = comCodeType &
|
||||
pubFacType &
|
||||
@@ -252,6 +257,7 @@ export type WrStore = comCodeType &
|
||||
ruseMapType &
|
||||
coSttnMapType &
|
||||
ldrmntMapType &
|
||||
DmMapStore &
|
||||
authType;
|
||||
|
||||
export const StoreZus = (
|
||||
@@ -289,6 +295,7 @@ export const StoreZus = (
|
||||
ruseMapState &
|
||||
coSttnMapState &
|
||||
ldrmntMapState &
|
||||
DmMapType &
|
||||
authState = {
|
||||
...comCodeStateExport,
|
||||
...pubFacStateExport,
|
||||
@@ -325,6 +332,7 @@ export const StoreZus = (
|
||||
...ruseMapStateExport,
|
||||
...coSttnMapStateExport,
|
||||
...ldrmntMapStateExport,
|
||||
...dmMapState,
|
||||
}
|
||||
) => {
|
||||
return createStore<WrStore>()((set) => {
|
||||
@@ -372,6 +380,7 @@ export const StoreZus = (
|
||||
...ruseMapActionsExport(set),
|
||||
...coSttnMapActionsExport(set),
|
||||
...ldrmntMapActionsExport(set),
|
||||
...DmMapReducer(set),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
//8880
|
||||
|
||||
import { AUTH_USER_ID, CLAIM_NAME } from "@/src/shared/config";
|
||||
import { DEV_URL_FO, DEV_URL_WR, LOCAL_URL } from "../config/";
|
||||
import { getCookie } from "@common/components/lib/cookie";
|
||||
|
||||
export const fetchType = { COMMON: "common", WR: "wr", LOCAL: "LOCAL" , CS: "CS" };
|
||||
export const fetchType = {
|
||||
COMMON: "common",
|
||||
WR: "wr",
|
||||
LOCAL: "LOCAL",
|
||||
CS: "CS",
|
||||
};
|
||||
|
||||
export const getFetch = async (type: string, url: string) => {
|
||||
const div =
|
||||
type === fetchType.COMMON
|
||||
? DEV_URL_FO
|
||||
: type === fetchType.LOCAL
|
||||
? LOCAL_URL
|
||||
: type === fetchType.CS
|
||||
? ''
|
||||
: DEV_URL_WR;
|
||||
type === fetchType.COMMON
|
||||
? DEV_URL_FO
|
||||
: type === fetchType.LOCAL
|
||||
? LOCAL_URL
|
||||
: type === fetchType.CS
|
||||
? ""
|
||||
: DEV_URL_WR;
|
||||
|
||||
const tokenId = getCookie(AUTH_USER_ID);
|
||||
const Authorization = getCookie(CLAIM_NAME);
|
||||
const response = await fetch(div + url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
AUTH_USER_ID: tokenId ?? "",
|
||||
CLAIM_NAME: Authorization ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`오류 발생: ${response.status}`);
|
||||
console.log(`오류 발생: ${response.status}`);
|
||||
}
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
|
||||
@@ -30,7 +41,10 @@ export const getFetch = async (type: string, url: string) => {
|
||||
// JSON 응답 처리
|
||||
const res = await response.json();
|
||||
return res.data;
|
||||
} else {
|
||||
} else if(contentType.includes('application/octet-stream')) {
|
||||
// 파일 응답 처리 (ex. PDF, Excel 등)
|
||||
|
||||
console.log('contentType ' ,contentType)
|
||||
// 파일 응답 처리 (ex. PDF, Excel 등)
|
||||
const blob = await response.blob();
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
@@ -41,30 +55,36 @@ export const getFetch = async (type: string, url: string) => {
|
||||
a.download = filename;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
|
||||
|
||||
return null; // 다운로드이므로 반환값 없음
|
||||
}
|
||||
}else{
|
||||
throw Error('오류 발생')
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
type === fetchType.COMMON
|
||||
? DEV_URL_FO
|
||||
: type === fetchType.LOCAL
|
||||
? LOCAL_URL
|
||||
: type === fetchType.CS
|
||||
? ""
|
||||
: DEV_URL_WR;
|
||||
const tokenId = getCookie(AUTH_USER_ID);
|
||||
const Authorization = getCookie(CLAIM_NAME);
|
||||
|
||||
const response = await fetch(div + url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
AUTH_USER_ID: tokenId ?? "",
|
||||
CLAIM_NAME: Authorization ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`오류 발생: ${response.status}`);
|
||||
console.log(`오류 발생: ${response.status}`);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
@@ -73,10 +93,11 @@ export const getFetchWithCount = async (type: string, url: string) => {
|
||||
// JSON 응답 처리
|
||||
const res = await response.json();
|
||||
|
||||
|
||||
return res;
|
||||
} else {
|
||||
} 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");
|
||||
@@ -86,19 +107,25 @@ export const getFetchWithCount = async (type: string, url: string) => {
|
||||
a.download = filename;
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(blobUrl);
|
||||
|
||||
|
||||
return null; // 다운로드이므로 반환값 없음
|
||||
}
|
||||
}else{
|
||||
throw Error('오류 발생')
|
||||
}
|
||||
};
|
||||
|
||||
export const postFectch = async (url: string, params: any, type?: string) => {
|
||||
const div = type === fetchType.COMMON ? DEV_URL_FO : DEV_URL_WR;
|
||||
|
||||
const tokenId = getCookie(AUTH_USER_ID);
|
||||
const Authorization = getCookie(CLAIM_NAME);
|
||||
const response = await fetch(div + url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
AUTH_USER_ID: tokenId ?? "",
|
||||
CLAIM_NAME: Authorization ?? "",
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
@@ -108,39 +135,25 @@ export const postFectch = async (url: string, params: any, type?: string) => {
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const cmsPostFectch = async (url: string, params: any,headers:any) => {
|
||||
const response = fetch(url, {
|
||||
method: "POST",
|
||||
headers:headers ,
|
||||
body: JSON.stringify(params),
|
||||
}).then((res) =>
|
||||
{
|
||||
console.log(' res ' , res)
|
||||
console.log(' access-token ' , res.headers.get('access-token'))
|
||||
console.log(' refresh-token ' , res.headers.get('refresh-token'))
|
||||
console.log(' token-id ' , res.headers.get('token-id'))
|
||||
|
||||
})
|
||||
|
||||
|
||||
return await response;
|
||||
};
|
||||
|
||||
export const putFectch = async (url: string, params: any,type?:string) => {
|
||||
export const putFectch = async (url: string, params: any, type?: string) => {
|
||||
const div =
|
||||
type === fetchType.COMMON
|
||||
? DEV_URL_FO
|
||||
: type === fetchType.LOCAL
|
||||
? LOCAL_URL
|
||||
: type === fetchType.CS
|
||||
? ''
|
||||
: DEV_URL_WR;
|
||||
? ""
|
||||
: DEV_URL_WR;
|
||||
|
||||
const tokenId = getCookie(AUTH_USER_ID);
|
||||
const Authorization = getCookie(CLAIM_NAME);
|
||||
|
||||
const response = await fetch(div + url, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
AUTH_USER_ID: tokenId ?? "",
|
||||
CLAIM_NAME: Authorization ?? "",
|
||||
},
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
@@ -150,25 +163,33 @@ export const putFectch = async (url: string, params: any,type?:string) => {
|
||||
return response;
|
||||
};
|
||||
|
||||
export const deleteFectch = async (url: string, params?: any, type?: string) => {
|
||||
export const deleteFectch = async (
|
||||
url: string,
|
||||
params?: any,
|
||||
type?: string
|
||||
) => {
|
||||
const div =
|
||||
type === fetchType.COMMON
|
||||
? DEV_URL_FO
|
||||
: type === fetchType.LOCAL
|
||||
? LOCAL_URL
|
||||
: type === fetchType.CS
|
||||
? ''
|
||||
: DEV_URL_WR;
|
||||
? ""
|
||||
: DEV_URL_WR;
|
||||
const tokenId = getCookie(AUTH_USER_ID);
|
||||
const Authorization = getCookie(CLAIM_NAME);
|
||||
|
||||
const response = await fetch(div + url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
AUTH_USER_ID: tokenId ?? "",
|
||||
CLAIM_NAME: Authorization ?? "",
|
||||
},
|
||||
body: params?JSON.stringify(params):undefined,
|
||||
body: params ? JSON.stringify(params) : undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`오류 발생: ${response.status}`);
|
||||
}
|
||||
return await response.text();
|
||||
};
|
||||
return await response.text();
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
SERVER_API_CS_URL,
|
||||
SERVER_API_FO_URL,
|
||||
SERVER_API_WR_URL,
|
||||
SERVER_SEWERAGE_API_URL,
|
||||
} from "@/src/shared/config";
|
||||
|
||||
//const DEV_PORT = "11080";
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
export const LOCAL_URL_WR = PROXY_HOST + `/api/v1`;
|
||||
export const DEV_URL_WR = SERVER_API_WR_URL;
|
||||
export const DEV_URL_FO = SERVER_API_FO_URL;
|
||||
export const DEV_URL_SN = SERVER_SEWERAGE_API_URL;
|
||||
export const LOCAL_URL = PROXY_HOST;
|
||||
export const CS_URL = SERVER_API_CS_URL;
|
||||
export const CS_TUS_UPLOAD = SERVER_API_CS_TUS_URL;
|
||||
|
||||
@@ -50,6 +50,10 @@ export default function ConCostForm({
|
||||
const ref = useRef<boolean>(false);
|
||||
|
||||
const [checkFinish, setCheckFinish] = useState<boolean>(false);
|
||||
const [lastSub, setLastSub] = useState<{
|
||||
ruseUtztnFcltNm: string;
|
||||
ruseUtztnFcltCd: string;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
@@ -65,6 +69,18 @@ export default function ConCostForm({
|
||||
setCheckFinish(false);
|
||||
}, [selectionVersion]);
|
||||
|
||||
//공급처 팝업 값 셋팅
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === "SET_SUBS_INFO") {
|
||||
setLastSub(event.data.payload);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
return () => window.removeEventListener("message", handleMessage);
|
||||
}, [setLastSub]);
|
||||
|
||||
const zipDowonLoad = () => {
|
||||
fetch(`/2024_constarctFeeProgram.zip`, {}).then(async (response) => {
|
||||
const blobRet = await response.blob();
|
||||
@@ -80,7 +96,7 @@ export default function ConCostForm({
|
||||
const dmndQtyWatch = form.watch("dmndQty");
|
||||
const totalConstCost = form.watch("totalConstCost");
|
||||
const cstrnCst = form.watch("demandCst.cstrnCst");
|
||||
|
||||
const splyList = form.watch("splyCndtList");
|
||||
const calculateConstCost = useCallback(() => {
|
||||
const usgPrcsProcs = usgPrcsProcsWatch;
|
||||
if (!dmndQtyWatch) {
|
||||
@@ -157,8 +173,11 @@ export default function ConCostForm({
|
||||
return;
|
||||
}
|
||||
|
||||
const splycd = form.getValues("splyCndtList")
|
||||
const pk = splycd&&splycd[0]?.ruseUtztnFcltCd?splycd[0]?.ruseUtztnFcltCd:'11000SW001R1'
|
||||
const splycd = form.getValues("splyCndtList");
|
||||
const pk =
|
||||
splycd && splycd[0]?.ruseUtztnFcltCd
|
||||
? splycd[0]?.ruseUtztnFcltCd
|
||||
: "11000SW001R1";
|
||||
|
||||
if (type === "mat") {
|
||||
if (value.demandCst) {
|
||||
@@ -183,11 +202,7 @@ export default function ConCostForm({
|
||||
onClick={() => {
|
||||
toast.dismiss(t.id);
|
||||
|
||||
coMatRvwUpdate(
|
||||
pk,
|
||||
form.getValues("dmdInfoId"),
|
||||
data
|
||||
);
|
||||
coMatRvwUpdate(pk, form.getValues("dmdInfoId"), data);
|
||||
}}
|
||||
>
|
||||
확인
|
||||
@@ -199,7 +214,6 @@ export default function ConCostForm({
|
||||
</span>
|
||||
));
|
||||
} else {
|
||||
|
||||
coMatRvwUpdate(pk, form.getValues("dmdInfoId"), data);
|
||||
}
|
||||
}
|
||||
@@ -216,12 +230,63 @@ export default function ConCostForm({
|
||||
console.log(" form ", form.formState.errors);
|
||||
}, [form.formState]);
|
||||
|
||||
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 === "REQUEST_INIT_SUBS_INFO") {
|
||||
const initSubs =
|
||||
(splyList ?? [])
|
||||
.filter(Boolean)
|
||||
.map((x: any) => ({
|
||||
ruseUtztnFcltNm: x.ruseUtztnFcltNm ?? "",
|
||||
ruseUtztnFcltCd: x.ruseUtztnFcltCd ?? "",
|
||||
}));
|
||||
|
||||
// 응답: 옵션 객체 형태로 전송 (TS 에러 방지)
|
||||
const target = event.source as Window | null;
|
||||
target?.postMessage(
|
||||
{ type: "INIT_SUBS_INFO", payload: initSubs },
|
||||
{ targetOrigin: origin }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", handler);
|
||||
return () => window.removeEventListener("message", handler);
|
||||
}, [splyList]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
form.register("dmdInfoSn");
|
||||
form.register("dmdInfoId");
|
||||
form.register("ruseUtztnFcltNm");
|
||||
}, []);
|
||||
|
||||
|
||||
const searchCandidate = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const dmdInfoId = form.getValues("dmdInfoId");
|
||||
console.log(dmdInfoId)
|
||||
|
||||
const url = `/splyPopup?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);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr className="mt-8" />
|
||||
@@ -369,8 +434,8 @@ export default function ConCostForm({
|
||||
console.log("dmndQty ", e);
|
||||
//field.onChange(Number(e.target.value) * 365 );
|
||||
}}
|
||||
// value={Number(field.value) * 365 || 0}
|
||||
value={Number(field.value) || 0}
|
||||
// value={Number(field.value) * 365 || 0}
|
||||
value={Number(field.value) || 0}
|
||||
readOnly
|
||||
/>
|
||||
</FormUi>
|
||||
@@ -501,30 +566,34 @@ export default function ConCostForm({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="col-span-3 mt-4">
|
||||
<div className="col-span-3 mt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>공급처 선정</Label>
|
||||
<Button size={"sm"} onClick={(e) => {}} className="bg-none">
|
||||
<Button
|
||||
size={"sm"}
|
||||
onClick={(e) => {
|
||||
console.log(e);
|
||||
setLastSub(null) // 빌드오류때문에 넣은거 삭제필요
|
||||
|
||||
searchCandidate(e);
|
||||
|
||||
//splyList => 해당객체가 대민에서 선택한 후보처 리스트
|
||||
//setLastSub => 해당 스테이트가 선택한 마지막 후보처를 넣어야하는 스테이트
|
||||
}}
|
||||
className="bg-none"
|
||||
>
|
||||
후보지 조회
|
||||
</Button>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name=""
|
||||
render={({ field }) => (
|
||||
<FormUi label="">
|
||||
<Input
|
||||
placeholder=""
|
||||
type="text"
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
}}
|
||||
value={field.value ?? ""}
|
||||
/>
|
||||
</FormUi>
|
||||
)}
|
||||
/>
|
||||
</div> */}
|
||||
<FormUi label="">
|
||||
<Input
|
||||
placeholder=""
|
||||
type="text"
|
||||
value={lastSub?.ruseUtztnFcltNm || ""}
|
||||
readOnly
|
||||
/>
|
||||
</FormUi>
|
||||
</div>
|
||||
<div className="col-span-3 mt-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
@@ -536,9 +605,8 @@ export default function ConCostForm({
|
||||
type="number"
|
||||
step="0.01"
|
||||
onChange={(e) => {
|
||||
if (readonly) return;
|
||||
|
||||
if(readonly) return;
|
||||
|
||||
field.onChange(Number(e.target.value));
|
||||
}}
|
||||
value={field.value ?? 0}
|
||||
|
||||
@@ -34,11 +34,13 @@ export default function HtdflRegForm({
|
||||
isModal,
|
||||
readonly = false,
|
||||
setIsModal,
|
||||
isMap
|
||||
}: {
|
||||
htdflForm: customHtdflBdst;
|
||||
isModal: boolean;
|
||||
readonly?: boolean;
|
||||
setIsModal?: (modal: boolean) => void;
|
||||
isMap?: boolean;
|
||||
}) {
|
||||
const { code, htdflBdstUpdate, htdflBdstReg } = useWrStore((state) => state);
|
||||
const [frontPHdata, setFrontPHdata] = useState("");
|
||||
@@ -140,14 +142,16 @@ export default function HtdflRegForm({
|
||||
|
||||
const [divStyle, setDivStyle] = useState("h-[53vh]");
|
||||
useEffect(() => {
|
||||
if (isModal === true) {
|
||||
setDivStyle("h-[53vh] max-h-[37vh]");
|
||||
if(isMap){
|
||||
setDivStyle("h-[100vh]")
|
||||
}else if (isModal === true) {
|
||||
setDivStyle("h-[53vh] max-h-[37vh]")
|
||||
} else if (readonly === true) {
|
||||
setDivStyle("h-[62vh]");
|
||||
setDivStyle("h-[62vh]")
|
||||
} else {
|
||||
setDivStyle("h-[57vh]")
|
||||
}
|
||||
}, [isModal, readonly]);
|
||||
}, [isModal, readonly ,isMap]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("form errors:", form.formState.errors);
|
||||
|
||||
@@ -30,11 +30,13 @@ export default function RainRegForm({
|
||||
isModal,
|
||||
readonly = false,
|
||||
setIsModal,
|
||||
isMap
|
||||
}: {
|
||||
rainForm: customRainBdst;
|
||||
isModal: boolean;
|
||||
setIsModal?: (modal: boolean) => void;
|
||||
readonly?: boolean;
|
||||
isMap?: boolean;
|
||||
}) {
|
||||
const { code, rainBdstReg, rainBdstUpdate } = useWrStore(
|
||||
(state) => state
|
||||
@@ -54,14 +56,16 @@ export default function RainRegForm({
|
||||
|
||||
const [divStyle, setDivStyle] = useState("h-[53vh]");
|
||||
useEffect(() => {
|
||||
if (isModal === true) {
|
||||
if(isMap=== true){
|
||||
setDivStyle("h-[100vh]")
|
||||
}else if (isModal === true) {
|
||||
setDivStyle("h-[53vh] max-h-[37vh]")
|
||||
} else if (readonly === true) {
|
||||
setDivStyle("h-[62vh]")
|
||||
} else {
|
||||
setDivStyle("h-[57vh]")
|
||||
}
|
||||
}, [isModal, readonly]);
|
||||
}, [isModal, readonly ,isMap]);
|
||||
|
||||
const cleanRainData = (raw: any): z.infer<typeof rainZod> => {
|
||||
const buildings =
|
||||
|
||||
@@ -34,25 +34,30 @@ export default function RswtrRegForm({
|
||||
isModal,
|
||||
setIsModal,
|
||||
readonly = false,
|
||||
isMap
|
||||
}: {
|
||||
rswtForm: customRswtBdst;
|
||||
isModal: boolean;
|
||||
setIsModal?: (isModal: boolean) => void;
|
||||
readonly?: boolean;
|
||||
isMap?: boolean;
|
||||
|
||||
}) {
|
||||
const { code, rswtBdstUpdate, rswtBdstReg } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
const [divStyle, setDivStyle] = useState("h-[53vh]");
|
||||
useEffect(() => {
|
||||
if (isModal === true) {
|
||||
if(isMap){
|
||||
setDivStyle("h-[100vh]")
|
||||
}else if (isModal === true) {
|
||||
setDivStyle("h-[53vh] max-h-[37vh]")
|
||||
} else if (readonly === true) {
|
||||
setDivStyle("h-[62vh]")
|
||||
} else {
|
||||
setDivStyle("h-[57vh]")
|
||||
}
|
||||
}, [isModal, readonly]);
|
||||
}, [isModal, readonly ,isMap]);
|
||||
// const defaultValues: z.infer<typeof rswtZod> = rswtZod.parse({});
|
||||
|
||||
const form = useForm<z.infer<typeof rswtZod>>({
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export default function coSttnDetailPopup(){
|
||||
|
||||
|
||||
return(
|
||||
<>
|
||||
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useWrStore } from "@/app/store";
|
||||
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
|
||||
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@common/components/ui";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export function RuseDetailPopup({ bscFctCd }: { bscFctCd: string }) {
|
||||
const { ruseMap, getRuseMaplList } = useWrStore((state) => state);
|
||||
const { ruseDetailList } = ruseMap;
|
||||
const [fcltTab, setFcltTab] = useState<string>("");
|
||||
const [isDetail, setIsDetail] = useState<customPbnst>({} as customPbnst);
|
||||
const initRef = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!initRef.current) {
|
||||
getRuseMaplList(bscFctCd);
|
||||
initRef.current = true;
|
||||
}
|
||||
}, [bscFctCd]);
|
||||
|
||||
useEffect(() => {
|
||||
const detail = ruseDetailList.find(
|
||||
(list) => list.pbnstRuseFcltId === fcltTab
|
||||
);
|
||||
if (detail) setIsDetail(detail);
|
||||
}, [fcltTab, ruseDetailList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ruseDetailList && ruseDetailList[0]) {
|
||||
setFcltTab(ruseDetailList[0].pbnstRuseFcltId);
|
||||
}
|
||||
}, [ruseDetailList]);
|
||||
|
||||
if (!(ruseDetailList && ruseDetailList[0])) return;
|
||||
return (
|
||||
<div>
|
||||
<Tabs value={fcltTab} onValueChange={setFcltTab}>
|
||||
<TabsList className="mt-4">
|
||||
{ruseDetailList.map((item, index) => {
|
||||
return (
|
||||
<TabsTrigger
|
||||
key={item.pbnstRuseFcltId + index}
|
||||
className="!text-xl"
|
||||
value={item.pbnstRuseFcltId}
|
||||
>
|
||||
{item.ruseUtztnFcltNm}
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent className="flex-1 px-4" value={fcltTab}>
|
||||
<PbnstSttnModal data={isDetail} isMap={true} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
export function PbnstSttnModal({ data, titleVisible }: { data: customPbnst; titleVisible?: boolean }) {
|
||||
export function PbnstSttnModal({ data, titleVisible,isMap}: { data: customPbnst; titleVisible?: boolean ,isMap?:boolean}) {
|
||||
const { pbnstSttn, prfmnc, pbnstPrfmncSttn } = useWrStore((state) => state);
|
||||
const [fcltTab, setFcltTab] = useState<string>("fclt");
|
||||
const [ruseTab, setRuseTab] = useState<string>("totalPrf");
|
||||
@@ -223,17 +223,24 @@ export function PbnstSttnModal({ data, titleVisible }: { data: customPbnst; titl
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (pbnstSttn.pbnstSttnRes && data) {
|
||||
const prfList = pbnstSttn.pbnstSttnRes.find(
|
||||
(item) => item.pbnstRuseFcltId === data.pbnstRuseFcltId
|
||||
)?.prfList;
|
||||
if(isMap){
|
||||
if(data.prfList &&data.prfList.length>0){
|
||||
getTablePrfData(data.prfList);
|
||||
}
|
||||
}else{
|
||||
|
||||
if (prfList) {
|
||||
|
||||
getTablePrfData(prfList);
|
||||
if (pbnstSttn.pbnstSttnRes && data) {
|
||||
const prfList = pbnstSttn.pbnstSttnRes.find(
|
||||
(item) => item.pbnstRuseFcltId === data.pbnstRuseFcltId
|
||||
)?.prfList;
|
||||
|
||||
if (prfList) {
|
||||
|
||||
getTablePrfData(prfList);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [pbnstSttn.pbnstSttnRes, data]);
|
||||
}, [pbnstSttn.pbnstSttnRes, data,isMap]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
@@ -252,7 +259,7 @@ export function PbnstSttnModal({ data, titleVisible }: { data: customPbnst; titl
|
||||
<Title title={"하수재이용 시설 상세정보"} />
|
||||
</>
|
||||
)}
|
||||
<ScrollArea className={"h-[70vh]"}>
|
||||
<ScrollArea className={ isMap==true? "h-[100vh]":"h-[70vh]" }>
|
||||
<PbnstMngForm
|
||||
form={form}
|
||||
formType={"sttn"}
|
||||
|
||||
@@ -15,25 +15,26 @@ import RswtrRegForm from "../../../form/fcltReg/rswtrReg/rswtrRegForm";
|
||||
export default function SttnDetailModal({
|
||||
type,
|
||||
formObj,
|
||||
isMap ,
|
||||
}: {
|
||||
type: string;
|
||||
formObj: any;
|
||||
isMap?: boolean;
|
||||
}) {
|
||||
const { rainSttn, rswtSttn, rainSttnChkSearch } = useWrStore(
|
||||
const { rainSttn, rswtSttn, rainSttnChkSearch } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
const [detailData, setDetailData] = useState({
|
||||
head: [] as CommonTableProps[],
|
||||
node:[] as CommonTableProps[],
|
||||
node: [] as CommonTableProps[],
|
||||
data: {} as any,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (type === "RNB" && formObj?.reuseBuilding[0]) {
|
||||
rainSttnChkSearch(formObj.reuseBuilding[0].bdstRuseFcltSn);
|
||||
|
||||
} else if (type === "RST") {
|
||||
// rswtChkPolAndGuide(formObj.reuseBuilding[0].bdstRuseFcltSn)
|
||||
// rswtChkPolAndGuide(formObj.reuseBuilding[0].bdstRuseFcltSn)
|
||||
}
|
||||
}, [type, formObj]);
|
||||
|
||||
@@ -60,61 +61,89 @@ export default function SttnDetailModal({
|
||||
});
|
||||
setDetailData({
|
||||
head: rainSttn.rainSttnDetailHeadList,
|
||||
node:rainSttn.rainSttnDetailNodeList,
|
||||
node: rainSttn.rainSttnDetailNodeList,
|
||||
data: data,
|
||||
});
|
||||
} else if (type === "RST" && rswtSttn.rswtSttnRes) {
|
||||
// const tableTotalData = rswtChkTableData(rswtSttn.rswtSttnRes as unknown as rswtChkZodType[]);
|
||||
|
||||
// setDetailData({
|
||||
// head: rswtSttn.rswtSttHeadList,
|
||||
// node:rswtSttn.rswtSttNodeList,
|
||||
// // data: tableTotalData,
|
||||
// });
|
||||
|
||||
|
||||
// const tableTotalData = rswtChkTableData(rswtSttn.rswtSttnRes as unknown as rswtChkZodType[]);
|
||||
// setDetailData({
|
||||
// head: rswtSttn.rswtSttHeadList,
|
||||
// node:rswtSttn.rswtSttNodeList,
|
||||
// // data: tableTotalData,
|
||||
// });
|
||||
}
|
||||
}, [rainSttn.rainChkRes]);
|
||||
|
||||
useEffect(() =>{
|
||||
setDetailData({data:[] , head:[], node:[]})
|
||||
},[])
|
||||
|
||||
useEffect(() => {
|
||||
setDetailData({ data: [], head: [], node: [] });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
<Title title={ type === "RNB" ? "빗물이용시설 상세정보"
|
||||
: type === "RST" ? "중수도시설 상세정보"
|
||||
: "온배수시설 상세정보"}></Title>
|
||||
<ScrollArea className={detailData.data.length>0?"h-[80vh]":"h-[70vh]"} >
|
||||
<Title
|
||||
title={
|
||||
type === "RNB"
|
||||
? "빗물이용시설 상세정보"
|
||||
: type === "RST"
|
||||
? "중수도시설 상세정보"
|
||||
: "온배수시설 상세정보"
|
||||
}
|
||||
></Title>
|
||||
<ScrollArea
|
||||
className={isMap ?"h-[100vh]":
|
||||
detailData.data.length > 0
|
||||
? "h-[80vh]"
|
||||
: "h-[70vh]"
|
||||
}
|
||||
>
|
||||
{type === "RNB" ? (
|
||||
<RainRegForm rainForm={formObj as customRainBdst} isModal={false} readonly={true}/>
|
||||
<RainRegForm
|
||||
rainForm={formObj as customRainBdst}
|
||||
isModal={false}
|
||||
readonly={true}
|
||||
isMap={isMap}
|
||||
/>
|
||||
) : type === "RST" ? (
|
||||
<RswtrRegForm rswtForm={formObj as customRswtBdst} isModal={false} readonly={true}/>
|
||||
) : (<HtdflRegForm htdflForm={formObj as customHtdflBdst} isModal={false} readonly={true}/>)}
|
||||
<RswtrRegForm
|
||||
rswtForm={formObj as customRswtBdst}
|
||||
isModal={false}
|
||||
readonly={true}
|
||||
isMap={isMap}
|
||||
/>
|
||||
) : (
|
||||
<HtdflRegForm
|
||||
htdflForm={formObj as customHtdflBdst}
|
||||
isModal={false}
|
||||
readonly={true}
|
||||
isMap={isMap}
|
||||
/>
|
||||
)}
|
||||
|
||||
{detailData.data.length>0?
|
||||
<UiTable
|
||||
headName={
|
||||
type === "RNB" ? "빗물이용시설 상세정보"
|
||||
: type === "RST" ? "중수도시설 상세정보"
|
||||
: "온배수시설 상세정보"
|
||||
}
|
||||
tableData={detailData.data || []}
|
||||
headlist={detailData.head || []}
|
||||
|
||||
pageSize={0}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
onPageChange={function (page: number): void {
|
||||
console.log('page ', page)
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={detailData.node} />
|
||||
</UiTable>:<></>}
|
||||
{detailData.data.length > 0 ? (
|
||||
<UiTable
|
||||
headName={
|
||||
type === "RNB"
|
||||
? "빗물이용시설 상세정보"
|
||||
: type === "RST"
|
||||
? "중수도시설 상세정보"
|
||||
: "온배수시설 상세정보"
|
||||
}
|
||||
tableData={detailData.data || []}
|
||||
headlist={detailData.head || []}
|
||||
pageSize={0}
|
||||
total={0}
|
||||
cellClick={undefined}
|
||||
onPageChange={function (page: number): void {
|
||||
console.log("page ", page);
|
||||
throw new Error("Function not implemented.");
|
||||
}}
|
||||
>
|
||||
<HeadMakeColSpan upHeadList={detailData.node} />
|
||||
</UiTable>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ScrollArea>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user